-
-
Notifications
You must be signed in to change notification settings - Fork 125
Expand file tree
/
Copy pathscript-pick.mjs
More file actions
145 lines (126 loc) · 4.99 KB
/
Copy pathscript-pick.mjs
File metadata and controls
145 lines (126 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env node
/**
* This script allows the user to run a specified npm command (default is 'dev')
* on selected packages, apps, or examples from a monorepo.
* The user is prompted to choose items (apps, packages, or examples) through a multi-select prompt.
* It constructs a command for the selected items and runs it using `bun`.
* The script supports filtering specific directories or selecting "all" items.
*
* The script uses `fast-glob` for pattern matching, `enquirer` for interactive user prompts,
* and `spawn` to execute commands in the shell.
*
* @module CommandExecutor
*/
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import Enquirer from 'enquirer';
import fg from 'fast-glob';
import minimist from 'minimist';
const args = minimist(process.argv.slice(2));
// Defaults to 'dev' if not provided
const chosenCommand = args.command ?? 'dev';
/**
* Normalizes a file path to use forward slashes, which is consistent across platforms.
* @param {string} filePath - The file path to normalize.
* @returns {string} The normalized file path.
*/
const normalizePath = (filePath) => filePath.replace(/\\/g, '/');
/**
* Fetches packages from a given glob pattern.
* Returns an array of objects with the name and relative path of each package.
*
* @param {string} pattern - Glob pattern to match package.json files.
* @param {string} index - Index value used to prefix each package (e.g., 'app', 'package', 'example').
* @returns {Array<Object>} Array of { name, value } objects for each package.
*/
const getPackages = (pattern, index) => {
return fg.sync(pattern, { ignore: ['**/node_modules/**'] }).map((pkgPath) => {
const packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const packageRelativePath = path.relative(
process.cwd(),
path.dirname(pkgPath)
);
return {
name: packageJson.name,
value: `${index}:${normalizePath(packageRelativePath)}`,
};
});
};
const apps = getPackages('./apps/**/package.json', 'app');
const packages = getPackages('./packages/**/package.json', 'packages');
const examples = getPackages('./examples/**/package.json', 'example');
/**
* Prompts the user to select items using a multiselect prompt.
* Returns an object with the user's selection in the `.selected` property.
*
* @param {string} message - Message displayed in the prompt.
* @param {Array<Object>} choices - Array of possible choices for the user to select.
* @returns {Promise<Object>} Promise that resolves with the selected items.
*/
const askCheckboxPlus = (message, choices) => {
return Enquirer.prompt({
type: 'multiselect',
name: 'selected',
message: message,
choices: choices,
result(names) {
return this.map(names);
},
});
};
/**
* Main function that handles the logic of prompting the user, constructing the command,
* and executing it on the selected packages, apps, or examples.
*/
const main = async () => {
// Prompt user to pick from apps, packages, or examples
const selectedPackages = await askCheckboxPlus(
`Select items to start in mode: ${chosenCommand}`,
[
{ name: 'all apps', value: 'all_apps', choices: apps },
{ name: 'all packages', value: 'all_packages', choices: packages },
{ name: 'all examples', value: 'all_examples', choices: examples },
]
);
// Flatten the selected items from the user
const selectedPackagesArray = Object.values(selectedPackages.selected);
// Determine if user chose the "all" options
const isAllAppsSelected = selectedPackagesArray.includes('all_apps');
const isAllPackagesSelected = selectedPackagesArray.includes('all_packages');
const isAllExamplesSelected = selectedPackagesArray.includes('all_examples');
// Build filters for packages, apps, and examples
const packageFilters = isAllPackagesSelected
? ['--filter "./packages"']
: selectedPackagesArray
.filter((pkg) => pkg.startsWith('packages:'))
.map((pkg) => `--filter "./${pkg.replace('packages:', '')}"`);
const appFilters = isAllAppsSelected
? ['--filter "./apps"']
: selectedPackagesArray
.filter((app) => app.startsWith('app:'))
.map((app) => `--filter "./${app.replace('app:', '')}"`);
const exampleFilters = isAllExamplesSelected
? ['--filter "./examples"']
: selectedPackagesArray
.filter((ex) => ex.startsWith('example:'))
.map((ex) => `--filter "./${ex.replace('example:', '')}"`);
// Construct the PNPM command, including chosen command
const command = `bun ${[
...packageFilters,
...appFilters,
...exampleFilters,
].join(' ')} ${chosenCommand}`;
console.info('->', command);
// Execute the command
const childProcess = spawn(command, { shell: true, stdio: 'inherit' });
childProcess.on('error', (err) => {
console.error('Failed to start command:', err);
});
childProcess.on('exit', (code) => {
if (code !== 0) {
console.error(`Command exited with code ${code}`);
}
});
};
main().catch((err) => console.error(err));