@mono-labs/cli 0.0.5

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,12 @@
1
+ import { spawn } from 'child_process'
2
+
3
+ import { program } from '../../app.js'
4
+ import { pruneRepo } from './prune.js'
5
+
6
+ program
7
+ .command('prune')
8
+ .description('Prune local branches that are not on origin')
9
+
10
+ .action(() => {
11
+ pruneRepo()
12
+ })
@@ -0,0 +1,48 @@
1
+ import { execSync } from 'child_process'
2
+
3
+ const log = (...args) => console.log(...args)
4
+ const err = (...args) => console.error(...args)
5
+ export function pruneRepo() {
6
+ try {
7
+ // Fetch and prune remote branches
8
+ log('Fetching latest branch data from origin...')
9
+ execSync('git fetch --prune', { stdio: 'inherit' })
10
+
11
+ // Get local branches (trim whitespace)
12
+ const localBranches = execSync("git branch --format '%(refname:short)'")
13
+ .toString()
14
+ .trim()
15
+ .split('\n')
16
+ .map((branch) => branch.trim().replaceAll("'", ''))
17
+
18
+ // Get remote branches (remove "origin/" prefix)
19
+ const remoteBranches = execSync('git branch -r')
20
+ .toString()
21
+ .trim()
22
+ .split('\n')
23
+ .map((branch) => branch.replace(/^\s*origin\//, '').trim())
24
+
25
+ // Find local branches that are NOT in remote branches
26
+ const branchesToDelete = localBranches.filter((branch) => !remoteBranches.includes(branch))
27
+ if (branchesToDelete.length === 0) {
28
+ log('No local branches to delete.')
29
+ process.exit(0)
30
+ }
31
+
32
+ // Delete untracked local branches
33
+ log('Deleting local branches that are not on origin...')
34
+ branchesToDelete.forEach((branch) => {
35
+ log(`Attempting to delete: ${branch}`)
36
+ try {
37
+ execSync(`git branch -D ${branch}`, { stdio: 'inherit' })
38
+ log(`Deleted: ${branch}`)
39
+ } catch (error) {
40
+ error(`Failed to delete branch ${branch}:`, error.message)
41
+ }
42
+ })
43
+
44
+ log('Cleanup complete!')
45
+ } catch (error) {
46
+ err('An error occurred:', error.message)
47
+ }
48
+ }
@@ -0,0 +1,31 @@
1
+ import { spawn } from 'child_process'
2
+
3
+ import { program } from '../app.js'
4
+
5
+ function createChild(command) {
6
+ const child = spawn(command, {
7
+ stdio: ['inherit', 'pipe', 'pipe'], // Read from terminal, but capture output
8
+ shell: true,
9
+ env: {
10
+ ...process.env,
11
+ },
12
+ })
13
+
14
+ child.stdout.on('data', (data) => {
15
+ process.stdout.write(data) // pipe to main stdout
16
+ })
17
+
18
+ child.stderr.on('data', (data) => {
19
+ process.stderr.write(data) // pipe errors
20
+ })
21
+ child.on('message', (data) => {
22
+ console.log(`Message from child process: ${data}`)
23
+ })
24
+ }
25
+ program
26
+ .command('reset')
27
+ .description('Execute eas build command')
28
+ .option('-s, --soft', 'Pull from live')
29
+ .action(async (options) => {
30
+ createChild(`git reset ${options.soft ? '--soft' : '--mixed '} HEAD~1`)
31
+ })
@@ -0,0 +1,31 @@
1
+ import { execSync } from 'child_process'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export function importAllDynamoBatches(folderPath, useRemote = false) {
6
+ const files = fs
7
+ .readdirSync(folderPath)
8
+ .filter((file) => file.startsWith('dynamodb-seed-') && file.endsWith('.json'))
9
+
10
+ files.sort() // Optional: ensures files run in order
11
+
12
+ for (const file of files) {
13
+ const fullPath = path.resolve(path.join(folderPath, file))
14
+ console.log(`📝 Importing: ${fullPath}`)
15
+ console.log('Using remote DynamoDB:', useRemote)
16
+ const baseCommand = useRemote
17
+ ? `aws dynamodb batch-write-item --request-items file://${fullPath}`
18
+ : `aws dynamodb batch-write-item --endpoint-url http://localhost:8000 --request-items file://${fullPath}`
19
+ try {
20
+ console.log('baseCommand:', baseCommand)
21
+ console.log(`Executing command: ${baseCommand}`)
22
+ execSync(baseCommand, {
23
+ stdio: 'inherit',
24
+ })
25
+ console.log(`✅ Successfully imported ${file}\n`)
26
+ } catch (err) {
27
+ console.error(`❌ Error with ${file}:`, err.message)
28
+ break // or continue if you want to skip failed files
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,12 @@
1
+ import { spawn } from 'child_process'
2
+
3
+ import { program } from '../../app.js'
4
+ import { importAllDynamoBatches } from './import.js'
5
+
6
+ program
7
+ .command('seed')
8
+ .description('Execute eas build command')
9
+ .option('-p, --live', 'Pull from live')
10
+ .action(async (options) => {
11
+ importAllDynamoBatches('./docker/seed', options.live)
12
+ })
@@ -0,0 +1,8 @@
1
+ import { program } from '../../app.js'
2
+ import { squash } from './squash.js'
3
+
4
+ program
5
+ .command('squash')
6
+ .description('Squash x count of commits into one commit')
7
+
8
+ .action(() => squash())
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ import { execSync, spawnSync } from 'child_process'
3
+ import fs from 'fs'
4
+ import inquirer from 'inquirer'
5
+ import os from 'os'
6
+ import path from 'path'
7
+
8
+ async function maybeEditFile() {
9
+ const { shouldEdit } = await inquirer.prompt([
10
+ {
11
+ type: 'confirm',
12
+ name: 'shouldEdit',
13
+ message: 'Do you want to edit the commit message in your Git editor?',
14
+ default: true,
15
+ },
16
+ ])
17
+
18
+ if (shouldEdit) {
19
+ execSync('git commit --amend')
20
+ }
21
+ }
22
+
23
+ async function confirmDangerousSquash() {
24
+ const { shouldEdit } = await inquirer.prompt([
25
+ {
26
+ type: 'confirm',
27
+ name: 'shouldEdit',
28
+ message:
29
+ "You're about to squash on a previously squashed commit. You will need to manually modify the commit message. Do you want to proceed?",
30
+ default: true,
31
+ },
32
+ ])
33
+
34
+ return shouldEdit
35
+ }
36
+
37
+ export async function squash() {
38
+ const status = execSync('git status --porcelain').toString()
39
+ if (status.trim() !== '') {
40
+ console.error('You have unstaged changes. Please commit or stash them before proceeding.')
41
+ process.exit(1)
42
+ }
43
+ const log = execSync(`git log --reverse --pretty=format:"%h %s" -n ${20}`).toString()
44
+ console.log('Log:\n', log)
45
+
46
+ const commits = log
47
+ .split('\n')
48
+ .reverse()
49
+ .map((line, idx) => {
50
+ console.log('message', line.slice(8).split('- '))
51
+ return {
52
+ name: `${idx} [${line.slice(0, 7)}] : ${line.slice(8)}`,
53
+ value: idx, // Just the commit message
54
+ message: line.slice(8).split('- ').join('\n- '),
55
+ }
56
+ })
57
+ console.log(commits)
58
+
59
+ const { count } = await inquirer.prompt([
60
+ {
61
+ type: 'list',
62
+ name: 'count',
63
+ message: 'Select commit to squash down into:',
64
+ choices: commits,
65
+ },
66
+ ])
67
+
68
+ if (count === 0) {
69
+ console.log('You may not squash only one commit.')
70
+ return
71
+ }
72
+ let totalMerged = 0
73
+ const commitMessageGen = commits
74
+ .slice(0, count + 1)
75
+ .map((msg) => {
76
+ return msg.message
77
+ })
78
+ .map((msg) => `${msg}`)
79
+ .join('\n')
80
+
81
+ let shouldContinue = true
82
+ const match = commitMessageGen.match(/Merging (\d+)\s+into singular commit/)
83
+ if (match) {
84
+ shouldContinue = await confirmDangerousSquash()
85
+ console.log('Unable to merge with merged commits')
86
+ if (!shouldContinue) return
87
+ }
88
+
89
+ console.log('Total merged:', totalMerged)
90
+ console.log(count + 1 + totalMerged)
91
+ const commitMessage = `Merging ${count + 1 + totalMerged} into singular commit\n${commitMessageGen}`
92
+
93
+ console.log(`Commit message:\n${commitMessage}`)
94
+
95
+ // Write message to temp file
96
+ const tempMessagePath = path.join(os.tmpdir(), 'git_squash_message.txt')
97
+ fs.writeFileSync(tempMessagePath, commitMessage)
98
+
99
+ if (shouldContinue) {
100
+ const { confirmSquash } = await inquirer.prompt([
101
+ {
102
+ type: 'confirm',
103
+ name: 'confirmSquash',
104
+ message: 'Do you want to squash these commits?',
105
+ default: true, // or false if you want "No" as the default
106
+ },
107
+ ])
108
+
109
+ shouldContinue = confirmSquash
110
+ }
111
+
112
+ if (!shouldContinue) return
113
+ // proceed with
114
+
115
+ // GIT_EDITOR override for rebase message
116
+ const isWin = process.platform === 'win32'
117
+ const editorScript = path.join(os.tmpdir(), isWin ? 'git-editor.bat' : 'git-editor.sh')
118
+ const messagePath = path.join(os.tmpdir(), 'commit-message.txt')
119
+
120
+ fs.writeFileSync(messagePath, commitMessage)
121
+
122
+ if (isWin) {
123
+ fs.writeFileSync(editorScript, `@echo off\r\ncopy /Y "${messagePath}" %1 > nul\r\n`)
124
+ } else {
125
+ fs.writeFileSync(editorScript, `#!/bin/sh\ncat "${messagePath}" > "$1"\n`)
126
+ fs.chmodSync(editorScript, '755')
127
+ }
128
+
129
+ console.log('Starting interactive rebase...')
130
+
131
+ spawnSync('git', ['reset', '--soft', `HEAD~${count + 1}`], { stdio: 'inherit' })
132
+
133
+ spawnSync('git', ['commit', '-m', commitMessage], { stdio: 'inherit' })
134
+
135
+ const rebaseResult = spawnSync('git', ['rebase', `HEAD~${count}`], {
136
+ stdio: 'inherit',
137
+ env: { ...process.env, GIT_EDITOR: editorScript },
138
+ })
139
+
140
+ console.log(`Rebase result: ${rebaseResult.status}`)
141
+
142
+ if (rebaseResult.status !== 0) {
143
+ console.error('Rebase failed. Resolve conflicts and try again.')
144
+ process.exit(rebaseResult.status)
145
+ }
146
+
147
+ await maybeEditFile()
148
+
149
+ console.log('✅ Rebase complete.')
150
+ }
@@ -0,0 +1,38 @@
1
+ import { spawn } from 'child_process'
2
+
3
+ import { program } from '../../app.js'
4
+ import { generateEnvValues } from '../../app.js'
5
+ import { STAGING_URL } from '../../config.js'
6
+
7
+ program
8
+ .command('submit')
9
+ .description('Execute eas build command')
10
+ .option('--android', 'Build to target preview profile')
11
+ .option('--ios', 'Build to target production profile')
12
+ .action((str, options) => {
13
+ console.log('its me')
14
+ console.log('test')
15
+ //console.log(options);
16
+ console.log(str)
17
+
18
+ let envObj = generateEnvValues(true, '', false)
19
+
20
+ envObj.EXPO_PUBLIC_API_URL = `${STAGING_URL}`
21
+ envObj.EXPO_FORCE_PROD = 'true'
22
+ envObj.EAS_BUILD_PROFILE = 'production'
23
+
24
+ const command = `workspace app eas submit ${str.android ? `--platform android` : `--platform ios`}`
25
+ console.log('Running command:', command)
26
+ const child = spawn('yarn', [command], {
27
+ stdio: 'inherit',
28
+ shell: true, // required if using shell-style commands or cross-platform support
29
+ env: {
30
+ ...envObj,
31
+ },
32
+ })
33
+
34
+ child.on('exit', (code) => {
35
+ console.log(`Process exited with code ${code}`)
36
+ process.exit(code ?? 0)
37
+ })
38
+ })
@@ -0,0 +1,251 @@
1
+ import { spawn } from 'child_process'
2
+ import fs from 'fs'
3
+ import { program } from '../../app.js'
4
+ import { generateEnvValues } from '../../app.js'
5
+ import { STAGING_URL } from '../../config.js'
6
+ import {
7
+ getHasteConfig,
8
+ getHasteFiles,
9
+ getRootDirectory,
10
+ getRootHasteJson,
11
+ getRootJson,
12
+ } from '../loadFromRoot.js'
13
+
14
+ console.log('getRootDirectory', getRootDirectory())
15
+ console.log('rootJson', getRootJson())
16
+ const objHaste = getRootHasteJson()
17
+ const files = getHasteConfig()
18
+
19
+ console.log('Haste files', files)
20
+
21
+ const fileKeys = Object.keys(files)
22
+
23
+ fileKeys.forEach((key) => {
24
+ const commandName = key
25
+
26
+ console.log('Haste file key', commandName, files[key])
27
+ program
28
+ .command(commandName)
29
+ .description('Execute eas build command')
30
+ .option('--android', 'Build to target preview profile')
31
+ .option('--ios', 'Build to target production profile')
32
+ .option('--stage', 'Set environment to staging')
33
+ .action(async (options) => {
34
+ const configObject = files[commandName] || {}
35
+ await runHasteCommand(configObject, options)
36
+
37
+ // const devConfig = configObject.environments ? configObject.environments.dev : {}
38
+ // const productionConfig = configObject.environments ? configObject.environments.production : {}
39
+ // let envObj = {}
40
+ // if (options.stage) {
41
+ // envObj = { ...devConfig }
42
+ // } else {
43
+ // envObj = { ...productionConfig }
44
+ // }
45
+
46
+ // // You can decide what happens when each flag is passed.
47
+ // // Example: run `eas build` with different profiles.
48
+ // if (options.android) {
49
+ // console.log('→ Building Android (preview profile)…')
50
+ // //run('eas', ['build', '--platform', 'android', '--profile', 'preview'])
51
+ // } else if (options.ios) {
52
+ // console.log('→ Building iOS (production profile)…')
53
+ // //run('eas', ['build', '--platform', 'ios', '--profile', 'production'])
54
+ // } else {
55
+ // // console.log('No target specified. Use --android or --ios.')
56
+ // // program.help()
57
+ // console.log('objHaste.actions', configObject)
58
+ // const preactions = configObject['preactions'] || []
59
+ // const actions = configObject.actions || []
60
+ // console.log('preactions', preactions)
61
+ // for (const item of preactions) {
62
+ // console.log(`→ Running pre-action: ${item}`)
63
+ // await run(item, [], envObj)
64
+ // }
65
+ // for (const item of actions) {
66
+ // console.log(`→ Running action: ${item}`)
67
+ // run(item, [], envObj)
68
+ // }
69
+ // }
70
+ // })
71
+ })
72
+ })
73
+
74
+ // const commandName = 'test'
75
+ // program
76
+ // .command(commandName)
77
+ // .description('Execute eas build command')
78
+ // .option('--android', 'Build to target preview profile')
79
+ // .option('--ios', 'Build to target production profile')
80
+ // .option('--stage', 'Set environment to staging')
81
+ // .action((options) => {
82
+ // const configObject = objHaste[commandName] || {}
83
+
84
+ // const devConfig = configObject.environments ? configObject.environments.dev : {}
85
+ // const productionConfig = configObject.environments ? configObject.environments.production : {}
86
+ // let envObj = {}
87
+ // if (options.stage) {
88
+ // envObj = { ...devConfig }
89
+ // } else {
90
+ // envObj = { ...productionConfig }
91
+ // }
92
+
93
+ // // You can decide what happens when each flag is passed.
94
+ // // Example: run `eas build` with different profiles.
95
+ // if (options.android) {
96
+ // console.log('→ Building Android (preview profile)…')
97
+ // //run('eas', ['build', '--platform', 'android', '--profile', 'preview'])
98
+ // } else if (options.ios) {
99
+ // console.log('→ Building iOS (production profile)…')
100
+ // //run('eas', ['build', '--platform', 'ios', '--profile', 'production'])
101
+ // } else {
102
+ // // console.log('No target specified. Use --android or --ios.')
103
+ // // program.help()
104
+ // console.log('objHaste.actions', objHaste)
105
+ // for (const item of configObject.actions) {
106
+ // console.log(`→ Running action: ${item}`)
107
+ // run(item, [], envObj)
108
+ // }
109
+ // }
110
+ // })
111
+
112
+ const totalClosedActions = 0
113
+ // Utility to spawn and pipe child output
114
+ async function run(cmd, args, envObj = {}, count = 1) {
115
+ const child = spawn(cmd, args, {
116
+ stdio: 'inherit',
117
+ shell: true,
118
+ env: {
119
+ ...process.env,
120
+ ...envObj,
121
+ },
122
+ })
123
+ const isLast = count === totalClosedActions + 1
124
+
125
+ const exitAction = () => {
126
+ totalClosedActions++
127
+ process.exit(code)
128
+ }
129
+ if (!isLast) {
130
+ child.on('exit', (code) => {
131
+ exitAction()
132
+ })
133
+ child.on('sigint', () => {
134
+ exitAction()
135
+ })
136
+ }
137
+ if (isLast) {
138
+ child.on('exit', (code) => {
139
+ if (count < totalClosedActions) exitAction()
140
+ })
141
+ child.on('sigint', () => {
142
+ if (count < totalClosedActions) {
143
+ exitAction()
144
+ process.exit(code)
145
+ }
146
+ })
147
+ }
148
+ }
149
+
150
+ // Track background processes so we can kill them on exit
151
+ const bgChildren = new Set()
152
+
153
+ /** Run a command and resolve when it exits (attach stdio so you can see output). */
154
+ function runForeground(cmd, envObj = {}) {
155
+ return new Promise((resolve, reject) => {
156
+ // run the whole string via the shell so quotes/pipes work
157
+ const child = spawn(cmd, {
158
+ shell: true,
159
+ stdio: 'inherit',
160
+ env: { ...process.env, ...envObj },
161
+ })
162
+ child.on('error', reject)
163
+ child.on('exit', (code, signal) => {
164
+ if (signal) return reject(new Error(`${cmd} exited via signal ${signal}`))
165
+ if (code === 0) return resolve()
166
+ reject(new Error(`${cmd} exited with code ${code}`))
167
+ })
168
+ })
169
+ }
170
+
171
+ /** Start a command fully detached (no stdio). */
172
+ function runBackground(cmd, envObj = {}) {
173
+ const isWin = process.platform === 'win32'
174
+
175
+ const child = spawn(cmd, {
176
+ shell: true,
177
+ stdio: 'ignore', // no output in this terminal
178
+ env: { ...process.env, ...envObj },
179
+ // On POSIX, detach so we can signal the whole group; on Windows, DON'T.
180
+ detached: !isWin,
181
+ windowsHide: isWin, // prevent new console window on Windows
182
+ })
183
+
184
+ // Only unref on POSIX when detached; on Windows keep it referenced.
185
+ if (!isWin) child.unref()
186
+
187
+ bgChildren.add(child)
188
+ child.on('exit', () => bgChildren.delete(child))
189
+ child.on('error', () => bgChildren.delete(child))
190
+ }
191
+
192
+ /** Kill all background children (called on SIGINT/SIGTERM or when foreground ends). */
193
+ function killAllBackground() {
194
+ for (const child of Array.from(bgChildren)) {
195
+ try {
196
+ if (process.platform === 'win32') {
197
+ spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { shell: true, stdio: 'ignore' })
198
+ } else {
199
+ process.kill(-child.pid, 'SIGTERM') // whole group
200
+ }
201
+ } catch {}
202
+ }
203
+ bgChildren.clear()
204
+ }
205
+
206
+ process.on('SIGINT', () => {
207
+ console.log('\nSIGINT')
208
+ killAllBackground()
209
+ process.exit(130)
210
+ })
211
+ process.on('SIGTERM', () => {
212
+ killAllBackground()
213
+ process.exit(143)
214
+ })
215
+
216
+ /** In your commander .action handler */
217
+ async function runHasteCommand(configObject, options) {
218
+ const devConfig = configObject.environments?.dev ?? {}
219
+ const productionConfig = configObject.environments?.production ?? {}
220
+ const envObj = options.stage ? devConfig : productionConfig
221
+
222
+ const preactions = configObject.preactions ?? []
223
+ const actions = configObject.actions ?? []
224
+
225
+ // 1) Run preactions SEQUENTIALLY (each waits for previous to finish)
226
+ for (const cmd of preactions) {
227
+ console.log(`→ preaction: ${cmd}`)
228
+ await runForeground(cmd, envObj)
229
+ }
230
+
231
+ // 2) Run actions: background all but the last; attach to the last
232
+ if (actions.length === 0) return
233
+
234
+ const bg = actions.slice(0, -1)
235
+ const fg = actions[actions.length - 1]
236
+
237
+ for (const cmd of bg) {
238
+ console.log(`→ background action: ${cmd}`)
239
+ runBackground(cmd, envObj)
240
+ }
241
+
242
+ console.log(`→ foreground action (attached): ${fg}`)
243
+ console.log('envObj', envObj)
244
+ try {
245
+ console.log('envObj', envObj)
246
+ await runForeground(fg, envObj)
247
+ } finally {
248
+ // When the foreground ends, clean up background processes too (optional)
249
+ killAllBackground()
250
+ }
251
+ }
@@ -0,0 +1,39 @@
1
+ import { execSync } from 'child_process'
2
+
3
+ export function getEASChannels() {
4
+ const channelsData = execSync(
5
+ 'yarn eas channel:list --non-interactive --json',
6
+ { stdio: ['pipe', 'pipe', 'ignore'] }, // Ignore stderr
7
+ ).toString()
8
+
9
+ // Extract valid JSON from any extra noise
10
+ const jsonStart = channelsData.indexOf('[')
11
+ const jsonEnd = channelsData.lastIndexOf(']') + 1
12
+
13
+ if (jsonStart === -1 || jsonEnd === -1) {
14
+ throw new Error('JSON output not found in command output')
15
+ }
16
+
17
+ const jsonSlice = channelsData.slice(jsonStart, jsonEnd)
18
+ const channels = JSON.parse(jsonSlice)
19
+ return channels
20
+ }
21
+
22
+ export function getEASBranches() {
23
+ const channelsData = execSync(
24
+ 'yarn eas branch:list --non-interactive --json',
25
+ { stdio: ['pipe', 'pipe', 'ignore'] }, // Ignore stderr
26
+ ).toString()
27
+
28
+ // Extract valid JSON from any extra noise
29
+ const jsonStart = channelsData.indexOf('[')
30
+ const jsonEnd = channelsData.lastIndexOf(']') + 1
31
+
32
+ if (jsonStart === -1 || jsonEnd === -1) {
33
+ throw new Error('JSON output not found in command output')
34
+ }
35
+
36
+ const jsonSlice = channelsData.slice(jsonStart, jsonEnd)
37
+ const channels = JSON.parse(jsonSlice)
38
+ return channels
39
+ }