@take-out/scripts 0.0.92 → 0.0.94
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 +4 -2
- package/src/build-initial.ts +76 -81
- package/src/clean.ts +21 -21
- package/src/cmd.ts +82 -0
- package/src/dev-tunnel.ts +138 -159
- package/src/ensure-port.ts +62 -70
- package/src/ensure-tunnel.ts +13 -9
- package/src/env-pull.ts +49 -47
- package/src/env-update.ts +143 -175
- package/src/exec-with-env.ts +14 -11
- package/src/helpers/args.ts +4 -4
- package/src/helpers/get-test-env.ts +5 -3
- package/src/node-version-check.ts +9 -5
- package/src/release.ts +439 -394
- package/src/sst-get-environment.ts +5 -1
- package/src/typecheck.ts +19 -16
- package/src/up.ts +355 -374
- package/src/update-changelog.ts +39 -43
- package/src/update-local-env.ts +139 -158
- package/src/wait-for-dev.ts +21 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@take-out/scripts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.94",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/run.ts",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@
|
|
31
|
+
"@clack/prompts": "^0.8.2",
|
|
32
|
+
"@take-out/helpers": "0.0.94",
|
|
33
|
+
"picocolors": "^1.1.1"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|
|
34
36
|
"vxrn": "^1.4.15"
|
package/src/build-initial.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
10
|
+
const hasPackages = await exists(`./packages`)
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
24
|
-
if (hasPackages) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
if (needsBuild) {
|
|
26
|
+
// build helpers first as other packages depend on it
|
|
27
|
+
await $`cd packages/helpers && bun run build`
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
+
if (native) {
|
|
20
|
+
console.info('removing ios and android folders...')
|
|
21
|
+
await $`rm -rf ios android`
|
|
22
|
+
}
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
console.info('removing ios and android folders...')
|
|
22
|
-
await $`rm -rf ios android`
|
|
23
|
-
}
|
|
24
|
+
console.info('cleanup complete!')
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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,82 @@
|
|
|
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(fn: ((info: { description: string; args?: string }) => void) | undefined) {
|
|
22
|
+
_interceptCmd = fn
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createCmd(description: string) {
|
|
26
|
+
let argsSpec: string | undefined
|
|
27
|
+
|
|
28
|
+
function makeRun<S extends string>(spec: S): RunFn<S> {
|
|
29
|
+
return async (fn) => {
|
|
30
|
+
if (_interceptCmd) {
|
|
31
|
+
_interceptCmd({ description, args: argsSpec })
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const colors = (await import(`picocolors`)).default
|
|
36
|
+
|
|
37
|
+
if (process.argv.includes(`--help`) || process.argv.includes(`-h`)) {
|
|
38
|
+
console.info()
|
|
39
|
+
console.info(colors.bold(description))
|
|
40
|
+
if (argsSpec) {
|
|
41
|
+
console.info()
|
|
42
|
+
const flags = [...argsSpec.matchAll(/--?([a-z-]+)\s+(string|number|boolean)/gi)]
|
|
43
|
+
for (const [, flag, type] of flags) {
|
|
44
|
+
console.info(` --${flag} ${colors.dim(type!)}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
console.info()
|
|
48
|
+
process.exit(0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const [{ $ }, prompt, { run }, { args: parseArgs }, fs, path, os] = await Promise.all([
|
|
52
|
+
import(`bun`),
|
|
53
|
+
import(`@clack/prompts`),
|
|
54
|
+
import(`./helpers/run`),
|
|
55
|
+
import(`./helpers/args`),
|
|
56
|
+
import(`node:fs/promises`),
|
|
57
|
+
import(`node:path`),
|
|
58
|
+
import(`node:os`),
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
const args = spec ? parseArgs(spec) : ({ rest: [] } as Args<S>)
|
|
62
|
+
await fn({ args, $, colors, prompt, run, fs, path, os } as Prettify<
|
|
63
|
+
{ args: Args<S> } & CmdContext
|
|
64
|
+
>)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
args<const S extends string>(spec: S) {
|
|
70
|
+
argsSpec = spec
|
|
71
|
+
return { run: makeRun(spec) }
|
|
72
|
+
},
|
|
73
|
+
run: makeRun(`` as ``),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function cmd(description: string): ReturnType<typeof createCmd>
|
|
78
|
+
export function cmd(strings: TemplateStringsArray): ReturnType<typeof createCmd>
|
|
79
|
+
export function cmd(input: string | TemplateStringsArray) {
|
|
80
|
+
const description = typeof input === `string` ? input : input[0] || ``
|
|
81
|
+
return createCmd(description)
|
|
82
|
+
}
|
package/src/dev-tunnel.ts
CHANGED
|
@@ -1,179 +1,158 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
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')
|
|
10
|
+
|
|
11
|
+
handleProcessExit()
|
|
12
|
+
|
|
13
|
+
const TUNNEL_CONFIG_DIR = path.join(os.homedir(), '.onechat-tunnel')
|
|
14
|
+
const TUNNEL_ID_FILE = path.join(TUNNEL_CONFIG_DIR, 'tunnel-id.txt')
|
|
68
15
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
}
|
|
29
|
+
}
|
|
75
30
|
|
|
76
|
-
|
|
31
|
+
async function ensureAuthenticated(): Promise<boolean> {
|
|
32
|
+
const certPath = path.join(os.homedir(), '.cloudflared', 'cert.pem')
|
|
33
|
+
if (existsSync(certPath)) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
77
36
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
37
|
+
try {
|
|
38
|
+
await run('cloudflared tunnel login')
|
|
39
|
+
return true
|
|
40
|
+
} catch {
|
|
41
|
+
console.error('\n❌ Authentication failed')
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
81
44
|
}
|
|
82
45
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
|
|
89
58
|
try {
|
|
90
|
-
const { stdout } = await run(
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
console.error('Failed to
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
const { stdout } = await run(`cloudflared tunnel info ${tunnelId} --output json`, {
|
|
135
|
+
captureOutput: true,
|
|
136
|
+
silent: true,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const info = JSON.parse(stdout)
|
|
141
|
+
const hostname = info.hostname || `${tunnelId}.cfargotunnel.com`
|
|
142
|
+
writeFileSync(
|
|
143
|
+
path.join(TUNNEL_CONFIG_DIR, 'tunnel-url.txt'),
|
|
144
|
+
`https://${hostname}`
|
|
145
|
+
)
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// use fallback url already saved
|
|
148
|
+
}
|
|
149
149
|
} catch (e) {
|
|
150
|
-
// use fallback
|
|
150
|
+
// use fallback url already saved
|
|
151
151
|
}
|
|
152
|
-
}
|
|
153
|
-
// use fallback URL already saved
|
|
154
|
-
}
|
|
155
|
-
}, 3000)
|
|
152
|
+
}, 3000)
|
|
156
153
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
// run tunnel - managed by handleProcessExit
|
|
155
|
+
await run(`cloudflared tunnel run --url http://localhost:${port} ${tunnelId}`, {
|
|
156
|
+
detached: false,
|
|
157
|
+
})
|
|
160
158
|
})
|
|
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)
|