@mono-labs/cli 0.0.38 → 0.0.40
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/lib/commands/build-process/boot.js +15 -0
- package/lib/commands/build-process/cliFactory.js +58 -0
- package/lib/commands/build-process/dataLayer.js +30 -0
- package/lib/commands/build-process/index.js +10 -408
- package/lib/commands/build-process/runHasteCommand.js +46 -0
- package/lib/commands/build-process/runners/processManager.js +39 -0
- package/lib/commands/build-process/runners/runBackground.js +49 -0
- package/lib/commands/build-process/runners/runForeground.js +50 -0
- package/lib/commands/build-process/test.js +12 -0
- package/lib/commands/build-process/validators.js +12 -0
- package/lib/commands/loadFromRoot.js +0 -6
- package/lib/index.js +49 -48
- package/package.json +1 -1
- package/lib/commands/reset.js +0 -31
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Boot logic: load root + haste configuration
|
|
2
|
+
import {
|
|
3
|
+
getHasteConfig,
|
|
4
|
+
getRootDirectory,
|
|
5
|
+
getRootJson,
|
|
6
|
+
} from '../loadFromRoot.js';
|
|
7
|
+
|
|
8
|
+
export function boot() {
|
|
9
|
+
const rootDir = getRootDirectory();
|
|
10
|
+
const rootJson = getRootJson();
|
|
11
|
+
const { files, config } = getHasteConfig();
|
|
12
|
+
return { rootDir, rootJson, files, config };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default boot;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { program } from '../../app.js';
|
|
2
|
+
import runHasteCommand from './runHasteCommand.js';
|
|
3
|
+
import { verifyOptionValue } from './validators.js';
|
|
4
|
+
import { mergeData, setData } from './dataLayer.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register commander commands for each haste file definition.
|
|
8
|
+
* Handles argument, options, validation, and action wiring.
|
|
9
|
+
*/
|
|
10
|
+
export function buildCommands(files) {
|
|
11
|
+
Object.entries(files).forEach(([commandName, configObject]) => {
|
|
12
|
+
const optionsData = configObject.options || {};
|
|
13
|
+
|
|
14
|
+
let current = program
|
|
15
|
+
.command(commandName)
|
|
16
|
+
.description(configObject.description || 'Haste command');
|
|
17
|
+
|
|
18
|
+
// Argument
|
|
19
|
+
if (configObject.argument) {
|
|
20
|
+
const argInfo = configObject.argument;
|
|
21
|
+
const required = !!argInfo.required;
|
|
22
|
+
const type = argInfo.type || 'string';
|
|
23
|
+
const argSpec = required ? `<${type}>` : `[${type}]`;
|
|
24
|
+
current = current.argument(argSpec, argInfo.description || '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Options
|
|
28
|
+
Object.entries(optionsData).forEach(([optionKey, meta]) => {
|
|
29
|
+
const type = meta.type || 'boolean';
|
|
30
|
+
const shortcut = meta.shortcut ? `-${meta.shortcut}, ` : '';
|
|
31
|
+
if (type === 'string') {
|
|
32
|
+
current = current.option(
|
|
33
|
+
`${shortcut}--${optionKey} <${optionKey}>`,
|
|
34
|
+
meta.description || ''
|
|
35
|
+
);
|
|
36
|
+
if (meta.default !== undefined) setData(optionKey, meta.default);
|
|
37
|
+
} else {
|
|
38
|
+
current = current.option(
|
|
39
|
+
`${shortcut}--${optionKey}`,
|
|
40
|
+
meta.description || ''
|
|
41
|
+
);
|
|
42
|
+
if (meta.default !== undefined) setData(optionKey, meta.default);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
current.action(async (arg, cmd) => {
|
|
47
|
+
const optionVals = cmd.opts ? cmd.opts() : cmd;
|
|
48
|
+
Object.keys(optionVals).forEach((k) => {
|
|
49
|
+
optionVals[k] = verifyOptionValue(k, optionVals[k], optionsData);
|
|
50
|
+
});
|
|
51
|
+
const argVal = arg || configObject.argument?.default;
|
|
52
|
+
mergeData({ ...optionVals, arg: argVal });
|
|
53
|
+
await runHasteCommand(configObject, optionVals);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default buildCommands;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Shared mutable key/value store for build-process commands.
|
|
2
|
+
// Extracts dynamic values from preactions and holds defaults from options.
|
|
3
|
+
|
|
4
|
+
const dataLayer = {};
|
|
5
|
+
|
|
6
|
+
export function setData(key, value) {
|
|
7
|
+
if (value !== undefined) dataLayer[key] = value;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function mergeData(obj = {}) {
|
|
11
|
+
Object.entries(obj).forEach(([k, v]) => setData(k, v));
|
|
12
|
+
return dataLayer;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getData(key) {
|
|
16
|
+
return key ? dataLayer[key] : dataLayer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function hasData(key) {
|
|
20
|
+
return Object.prototype.hasOwnProperty.call(dataLayer, key);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function replaceTokens(str) {
|
|
24
|
+
if (typeof str !== 'string') return str;
|
|
25
|
+
return str.replace(/\$\{([^}]+)\}/g, (m, k) =>
|
|
26
|
+
hasData(k) ? String(getData(k)) : m
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default dataLayer;
|
|
@@ -1,411 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { STAGING_URL } from '../../config.js'
|
|
6
|
-
import { getHasteConfig, getHasteFiles, getRootDirectory, getRootJson } from '../loadFromRoot.js'
|
|
1
|
+
// Orchestrator for modular build-process command system.
|
|
2
|
+
import { boot } from './boot.js';
|
|
3
|
+
import { buildCommands } from './cliFactory.js';
|
|
4
|
+
import { ensureSignalHandlers } from './runners/processManager.js';
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
console.log('
|
|
10
|
-
|
|
6
|
+
const { files, config, rootDir } = boot();
|
|
7
|
+
console.log('[build-process] root:', rootDir);
|
|
8
|
+
console.log('[build-process] commands discovered:', Object.keys(files));
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
ensureSignalHandlers();
|
|
11
|
+
buildCommands(files);
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
let dataLayer = {}
|
|
16
|
-
|
|
17
|
-
fileKeys.forEach((key) => {
|
|
18
|
-
const commandName = key
|
|
19
|
-
|
|
20
|
-
console.log('Haste file key', commandName, files[key])
|
|
21
|
-
const configObject = files[commandName] || {}
|
|
22
|
-
const optionsData = configObject.options || {}
|
|
23
|
-
const optionsList = Object.keys(optionsData)
|
|
24
|
-
|
|
25
|
-
console.log('optionsList', optionsList, optionsData)
|
|
26
|
-
|
|
27
|
-
let currentCommand = program.command(commandName).description('Execute eas build command')
|
|
28
|
-
|
|
29
|
-
if (configObject.argument) {
|
|
30
|
-
console.log('has argument', configObject.argument)
|
|
31
|
-
// .argument('<input>', 'path to input file') // required
|
|
32
|
-
// .argument('[output]', 'optional output file') // optional
|
|
33
|
-
const argumentRequired = configObject.argument.required || false
|
|
34
|
-
const argCommand = argumentRequired
|
|
35
|
-
? `<${configObject.argument.type || 'string'}>`
|
|
36
|
-
: `[${configObject.argument.type || 'string'}]`
|
|
37
|
-
currentCommand = currentCommand.argument(argCommand, configObject.argument.description || '')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
optionsList.forEach((optionKey) => {
|
|
41
|
-
const type = optionsData[optionKey].type || 'boolean'
|
|
42
|
-
|
|
43
|
-
if (type === 'string') {
|
|
44
|
-
if (optionsData[optionKey].shortcut) {
|
|
45
|
-
currentCommand = currentCommand.option(
|
|
46
|
-
`-${optionsData[optionKey].shortcut}, --${optionKey} <${optionKey}>`,
|
|
47
|
-
optionsData[optionKey].description || ''
|
|
48
|
-
)
|
|
49
|
-
console.log('optionKey', optionKey, optionsData[optionKey])
|
|
50
|
-
dataLayer[optionKey] = optionsData[optionKey].default || ''
|
|
51
|
-
} else {
|
|
52
|
-
currentCommand = currentCommand.option(
|
|
53
|
-
`--${optionKey} <${optionKey}>`,
|
|
54
|
-
optionsData[optionKey].description || ''
|
|
55
|
-
)
|
|
56
|
-
dataLayer[optionKey] = optionsData[optionKey].default || ''
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
if (optionsData[optionKey].shortcut) {
|
|
60
|
-
currentCommand = currentCommand.option(
|
|
61
|
-
`-${optionsData[optionKey].shortcut}, --${optionKey}`,
|
|
62
|
-
optionsData[optionKey].description || ''
|
|
63
|
-
)
|
|
64
|
-
console.log('optionKey', optionKey, optionsData[optionKey])
|
|
65
|
-
dataLayer[optionKey] = optionsData[optionKey].default || false
|
|
66
|
-
} else {
|
|
67
|
-
currentCommand = currentCommand.option(
|
|
68
|
-
`--${optionKey}`,
|
|
69
|
-
optionsData[optionKey].description || ''
|
|
70
|
-
)
|
|
71
|
-
dataLayer[optionKey] = optionsData[optionKey].default || false
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
function verifyOptionValues(optionKey, value) {
|
|
77
|
-
const optionInfo = optionsData[optionKey]
|
|
78
|
-
if (optionInfo && optionInfo.options) {
|
|
79
|
-
if (!optionInfo.options.includes(value)) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`Invalid value for --${optionKey}: ${value}. Valid options are: ${optionInfo.options.join(
|
|
82
|
-
', '
|
|
83
|
-
)}`
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return value
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
currentCommand.action(async (arg, cmd) => {
|
|
91
|
-
console.log('test:')
|
|
92
|
-
console.log('cmd', cmd)
|
|
93
|
-
console.log('arg', arg)
|
|
94
|
-
const optionVals = cmd.opts ? cmd.opts() : cmd
|
|
95
|
-
console.log('arg', arg)
|
|
96
|
-
console.log('optionVals')
|
|
97
|
-
|
|
98
|
-
const opts = optionVals
|
|
99
|
-
Object.keys(opts).forEach((k) => {
|
|
100
|
-
opts[k] = verifyOptionValues(k, opts[k])
|
|
101
|
-
})
|
|
102
|
-
const argVal = arg ? arg : configObject.argument.default
|
|
103
|
-
console.log('argVal', argVal)
|
|
104
|
-
dataLayer = { ...dataLayer, ...opts, arg: argVal }
|
|
105
|
-
console.log('opts', opts)
|
|
106
|
-
console.log('firstDataLayer', dataLayer)
|
|
107
|
-
await runHasteCommand(configObject, opts)
|
|
108
|
-
|
|
109
|
-
// const devConfig = configObject.environments ? configObject.environments.dev : {}
|
|
110
|
-
// const productionConfig = configObject.environments ? configObject.environments.production : {}
|
|
111
|
-
// let envObj = {}
|
|
112
|
-
// if (options.stage) {
|
|
113
|
-
// envObj = { ...devConfig }
|
|
114
|
-
// } else {
|
|
115
|
-
// envObj = { ...productionConfig }
|
|
116
|
-
// }
|
|
117
|
-
|
|
118
|
-
// // You can decide what happens when each flag is passed.
|
|
119
|
-
// // Example: run `eas build` with different profiles.
|
|
120
|
-
// if (options.android) {
|
|
121
|
-
// console.log('→ Building Android (preview profile)…')
|
|
122
|
-
// //run('eas', ['build', '--platform', 'android', '--profile', 'preview'])
|
|
123
|
-
// } else if (options.ios) {
|
|
124
|
-
// console.log('→ Building iOS (production profile)…')
|
|
125
|
-
// //run('eas', ['build', '--platform', 'ios', '--profile', 'production'])
|
|
126
|
-
// } else {
|
|
127
|
-
// // console.log('No target specified. Use --android or --ios.')
|
|
128
|
-
// // program.help()
|
|
129
|
-
// console.log('objHaste.actions', configObject)
|
|
130
|
-
// const preactions = configObject['preactions'] || []
|
|
131
|
-
// const actions = configObject.actions || []
|
|
132
|
-
// console.log('preactions', preactions)
|
|
133
|
-
// for (const item of preactions) {
|
|
134
|
-
// console.log(`→ Running pre-action: ${item}`)
|
|
135
|
-
// await run(item, [], envObj)
|
|
136
|
-
// }
|
|
137
|
-
// for (const item of actions) {
|
|
138
|
-
// console.log(`→ Running action: ${item}`)
|
|
139
|
-
// run(item, [], envObj)
|
|
140
|
-
// }
|
|
141
|
-
// }
|
|
142
|
-
// })
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// const commandName = 'test'
|
|
147
|
-
// program
|
|
148
|
-
// .command(commandName)
|
|
149
|
-
// .description('Execute eas build command')
|
|
150
|
-
// .option('--android', 'Build to target preview profile')
|
|
151
|
-
// .option('--ios', 'Build to target production profile')
|
|
152
|
-
// .option('--stage', 'Set environment to staging')
|
|
153
|
-
// .action((options) => {
|
|
154
|
-
// const configObject = objHaste[commandName] || {}
|
|
155
|
-
|
|
156
|
-
// const devConfig = configObject.environments ? configObject.environments.dev : {}
|
|
157
|
-
// const productionConfig = configObject.environments ? configObject.environments.production : {}
|
|
158
|
-
// let envObj = {}
|
|
159
|
-
// if (options.stage) {
|
|
160
|
-
// envObj = { ...devConfig }
|
|
161
|
-
// } else {
|
|
162
|
-
// envObj = { ...productionConfig }
|
|
163
|
-
// }
|
|
164
|
-
|
|
165
|
-
// // You can decide what happens when each flag is passed.
|
|
166
|
-
// // Example: run `eas build` with different profiles.
|
|
167
|
-
// if (options.android) {
|
|
168
|
-
// console.log('→ Building Android (preview profile)…')
|
|
169
|
-
// //run('eas', ['build', '--platform', 'android', '--profile', 'preview'])
|
|
170
|
-
// } else if (options.ios) {
|
|
171
|
-
// console.log('→ Building iOS (production profile)…')
|
|
172
|
-
// //run('eas', ['build', '--platform', 'ios', '--profile', 'production'])
|
|
173
|
-
// } else {
|
|
174
|
-
// // console.log('No target specified. Use --android or --ios.')
|
|
175
|
-
// // program.help()
|
|
176
|
-
// console.log('objHaste.actions', objHaste)
|
|
177
|
-
// for (const item of configObject.actions) {
|
|
178
|
-
// console.log(`→ Running action: ${item}`)
|
|
179
|
-
// run(item, [], envObj)
|
|
180
|
-
// }
|
|
181
|
-
// }
|
|
182
|
-
// })
|
|
183
|
-
|
|
184
|
-
const totalClosedActions = 0
|
|
185
|
-
// Utility to spawn and pipe child output
|
|
186
|
-
async function run(cmd, args, envObj = {}, count = 1) {
|
|
187
|
-
const child = spawn(cmd, args, {
|
|
188
|
-
stdio: 'inherit',
|
|
189
|
-
shell: true,
|
|
190
|
-
env: {
|
|
191
|
-
...process.env,
|
|
192
|
-
...envObj,
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
const isLast = count === totalClosedActions + 1
|
|
196
|
-
|
|
197
|
-
const exitAction = () => {
|
|
198
|
-
totalClosedActions++
|
|
199
|
-
process.exit(code)
|
|
200
|
-
}
|
|
201
|
-
if (!isLast) {
|
|
202
|
-
child.on('exit', (code) => {
|
|
203
|
-
exitAction()
|
|
204
|
-
})
|
|
205
|
-
child.on('sigint', () => {
|
|
206
|
-
exitAction()
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
if (isLast) {
|
|
210
|
-
child.on('exit', (code) => {
|
|
211
|
-
if (count < totalClosedActions) exitAction()
|
|
212
|
-
})
|
|
213
|
-
child.on('sigint', () => {
|
|
214
|
-
if (count < totalClosedActions) {
|
|
215
|
-
exitAction()
|
|
216
|
-
process.exit(code)
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Track background processes so we can kill them on exit
|
|
223
|
-
const bgChildren = new Set()
|
|
224
|
-
|
|
225
|
-
/** Run a command and resolve when it exits (attach stdio so you can see output). */
|
|
226
|
-
function runForeground(cmd, envObj = {}, options = {}) {
|
|
227
|
-
return new Promise((resolve, reject) => {
|
|
228
|
-
let lastLine = ''
|
|
229
|
-
let myTextData = ''
|
|
230
|
-
const child = spawn(cmd, {
|
|
231
|
-
shell: true,
|
|
232
|
-
env: { ...process.env, ...envObj },
|
|
233
|
-
stdio: ['inherit', 'pipe', 'pipe'], // stdin pass-through, capture out/err
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
const handleData = (chunk, isErr = false) => {
|
|
237
|
-
const text = chunk.toString()
|
|
238
|
-
console.log(text.toString().trim())
|
|
239
|
-
console.log('text', text)
|
|
240
|
-
myTextData = myTextData.concat(text)
|
|
241
|
-
console.log('myTextData', myTextData)
|
|
242
|
-
|
|
243
|
-
// Track last line
|
|
244
|
-
const lines = text.trim().split(/\r?\n/)
|
|
245
|
-
if (lines.length) {
|
|
246
|
-
lastLine = lines[lines.length - 1]
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
child.stdout.on('data', (chunk) => handleData(chunk, false))
|
|
251
|
-
child.stderr.on('data', (chunk) => handleData(chunk, true))
|
|
252
|
-
|
|
253
|
-
child.on('error', reject)
|
|
254
|
-
child.on('exit', (code, signal) => {
|
|
255
|
-
myTextData.concat('\n<<< CHILD PROCESS EXITED >>>\n')
|
|
256
|
-
console.log('myTextData', myTextData)
|
|
257
|
-
//const rx = /\{out\s*:\s*(.+?)\}/gs
|
|
258
|
-
// ^\{out:field-name \$\{([^}]+)\}\}$
|
|
259
|
-
const rx = /\{out:([^\s]+) (.*)\}\n/g
|
|
260
|
-
//const rx = /Project ([^\r\n]*)/
|
|
261
|
-
console.log('myTextData', myTextData)
|
|
262
|
-
const match = rx.exec(myTextData)
|
|
263
|
-
|
|
264
|
-
const rx3 = /\{out:(?<field>[^\s}]+)\s+(?<value>[^\s}]+)\}/g
|
|
265
|
-
const results = [...myTextData.matchAll(rx3)].map((m) => {
|
|
266
|
-
const layerIndex = m.groups.field
|
|
267
|
-
const matchValue = m.groups.value
|
|
268
|
-
console.log('matchValue', matchValue)
|
|
269
|
-
dataLayer[layerIndex] = matchValue
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
field: m.groups.field,
|
|
273
|
-
value: m.groups.value,
|
|
274
|
-
}
|
|
275
|
-
})
|
|
276
|
-
console.log(results)
|
|
277
|
-
|
|
278
|
-
console.log('match', match)
|
|
279
|
-
|
|
280
|
-
if (signal) return reject(new Error(`${cmd} exited via signal ${signal}`))
|
|
281
|
-
if (code === 0) return resolve(lastLine) // resolve with last line
|
|
282
|
-
reject(new Error(`${cmd} exited with code ${code}. Last line: ${lastLine}`))
|
|
283
|
-
})
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function runBackground(cmd, envObj = {}, options = {}, attached = false) {
|
|
288
|
-
const isWin = process.platform === 'win32'
|
|
289
|
-
// Expand ${0}, ${1}, ... from dataLayer into env strings
|
|
290
|
-
const newEnv = {}
|
|
291
|
-
const commandClone = cmd
|
|
292
|
-
for (const key of Object.keys(envObj)) {
|
|
293
|
-
let value = envObj[key]
|
|
294
|
-
if (typeof value === 'string') {
|
|
295
|
-
console.log('dataLayer', dataLayer)
|
|
296
|
-
Object.keys(dataLayer).map((k) => {
|
|
297
|
-
value = value.replace(new RegExp(`\\$\\{${k}\\}`, 'g'), dataLayer[k])
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
newEnv[key] = value
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
console.log('newEnv', newEnv)
|
|
304
|
-
|
|
305
|
-
const out = commandClone.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
|
306
|
-
// keep 0/false, only treat null/undefined as missing
|
|
307
|
-
const v = dataLayer[key]
|
|
308
|
-
return v == null ? match : String(v)
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
return new Promise((resolve, reject) => {
|
|
312
|
-
const child = spawn(out, {
|
|
313
|
-
shell: true,
|
|
314
|
-
stdio: attached ? 'inherit' : 'ignore', // no output in this terminal
|
|
315
|
-
env: { ...process.env, ...newEnv },
|
|
316
|
-
// On POSIX, detach so we can signal the whole group; on Windows, DON'T.
|
|
317
|
-
detached: !attached && !isWin,
|
|
318
|
-
windowsHide: !attached && !isWin, // prevent new console window on Windows
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
// Only unref on POSIX when detached; on Windows keep it referenced.
|
|
322
|
-
if (!attached && !isWin) child.unref()
|
|
323
|
-
|
|
324
|
-
child.once('error', (err) => {
|
|
325
|
-
bgChildren.delete(child)
|
|
326
|
-
reject(err)
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
child.once('exit', (code, signal) => {
|
|
330
|
-
bgChildren.delete(child)
|
|
331
|
-
if (signal) return reject(new Error(`${cmd} exited via signal ${signal}`))
|
|
332
|
-
if (code === 0) return resolve()
|
|
333
|
-
reject(new Error(`${cmd} exited with code ${code}`))
|
|
334
|
-
})
|
|
335
|
-
})
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** Kill all background children (called on SIGINT/SIGTERM or when foreground ends). */
|
|
339
|
-
function killAllBackground() {
|
|
340
|
-
for (const child of Array.from(bgChildren)) {
|
|
341
|
-
try {
|
|
342
|
-
if (process.platform === 'win32') {
|
|
343
|
-
spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { shell: true, stdio: 'ignore' })
|
|
344
|
-
} else {
|
|
345
|
-
process.kill(-child.pid, 'SIGTERM') // whole group
|
|
346
|
-
}
|
|
347
|
-
} catch {}
|
|
348
|
-
}
|
|
349
|
-
bgChildren.clear()
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
process.on('SIGINT', () => {
|
|
353
|
-
console.log('\nSIGINT')
|
|
354
|
-
killAllBackground()
|
|
355
|
-
process.exit(130)
|
|
356
|
-
})
|
|
357
|
-
process.on('SIGTERM', () => {
|
|
358
|
-
killAllBackground()
|
|
359
|
-
process.exit(143)
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
/** In your commander .action handler */
|
|
363
|
-
async function runHasteCommand(configObject, options = {}) {
|
|
364
|
-
const environments = configObject.environments
|
|
365
|
-
const devConfig = configObject.environments?.dev ?? {}
|
|
366
|
-
const productionConfig = configObject.environments?.stage ?? {}
|
|
367
|
-
console.log('options', options)
|
|
368
|
-
const awsProfile = process.env.CDK_DEPLOY_PROFILE || 'default'
|
|
369
|
-
console.log('prof', awsProfile)
|
|
370
|
-
const envObj = options.stage ? productionConfig : devConfig
|
|
371
|
-
envObj['AWS_PROFILE'] = awsProfile
|
|
372
|
-
|
|
373
|
-
const preactions = configObject.preactions ?? []
|
|
374
|
-
const actions = configObject.actions ?? []
|
|
375
|
-
|
|
376
|
-
console.log('environments', environments)
|
|
377
|
-
|
|
378
|
-
console.log('preactions', preactions)
|
|
379
|
-
|
|
380
|
-
// 1) Run preactions SEQUENTIALLY (each waits for previous to finish)
|
|
381
|
-
let num = 0
|
|
382
|
-
console.log('envObj', envObj)
|
|
383
|
-
for (const cmd of preactions) {
|
|
384
|
-
console.log(`→ preaction: ${cmd}`)
|
|
385
|
-
await runForeground(cmd, envObj, options)
|
|
386
|
-
num++
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// 2) Run actions: background all but the last; attach to the last
|
|
390
|
-
if (actions.length === 0) return
|
|
391
|
-
|
|
392
|
-
const bg = actions.slice(0, -1)
|
|
393
|
-
const fg = actions[actions.length - 1]
|
|
394
|
-
|
|
395
|
-
for (const cmd of bg) {
|
|
396
|
-
console.log(`→ background action: ${cmd}`)
|
|
397
|
-
runBackground(cmd, envObj, options)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
console.log(`→ foreground action (attached): ${fg}`)
|
|
401
|
-
try {
|
|
402
|
-
console.log('envObj', envObj)
|
|
403
|
-
await runBackground(fg, envObj, options, true)
|
|
404
|
-
} finally {
|
|
405
|
-
// When the foreground ends, clean up background processes too (optional)
|
|
406
|
-
killAllBackground()
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
//ACTION TYPES: preaction, action
|
|
411
|
-
//COMMAND TYPES: sequence, parallel
|
|
13
|
+
// (No direct export; importing this file registers commands on the shared commander program.)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { runForeground } from './runners/runForeground.js';
|
|
2
|
+
import { runBackground } from './runners/runBackground.js';
|
|
3
|
+
import { killAllBackground } from './runners/processManager.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Orchestrate execution of a single haste command definition.
|
|
7
|
+
* Phases:
|
|
8
|
+
* 1. Preactions (sequential, blocking) via runForeground
|
|
9
|
+
* 2. Actions (background except last; last attached) via runBackground
|
|
10
|
+
* Environment selection based on --stage flag and injection of AWS_PROFILE.
|
|
11
|
+
*/
|
|
12
|
+
export async function runHasteCommand(configObject, options = {}) {
|
|
13
|
+
const devConfig = configObject.environments?.dev ?? {};
|
|
14
|
+
const stageConfig = configObject.environments?.stage ?? {};
|
|
15
|
+
const awsProfile = process.env.CDK_DEPLOY_PROFILE || 'default';
|
|
16
|
+
const envObj = options.stage ? { ...stageConfig } : { ...devConfig };
|
|
17
|
+
envObj.AWS_PROFILE = awsProfile;
|
|
18
|
+
|
|
19
|
+
const preactions = configObject.preactions ?? [];
|
|
20
|
+
const actions = configObject.actions ?? [];
|
|
21
|
+
|
|
22
|
+
// Run preactions sequentially
|
|
23
|
+
for (const cmd of preactions) {
|
|
24
|
+
console.log(`→ preaction: ${cmd}`);
|
|
25
|
+
await runForeground(cmd, envObj, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (actions.length === 0) return;
|
|
29
|
+
|
|
30
|
+
const bg = actions.slice(0, -1);
|
|
31
|
+
const fg = actions[actions.length - 1];
|
|
32
|
+
|
|
33
|
+
for (const cmd of bg) {
|
|
34
|
+
console.log(`→ background action: ${cmd}`);
|
|
35
|
+
runBackground(cmd, envObj, options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`→ foreground action (attached): ${fg}`);
|
|
39
|
+
try {
|
|
40
|
+
await runBackground(fg, envObj, options, true);
|
|
41
|
+
} finally {
|
|
42
|
+
killAllBackground();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default runHasteCommand;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
|
|
3
|
+
// Track background processes so we can kill them on exit
|
|
4
|
+
export const bgChildren = new Set();
|
|
5
|
+
|
|
6
|
+
export function registerBackground(child) {
|
|
7
|
+
bgChildren.add(child);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function killAllBackground() {
|
|
11
|
+
for (const child of Array.from(bgChildren)) {
|
|
12
|
+
try {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], {
|
|
15
|
+
shell: true,
|
|
16
|
+
stdio: 'ignore',
|
|
17
|
+
});
|
|
18
|
+
} else {
|
|
19
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
bgChildren.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let signalsRegistered = false;
|
|
27
|
+
export function ensureSignalHandlers() {
|
|
28
|
+
if (signalsRegistered) return;
|
|
29
|
+
signalsRegistered = true;
|
|
30
|
+
process.on('SIGINT', () => {
|
|
31
|
+
console.log('\nSIGINT');
|
|
32
|
+
killAllBackground();
|
|
33
|
+
process.exit(130);
|
|
34
|
+
});
|
|
35
|
+
process.on('SIGTERM', () => {
|
|
36
|
+
killAllBackground();
|
|
37
|
+
process.exit(143);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { getData, replaceTokens } from '../dataLayer.js';
|
|
3
|
+
import { registerBackground } from './processManager.js';
|
|
4
|
+
|
|
5
|
+
export function runBackground(
|
|
6
|
+
cmd,
|
|
7
|
+
envObj = {},
|
|
8
|
+
options = {},
|
|
9
|
+
attached = false
|
|
10
|
+
) {
|
|
11
|
+
const isWin = process.platform === 'win32';
|
|
12
|
+
|
|
13
|
+
// Replace ${field} tokens in env values using dataLayer
|
|
14
|
+
const expandedEnv = {};
|
|
15
|
+
for (const k of Object.keys(envObj)) {
|
|
16
|
+
const v = envObj[k];
|
|
17
|
+
expandedEnv[k] = typeof v === 'string' ? replaceTokens(v) : v;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Replace in command string
|
|
21
|
+
const outCmd = replaceTokens(cmd);
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const child = spawn(outCmd, {
|
|
25
|
+
shell: true,
|
|
26
|
+
stdio: attached ? 'inherit' : 'ignore',
|
|
27
|
+
env: { ...process.env, ...expandedEnv },
|
|
28
|
+
detached: !attached && !isWin,
|
|
29
|
+
windowsHide: !attached && isWin,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!attached && !isWin) child.unref();
|
|
33
|
+
|
|
34
|
+
registerBackground(child);
|
|
35
|
+
|
|
36
|
+
child.once('error', (err) => {
|
|
37
|
+
reject(err);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.once('exit', (code, signal) => {
|
|
41
|
+
if (signal)
|
|
42
|
+
return reject(new Error(`${cmd} exited via signal ${signal}`));
|
|
43
|
+
if (code === 0) return resolve();
|
|
44
|
+
reject(new Error(`${cmd} exited with code ${code}`));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default runBackground;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { setData } from '../dataLayer.js';
|
|
3
|
+
|
|
4
|
+
// Regex to capture tokens like: {out:field value}
|
|
5
|
+
const TOKEN_RX = /\{out:(?<field>[^\s}]+)\s+(?<value>[^\s}]+)\}/g;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run a command in the foreground, capturing stdout/stderr. Extracts token patterns
|
|
9
|
+
* of the form {out:field value} and stores them in the shared dataLayer.
|
|
10
|
+
*/
|
|
11
|
+
export function runForeground(cmd, envObj = {}, options = {}) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
let lastLine = '';
|
|
14
|
+
let buffer = '';
|
|
15
|
+
|
|
16
|
+
const child = spawn(cmd, {
|
|
17
|
+
shell: true,
|
|
18
|
+
env: { ...process.env, ...envObj },
|
|
19
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const handleData = (chunk) => {
|
|
23
|
+
const text = chunk.toString();
|
|
24
|
+
buffer += text;
|
|
25
|
+
const lines = text.trim().split(/\r?\n/);
|
|
26
|
+
if (lines.length) lastLine = lines[lines.length - 1];
|
|
27
|
+
process.stdout.write(text); // echo output
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
child.stdout.on('data', handleData);
|
|
31
|
+
child.stderr.on('data', handleData);
|
|
32
|
+
child.on('error', reject);
|
|
33
|
+
|
|
34
|
+
child.on('exit', (code, signal) => {
|
|
35
|
+
// Extract tokens and populate dataLayer
|
|
36
|
+
for (const match of buffer.matchAll(TOKEN_RX)) {
|
|
37
|
+
setData(match.groups.field, match.groups.value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (signal)
|
|
41
|
+
return reject(new Error(`${cmd} exited via signal ${signal}`));
|
|
42
|
+
if (code === 0) return resolve(lastLine);
|
|
43
|
+
reject(
|
|
44
|
+
new Error(`${cmd} exited with code ${code}. Last line: ${lastLine}`)
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default runForeground;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
//Run Action List before all script actions
|
|
4
|
+
|
|
5
|
+
const result = execSync('yarn workspaces list --json', { encoding: 'utf8' })
|
|
6
|
+
.trim()
|
|
7
|
+
.split('\n')
|
|
8
|
+
.map((line) => JSON.parse(line));
|
|
9
|
+
|
|
10
|
+
console.log(result.map((w) => w.location)); // list of workspace paths
|
|
11
|
+
|
|
12
|
+
function executeCommandsIfWorkspaceAction(commands = [], action) {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Option/value validation utilities
|
|
2
|
+
export function verifyOptionValue(optionKey, value, optionsData) {
|
|
3
|
+
const optionInfo = optionsData[optionKey];
|
|
4
|
+
if (optionInfo && Array.isArray(optionInfo.options)) {
|
|
5
|
+
if (!optionInfo.options.includes(value)) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
`Invalid value for --${optionKey}: ${value}. Valid options are: ${optionInfo.options.join(', ')}`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
@@ -24,10 +24,6 @@ export function getHasteFiles() {
|
|
|
24
24
|
return files.map((f) => path.join(dir, f));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const workspaceMap = {
|
|
28
|
-
web: 'next-app',
|
|
29
|
-
app: 'expo-app',
|
|
30
|
-
};
|
|
31
27
|
export function getHasteConfig() {
|
|
32
28
|
const objHaste = getHasteFiles();
|
|
33
29
|
const hasteFileConfig = {};
|
|
@@ -36,12 +32,10 @@ export function getHasteConfig() {
|
|
|
36
32
|
const fileName = path.basename(file).replace('.json', '');
|
|
37
33
|
if (fileName === 'config') {
|
|
38
34
|
const raw = fs.readFileSync(file, 'utf-8');
|
|
39
|
-
console.log('file content', file, raw);
|
|
40
35
|
const data = JSON.parse(raw);
|
|
41
36
|
if (data) configObject = data;
|
|
42
37
|
} else {
|
|
43
38
|
const raw = fs.readFileSync(file, 'utf-8');
|
|
44
|
-
console.log('file content', file, raw);
|
|
45
39
|
const data = JSON.parse(raw);
|
|
46
40
|
hasteFileConfig[fileName] = data;
|
|
47
41
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,55 +1,56 @@
|
|
|
1
|
-
import 'dotenv/config'
|
|
1
|
+
import 'dotenv/config';
|
|
2
2
|
import spawn from 'cross-spawn';
|
|
3
3
|
|
|
4
|
-
import { program } from './app.js'
|
|
5
|
-
import './commands/build/index.js'
|
|
6
|
-
import './commands/deploy/index.js'
|
|
7
|
-
import './commands/destroy.js'
|
|
8
|
-
import './commands/dev/index.js'
|
|
9
|
-
import './commands/generate/index.js'
|
|
10
|
-
import './commands/init/index.js'
|
|
11
|
-
import './commands/prune/index.js'
|
|
12
|
-
import './commands/
|
|
13
|
-
import './commands/
|
|
14
|
-
import './commands/
|
|
15
|
-
import './commands/
|
|
16
|
-
import './commands/
|
|
17
|
-
import { getHasteConfig} from './commands/loadFromRoot.js'
|
|
4
|
+
import { program } from './app.js';
|
|
5
|
+
import './commands/build/index.js';
|
|
6
|
+
import './commands/deploy/index.js';
|
|
7
|
+
import './commands/destroy.js';
|
|
8
|
+
import './commands/dev/index.js';
|
|
9
|
+
import './commands/generate/index.js';
|
|
10
|
+
import './commands/init/index.js';
|
|
11
|
+
import './commands/prune/index.js';
|
|
12
|
+
import './commands/seed/index.js';
|
|
13
|
+
import './commands/submit/index.js';
|
|
14
|
+
import './commands/update/index.js';
|
|
15
|
+
import './commands/build-process/index.js';
|
|
16
|
+
import { getHasteConfig } from './commands/loadFromRoot.js';
|
|
18
17
|
|
|
19
|
-
const {config} = getHasteConfig()
|
|
18
|
+
const { config } = getHasteConfig();
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
const workspacemap = config.workspace || {}
|
|
20
|
+
const workspacemap = config.workspace || {};
|
|
23
21
|
|
|
24
22
|
program.on('command:*', (operands) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
const [cmd] = operands; // e.g. "destroy3"
|
|
24
|
+
const raw = program.rawArgs.slice(2); // after `node script.js`
|
|
25
|
+
const i = raw.indexOf(cmd);
|
|
26
|
+
const tokens = i >= 0 ? raw.slice(i) : operands;
|
|
27
|
+
|
|
28
|
+
const workspace = workspacemap[tokens[0]] || tokens[0];
|
|
29
|
+
let rest = tokens.slice(1);
|
|
30
|
+
console.log('Workspace:', workspace);
|
|
31
|
+
console.log('Rest:', rest);
|
|
32
|
+
|
|
33
|
+
// If the “rest” is empty or starts with flags, insert a default script
|
|
34
|
+
if (rest.length === 0 || rest[0].startsWith('--')) {
|
|
35
|
+
console.log('Rest is empty or starts with flags, inserting DEFAULT_SCRIPT');
|
|
36
|
+
// Prefer an explicit script name; if you want to always use `run`, do:
|
|
37
|
+
// rest = ['run', DEFAULT_SCRIPT, ...rest];
|
|
38
|
+
rest = [...rest]; // yarn workspace <ws> dev --flags
|
|
39
|
+
console.log('Rest after inserting DEFAULT_SCRIPT:', rest);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const args = ['workspace', workspace, ...rest];
|
|
43
|
+
console.log('Final args for yarn:', args);
|
|
44
|
+
|
|
45
|
+
console.error(`Unknown command. Falling back to: yarn ${args.join(' ')}`);
|
|
46
|
+
|
|
47
|
+
const child = spawn('yarn', args, {
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
shell: process.platform === 'win32',
|
|
50
|
+
});
|
|
51
|
+
child.on('exit', (code) => {
|
|
52
|
+
console.log('Child process exited with code:', code);
|
|
53
|
+
process.exitCode = code ?? 1;
|
|
54
|
+
});
|
|
54
55
|
});
|
|
55
|
-
program.parse()
|
|
56
|
+
program.parse();
|
package/package.json
CHANGED
package/lib/commands/reset.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
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
|
-
})
|