@take-out/scripts 0.0.28

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.
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bun
2
+
3
+ const netstat = Bun.spawnSync(`netstat -rn`.split(' ')).stdout
4
+ const isTunnelActive = netstat.includes(`10.0.8/22`)
5
+
6
+ if (!isTunnelActive) {
7
+ console.error(`No tunnel active, run bun sst:tunnel:production first`)
8
+ process.exit(1)
9
+ }
10
+
11
+ export function ensureEnv() {
12
+ // make it a module
13
+ }
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { existsSync, writeFileSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { getEnvironment } from './sst-get-environment'
6
+
7
+ const rootDir = process.cwd()
8
+
9
+ const envFilePath = join(rootDir, '.env.production')
10
+ if (existsSync(envFilePath)) {
11
+ console.error(
12
+ '❌ Error: .env.production already exists. Please remove or rename it first.'
13
+ )
14
+ process.exit(1)
15
+ }
16
+
17
+ const packageJson = require(join(rootDir, 'package.json'))
18
+ const envVarsNeeded = packageJson.env ? Object.keys(packageJson.env) : []
19
+ if (!envVarsNeeded.length) {
20
+ console.error(
21
+ `❌ Error: No environment variables specified in package.json 'env' array.`
22
+ )
23
+ process.exit(1)
24
+ }
25
+
26
+ try {
27
+ const envVars = getEnvironment('WebApp')
28
+
29
+ let envFileContent = ''
30
+ let foundCount = 0
31
+
32
+ for (const varName of envVarsNeeded) {
33
+ if (envVars[varName] !== undefined) {
34
+ envFileContent += `${varName}=${envVars[varName]}\n`
35
+ foundCount++
36
+ } else {
37
+ console.warn(`⚠️ Warning: Didn't find env: ${varName}`)
38
+ }
39
+ }
40
+
41
+ if (foundCount === 0) {
42
+ console.error(`❌ Error: None of the required environment variables were found.`)
43
+ process.exit(1)
44
+ }
45
+
46
+ writeFileSync(envFilePath, envFileContent)
47
+
48
+ console.info(
49
+ `✅ Success! ${foundCount} environment variables written to .env.production`
50
+ )
51
+ } catch (error) {
52
+ console.error('❌ Error fetching environment variables:', error)
53
+ process.exit(1)
54
+ }
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Environment Variable Management Script
5
+ *
6
+ * This script reads the environment variables defined in package.json
7
+ * and updates the necessary files with the correct environment variables.
8
+ *
9
+ * Files updated:
10
+ * - .github/workflows/deploy.yml - GitHub Actions environment variables
11
+ * - src/constants/env-server.ts - Server environment variables
12
+ * - Dockerfile - Docker build ARG definitions
13
+ */
14
+
15
+ import { readFileSync, writeFileSync } from 'node:fs'
16
+
17
+ const packageJson = JSON.parse(readFileSync('package.json', 'utf-8'))
18
+ const envVars = packageJson.env as Record<string, boolean | string>
19
+
20
+ if (!envVars || Array.isArray(envVars) || typeof envVars !== 'object') {
21
+ console.error('No environment variables found in package.json')
22
+ process.exit(1)
23
+ }
24
+
25
+ const markerStart = '🔒 start - this is generated by "bun env:update"'
26
+ const markerEnd = '🔒 end - this is generated by "bun env:update"'
27
+
28
+ const yamlStartMarker = `# ${markerStart}`
29
+ const yamlEndMarker = `# ${markerEnd}`
30
+
31
+ const jsStartMarker = `// ${markerStart}`
32
+ const jsEndMarker = `// ${markerEnd}`
33
+
34
+ function updateDeployYml() {
35
+ const deployYmlPath = '.github/workflows/ci.yml'
36
+ let deployYml = readFileSync(deployYmlPath, 'utf-8')
37
+
38
+ if (!deployYml.includes(yamlStartMarker) || !deployYml.includes(yamlEndMarker)) {
39
+ throw new Error(`Markers not found in ${deployYmlPath}`)
40
+ }
41
+
42
+ const newlines = ` `
43
+
44
+ const envSection = Object.keys(envVars)
45
+ .map((key) => `${newlines}${key}: \${{ secrets.${key} }}`)
46
+ .join('\n')
47
+
48
+ const newDeployYml = deployYml.replace(
49
+ new RegExp(`${yamlStartMarker}(.|\n)*?${yamlEndMarker}`, 'gm'),
50
+ `${yamlStartMarker}\n${envSection}\n${newlines}${yamlEndMarker}`
51
+ )
52
+
53
+ writeFileSync(deployYmlPath, newDeployYml, 'utf-8')
54
+ console.info('✅ Updated Github workflow')
55
+ }
56
+
57
+ function updateEnvServerTs() {
58
+ const envServerPath = 'src/server/env-server.ts'
59
+
60
+ let envServer = ''
61
+ try {
62
+ envServer = readFileSync(envServerPath, 'utf-8')
63
+ } catch (_error) {
64
+ throw new Error(`File ${envServerPath} not found`)
65
+ }
66
+
67
+ if (!envServer.includes(jsStartMarker) || !envServer.includes(jsEndMarker)) {
68
+ throw new Error(`Markers not found in ${envServerPath}`)
69
+ }
70
+
71
+ const envExports = Object.entries(envVars)
72
+ .map(
73
+ ([key, value]) =>
74
+ `export const ${key} = ensureEnv('${key}'${typeof value === 'string' ? `, ${JSON.stringify(value)}` : ''})`
75
+ )
76
+ .join('\n')
77
+
78
+ const newEnvServer = envServer.replace(
79
+ new RegExp(`${jsStartMarker}(.|\n)*?${jsEndMarker}`, 'm'),
80
+ `${jsStartMarker}\n${envExports}\n${jsEndMarker}`
81
+ )
82
+
83
+ writeFileSync(envServerPath, newEnvServer, 'utf-8')
84
+ console.info('✅ Updated server env')
85
+ }
86
+
87
+ // function updateDockerfile() {
88
+ // const dockerfilePath = 'Dockerfile'
89
+
90
+ // let dockerfile = ''
91
+ // try {
92
+ // dockerfile = readFileSync(dockerfilePath, 'utf-8')
93
+ // } catch (_error) {
94
+ // throw new Error(`File ${dockerfilePath} not found`)
95
+ // }
96
+
97
+ // if (!dockerfile.includes(yamlStartMarker) || !dockerfile.includes(yamlEndMarker)) {
98
+ // throw new Error(`Markers not found in ${dockerfilePath}`)
99
+ // }
100
+
101
+ // const dockerArgs = Object.keys(envVars)
102
+ // .map((env) => `ARG ${env}`)
103
+ // .join('\n')
104
+
105
+ // const newDockerfile = dockerfile.replace(
106
+ // new RegExp(`${yamlStartMarker}(.|\n)*?${yamlEndMarker}`, 'm'),
107
+ // `${yamlStartMarker}\n${dockerArgs}\n${yamlEndMarker}`
108
+ // )
109
+
110
+ // writeFileSync(dockerfilePath, newDockerfile, 'utf-8')
111
+ // console.info('✅ Updated Dockerfile')
112
+ // }
113
+
114
+ async function updateAll() {
115
+ try {
116
+ updateDeployYml()
117
+ updateEnvServerTs()
118
+ // updateDockerfile()
119
+ console.info('✅ All files updated successfully')
120
+ } catch (error: any) {
121
+ console.error('❌ Update failed:', error.message)
122
+ process.exit(1)
123
+ }
124
+ }
125
+
126
+ await updateAll()
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { ensureExists } from '@take-out/helpers'
4
+ import { loadEnv } from '@take-out/scripts/helpers/env-load'
5
+
6
+ export async function execWithEnvironment(
7
+ environment: 'development' | 'production',
8
+ command: string
9
+ ) {
10
+ const { USE_LOCAL_SERVER } = process.env
11
+
12
+ let envFileEnv: Record<string, string>
13
+
14
+ if (process.env.REMOTE) {
15
+ await import('@take-out/scripts/ensure-tunnel')
16
+ const { getEnvironment } = await import('./sst-get-environment')
17
+ envFileEnv = getEnvironment(process.env.REMOTE)
18
+ ensureExists(envFileEnv)
19
+ } else {
20
+ envFileEnv = await loadEnv(environment)
21
+ }
22
+
23
+ console.info(
24
+ `($): ${command} | env ${environment} ${USE_LOCAL_SERVER ? '| using local endpoints' : ''}`
25
+ )
26
+
27
+ const devEnv = USE_LOCAL_SERVER ? await loadEnv('development') : null
28
+
29
+ const env = {
30
+ ...envFileEnv,
31
+ ...process.env,
32
+ // running local server but hitting most prod endpoints except auth
33
+ ...(USE_LOCAL_SERVER && {
34
+ ONE_SERVER_URL: devEnv?.ONE_SERVER_URL,
35
+ // vite reads from .env.production for us unless defined into process.env
36
+ VITE_PUBLIC_ZERO_SERVER: 'https://start.chat',
37
+ // actually we need to use prod better auth but allow localhost trusted origin, may need other env though
38
+ // BETTER_AUTH_SECRET: devEnv?.BETTER_AUTH_SECRET,
39
+ // BETTER_AUTH_URL: devEnv?.BETTER_AUTH_URL,
40
+ }),
41
+ } as any as Record<string, string>
42
+
43
+ return Bun.spawnSync(command.split(' '), {
44
+ stdin: 'ignore',
45
+ stdout: 'inherit',
46
+ stderr: 'inherit',
47
+ env,
48
+ })
49
+ }
50
+
51
+ if (import.meta.main) {
52
+ const result = await execWithEnvironment(
53
+ (process.env.NODE_ENV as 'development' | 'production') || 'development',
54
+ process.argv.slice(3).join(' ')
55
+ )
56
+ process.exit(result.exitCode)
57
+ }
@@ -0,0 +1,22 @@
1
+ import { Socket } from 'node:net'
2
+
3
+ export function checkPort(port: number, host = '127.0.0.1'): Promise<boolean> {
4
+ return new Promise((resolve) => {
5
+ const tester = new Socket()
6
+
7
+ tester.once('connect', () => {
8
+ tester.destroy()
9
+ resolve(true)
10
+ })
11
+
12
+ tester.once('error', (err: NodeJS.ErrnoException) => {
13
+ if (err.code === 'ECONNREFUSED') {
14
+ resolve(false)
15
+ } else {
16
+ resolve(true)
17
+ }
18
+ })
19
+
20
+ tester.connect(port, host)
21
+ })
22
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ CreateBucketCommand,
3
+ HeadBucketCommand,
4
+ PutBucketLifecycleConfigurationCommand,
5
+ PutBucketVersioningCommand,
6
+ S3Client,
7
+ } from '@aws-sdk/client-s3'
8
+
9
+ export async function ensureS3Bucket(
10
+ bucketName: string,
11
+ options?: {
12
+ region?: string
13
+ retentionDays?: number
14
+ }
15
+ ): Promise<void> {
16
+ const region = options?.region ?? 'us-west-1'
17
+ const retentionDays = options?.retentionDays ?? 30
18
+
19
+ const s3Client = new S3Client({
20
+ region,
21
+ credentials: {
22
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
23
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
24
+ },
25
+ })
26
+
27
+ try {
28
+ // check if bucket exists
29
+ await s3Client.send(new HeadBucketCommand({ Bucket: bucketName }))
30
+ console.info(`✅ S3 bucket exists: ${bucketName}`)
31
+ } catch (error: any) {
32
+ if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
33
+ console.info(`📦 Creating S3 bucket: ${bucketName}`)
34
+
35
+ // create bucket
36
+ await s3Client.send(
37
+ new CreateBucketCommand({
38
+ Bucket: bucketName,
39
+ ...(region !== 'us-east-1' && {
40
+ CreateBucketConfiguration: {
41
+ LocationConstraint: region as any,
42
+ },
43
+ }),
44
+ })
45
+ )
46
+
47
+ // enable versioning for safety
48
+ await s3Client.send(
49
+ new PutBucketVersioningCommand({
50
+ Bucket: bucketName,
51
+ VersioningConfiguration: {
52
+ Status: 'Enabled',
53
+ },
54
+ })
55
+ )
56
+
57
+ // set lifecycle policy to automatically delete old backups
58
+ await s3Client.send(
59
+ new PutBucketLifecycleConfigurationCommand({
60
+ Bucket: bucketName,
61
+ LifecycleConfiguration: {
62
+ Rules: [
63
+ {
64
+ ID: 'delete-old-backups',
65
+ Status: 'Enabled',
66
+ Filter: {
67
+ Prefix: 'db-backups/',
68
+ },
69
+ Expiration: {
70
+ Days: retentionDays,
71
+ },
72
+ NoncurrentVersionExpiration: {
73
+ NoncurrentDays: 7,
74
+ },
75
+ },
76
+ ],
77
+ },
78
+ })
79
+ )
80
+
81
+ console.info(
82
+ `✅ S3 bucket created with ${retentionDays} day retention: ${bucketName}`
83
+ )
84
+ } else {
85
+ throw error
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,26 @@
1
+ import { join } from 'node:path'
2
+ import { loadEnv as vxrnLoadEnv } from 'vxrn/loadEnv'
3
+
4
+ export async function loadEnv(
5
+ environment: 'development' | 'production' | 'dev-prod',
6
+ options?: { optional?: string[]; envPath?: string }
7
+ ) {
8
+ // loads env into process.env
9
+ await vxrnLoadEnv(environment)
10
+
11
+ const Environment = await import(
12
+ options?.envPath || join(process.cwd(), 'src/server/env-server.ts')
13
+ )
14
+
15
+ // validate
16
+ for (const key in Environment) {
17
+ if (options?.optional?.includes(key)) {
18
+ continue
19
+ }
20
+ if (!Environment[key as keyof typeof Environment]) {
21
+ console.warn(`Missing key: ${key}`)
22
+ }
23
+ }
24
+
25
+ return Environment
26
+ }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { execSync } from 'node:child_process'
4
+ import { platform } from 'node:os'
5
+
6
+ /**
7
+ * Gets the appropriate host address for Docker containers to connect back to the host machine
8
+ * - On macOS/Windows: host.docker.internal works out of the box
9
+ * - On Linux: We need to get the actual host IP from the docker bridge network
10
+ */
11
+ export function getDockerHost(): string {
12
+ // In CI or on Linux, we need the actual host IP
13
+ if (platform() === 'linux') {
14
+ try {
15
+ // Get the host IP from docker network
16
+ const result = execSync(
17
+ "docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'",
18
+ { encoding: 'utf-8' }
19
+ ).trim()
20
+
21
+ if (result) {
22
+ console.info(`Using Docker bridge gateway IP: ${result}`)
23
+ return result
24
+ }
25
+ } catch (error) {
26
+ console.warn('Failed to get Docker bridge IP, falling back to host.docker.internal')
27
+ }
28
+ }
29
+
30
+ // Default for macOS/Windows or fallback
31
+ return 'host.docker.internal'
32
+ }
33
+
34
+ // If run directly, output the host
35
+ if (import.meta.main) {
36
+ console.info(getDockerHost())
37
+ }
@@ -0,0 +1,25 @@
1
+ import { join } from 'node:path'
2
+ import { loadEnv } from './env-load'
3
+ import { getDockerHost } from './get-docker-host'
4
+ import { getZeroVersion } from './zero-get-version'
5
+
6
+ export async function getTestEnv() {
7
+ const zeroVersion = getZeroVersion()
8
+ const devEnv = await loadEnv('development')
9
+ const dockerHost = getDockerHost()
10
+ const serverEnvFallback = await import(join(process.cwd(), 'src/server/env-server'))
11
+
12
+ return {
13
+ ...serverEnvFallback,
14
+ ...devEnv,
15
+ CI: 'true', // Ensure CI flag is set for test environment
16
+ ...(!process.env.DEBUG_BACKEND && {
17
+ ZERO_LOG_LEVEL: 'warn',
18
+ }),
19
+ DO_NOT_TRACK: '1',
20
+ ZERO_VERSION: zeroVersion,
21
+ ZERO_AUTH_JWKS_URL: `http://${dockerHost}:8081/api/auth/jwks`,
22
+ ZERO_PUSH_URL: `http://${dockerHost}:8081/api/zero/push`,
23
+ ZERO_GET_QUERIES_URL: `http://${dockerHost}:8081/api/zero/pull`,
24
+ }
25
+ }