xgem-cli 2.0.0-alpha.1 → 2.0.0-alpha.2
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/README.md +20 -1
- package/bin/xgem.js +218 -0
- package/lib-win/doctor.js +44 -0
- package/lib-win/flutter.js +97 -0
- package/lib-win/git.js +161 -0
- package/lib-win/logger.js +35 -0
- package/lib-win/scaffold.js +79 -0
- package/lib-win/utils.js +67 -0
- package/package.json +8 -7
- package/templates-win/docker/build-up.mjs.tmpl +3 -0
- package/templates-win/docker/hard-clean.mjs.tmpl +4 -0
- package/templates-win/flutter/build-runner.mjs.tmpl +4 -0
- package/templates-win/flutter/build.mjs.tmpl +4 -0
- package/templates-win/flutter/hard-clean.mjs.tmpl +6 -0
- package/templates-win/go/build.mjs.tmpl +3 -0
- package/templates-win/go/hard-clean.mjs.tmpl +4 -0
- package/templates-win/node/build.mjs.tmpl +4 -0
- package/templates-win/node/hard-clean.mjs.tmpl +9 -0
- package/templates-win/node/start.mjs.tmpl +3 -0
- package/templates-win/python/hard-clean.mjs.tmpl +8 -0
- package/templates-win/python/install.mjs.tmpl +12 -0
- package/templates-win/rust/build.mjs.tmpl +3 -0
- package/templates-win/rust/hard-clean.mjs.tmpl +3 -0
- package/templates-win/webframework/build.mjs.tmpl +3 -0
- package/templates-win/webframework/dev.mjs.tmpl +3 -0
- package/templates-win/webframework/hard-clean.mjs.tmpl +9 -0
- package/scripts/check-platform.js +0 -17
package/README.md
CHANGED
|
@@ -27,7 +27,8 @@ xgem isn't in Homebrew's central `homebrew-core` (that requires a formal submiss
|
|
|
27
27
|
npm install -g xgem-cli
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Works natively on Windows too (no WSL needed) — npm's install runs a small Node.js
|
|
31
|
+
engine there instead of the bash one; see [Windows support](#windows-support).
|
|
31
32
|
|
|
32
33
|
### curl
|
|
33
34
|
|
|
@@ -106,6 +107,24 @@ force Flutter to regenerate the generated package.
|
|
|
106
107
|
|
|
107
108
|
Run `xgem doctor ios` any time for a standalone readiness report without doing a build.
|
|
108
109
|
|
|
110
|
+
## Windows support
|
|
111
|
+
|
|
112
|
+
Homebrew and the curl installer are macOS/Linux only, same as any bash tool — that's
|
|
113
|
+
not going to change. npm is different: it's published with a small native Windows
|
|
114
|
+
engine (`lib-win/`, driven by `bin/xgem.js`) so `npm install -g xgem-cli` actually works
|
|
115
|
+
in `cmd.exe`/PowerShell, no WSL required.
|
|
116
|
+
|
|
117
|
+
What works on Windows: `init`/`add`/`run`/`terminate`, `doctor`, the git workflow
|
|
118
|
+
commands, and Flutter builds for **APK and Windows desktop** targets. What doesn't:
|
|
119
|
+
Flutter **iOS/macOS** builds — Xcode has no Windows equivalent, so `xgem doctor ios`
|
|
120
|
+
explains that plainly instead of pretending. `swift` isn't offered as a framework
|
|
121
|
+
choice on Windows for the same reason. If you're inside WSL, none of this applies —
|
|
122
|
+
WSL reports itself as Linux, so you get the full bash engine automatically.
|
|
123
|
+
|
|
124
|
+
On macOS/Linux, npm installs run the exact same bash engine as Homebrew/curl (`bin/xgem.js`
|
|
125
|
+
is a thin passthrough that execs the real `bin/xgem` script) — there's only one
|
|
126
|
+
implementation to trust on POSIX; Windows is the only platform with a second one.
|
|
127
|
+
|
|
109
128
|
## Roadmap
|
|
110
129
|
|
|
111
130
|
Deferred to a follow-up pass, not yet in this release:
|
package/bin/xgem.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// xgem npm entrypoint. On macOS/Linux this is a thin passthrough to the
|
|
3
|
+
// real, already-tested bash bin/xgem sitting next to it — zero logic
|
|
4
|
+
// duplication, zero behavioral change for POSIX users. On Windows it runs
|
|
5
|
+
// the native lib-win/*.js engine, since bash doesn't exist there.
|
|
6
|
+
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
|
|
10
|
+
if (process.platform !== 'win32') {
|
|
11
|
+
const bashScript = path.join(__dirname, 'xgem');
|
|
12
|
+
const result = spawnSync(bashScript, process.argv.slice(2), { stdio: 'inherit' });
|
|
13
|
+
process.exit(result.status ?? 0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ---- Windows-native engine ----
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const { logInfo, logSuccess, logWarn, logError, die, paint } = require('../lib-win/logger');
|
|
19
|
+
const { prompt } = require('../lib-win/utils');
|
|
20
|
+
const { cmdDoctor } = require('../lib-win/doctor');
|
|
21
|
+
const { cmdGit } = require('../lib-win/git');
|
|
22
|
+
const { cmdFlutter } = require('../lib-win/flutter');
|
|
23
|
+
const scaffold = require('../lib-win/scaffold');
|
|
24
|
+
|
|
25
|
+
const CONFIG_DIR = '.xgem-automate';
|
|
26
|
+
|
|
27
|
+
function printBanner() {
|
|
28
|
+
console.log(paint('yellow', ''));
|
|
29
|
+
console.log('██╗ ██╗ ██████╗ ███████╗███╗ ███╗██╗███╗ ██╗██╗');
|
|
30
|
+
console.log('╚██╗██╔╝ ██╔════╝ ██╔════╝████╗ ████║██║████╗ ██║██║');
|
|
31
|
+
console.log(' ╚███╔╝ █████╗██║ ███╗█████╗ ██╔████╔██║██║██╔██╗ ██║██║');
|
|
32
|
+
console.log(' ██╔██╗ ╚════╝██║ ██║██╔══╝ ██║╚██╔╝██║██║██║╚██╗██║██║');
|
|
33
|
+
console.log('██╔╝ ██╗ ╚██████╔╝███████╗██║ ╚═╝ ██║██║██║ ╚████║██║');
|
|
34
|
+
console.log('╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝');
|
|
35
|
+
console.log(paint('cyan', ' ....Windows engine....\n'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printUsage() {
|
|
39
|
+
console.log('Usage:');
|
|
40
|
+
console.log(' xgem init - Initialize tracking system and add first framework');
|
|
41
|
+
console.log(' xgem add - Interactive state-aware addition of remaining frameworks');
|
|
42
|
+
console.log(' xgem run <framework> <script> - Run a workspace automation command script');
|
|
43
|
+
console.log(' xgem terminate - Purge all generated automated layouts completely');
|
|
44
|
+
console.log(' xgem <framework> [help] - View tailored instructions for a specific script layout');
|
|
45
|
+
console.log(' xgem doctor [ios] - Report on your environment / iOS build availability');
|
|
46
|
+
console.log(' xgem git cmt "message" - Auto-stage, commit, rebase-pull, and push');
|
|
47
|
+
console.log(' xgem git init - Setup local repo, attach remote tracker shortcuts');
|
|
48
|
+
console.log(' xgem git rm-remote - Drop specified target remote tracing rules');
|
|
49
|
+
console.log(' xgem git rm-branch - Safely drop local and remote workspace branch states');
|
|
50
|
+
console.log(' xgem --version - Print xgem\'s version');
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log('Flags (any command): --yes (skip confirmations), --dry-run (show, don\'t apply), --verbose');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function selectFramework(options) {
|
|
56
|
+
options.forEach((fw, i) => console.log(`${i + 1}) ${fw}`));
|
|
57
|
+
const choice = await prompt('Enter target selection number');
|
|
58
|
+
const idx = parseInt(choice, 10) - 1;
|
|
59
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= options.length) return null;
|
|
60
|
+
return options[idx];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateGitignore() {
|
|
64
|
+
const gitignorePath = '.gitignore';
|
|
65
|
+
if (fs.existsSync(gitignorePath)) {
|
|
66
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
67
|
+
if (!content.includes(`${CONFIG_DIR}/`)) {
|
|
68
|
+
fs.appendFileSync(gitignorePath, `\n${CONFIG_DIR}/\n`);
|
|
69
|
+
logSuccess('Added automation tracking to .gitignore');
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
fs.writeFileSync(gitignorePath, `${CONFIG_DIR}/\n`);
|
|
73
|
+
logSuccess('Created .gitignore and hidden tracking layer folder references.');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function cmdInit() {
|
|
78
|
+
printBanner();
|
|
79
|
+
if (fs.existsSync(CONFIG_DIR)) {
|
|
80
|
+
logWarn("Workspace configuration layer already exists! Use 'xgem add' to append frameworks.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
84
|
+
|
|
85
|
+
console.log('Select your starting framework architecture:');
|
|
86
|
+
const fw = await selectFramework(scaffold.ALL_FRAMEWORKS);
|
|
87
|
+
if (!fw) die('Invalid selection.');
|
|
88
|
+
scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
89
|
+
logSuccess(`Successfully appended standard scripts for: ${CONFIG_DIR}/${fw}`);
|
|
90
|
+
|
|
91
|
+
updateGitignore();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function cmdAdd() {
|
|
95
|
+
if (!fs.existsSync(CONFIG_DIR)) die("System tracking layer not found. Run 'xgem init' first.");
|
|
96
|
+
|
|
97
|
+
const remaining = scaffold.getRemainingFrameworks(CONFIG_DIR);
|
|
98
|
+
if (remaining.length === 0) {
|
|
99
|
+
logWarn('All available framework blocks are already generated inside the workspace!');
|
|
100
|
+
const restore = (await prompt('Do you want to restore/regenerate all framework templates to defaults? (y/n)')).toLowerCase();
|
|
101
|
+
if (restore === 'y') {
|
|
102
|
+
for (const fw of scaffold.ALL_FRAMEWORKS) scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
103
|
+
logSuccess('All framework automation layouts cleanly re-generated!');
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('Select a framework script block to add:');
|
|
109
|
+
const fw = await selectFramework(remaining);
|
|
110
|
+
if (!fw) { console.log('Operation canceled.'); return; }
|
|
111
|
+
scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
112
|
+
logSuccess(`Successfully loaded and updated files for: ${CONFIG_DIR}/${fw}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function cmdRun(fw, script) {
|
|
116
|
+
if (!fw || !script) die('Usage: xgem run <framework> <script>');
|
|
117
|
+
if (fw === 'flutter' && ['hard-clean', 'build', 'build-runner'].includes(script)) {
|
|
118
|
+
await cmdFlutter(script);
|
|
119
|
+
} else {
|
|
120
|
+
scaffold.runScript(fw, script, CONFIG_DIR);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function cmdTerminate() {
|
|
125
|
+
if (!fs.existsSync(CONFIG_DIR)) die(`Tracking layer ${CONFIG_DIR} not found. Nothing to terminate.`);
|
|
126
|
+
|
|
127
|
+
logWarn(`Initiating core termination sequence for ${CONFIG_DIR}...`);
|
|
128
|
+
const folders = fs.readdirSync(CONFIG_DIR, { withFileTypes: true })
|
|
129
|
+
.filter((d) => d.isDirectory())
|
|
130
|
+
.map((d) => d.name);
|
|
131
|
+
|
|
132
|
+
const { confirm } = require('../lib-win/utils');
|
|
133
|
+
const ok = await confirm(`This deletes ${CONFIG_DIR} and everything in it. Continue?`);
|
|
134
|
+
if (!ok) { logInfo('Cancelled.'); return; }
|
|
135
|
+
|
|
136
|
+
for (const fw of folders) {
|
|
137
|
+
console.log(` [ x ] Removing ${fw}...`);
|
|
138
|
+
fs.rmSync(path.join(CONFIG_DIR, fw), { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
141
|
+
|
|
142
|
+
if (fs.existsSync('.gitignore')) {
|
|
143
|
+
const lines = fs.readFileSync('.gitignore', 'utf8').split(/\r?\n/).filter((l) => l !== `${CONFIG_DIR}/`);
|
|
144
|
+
fs.writeFileSync('.gitignore', lines.join('\n'));
|
|
145
|
+
logSuccess('Cleaned up .gitignore tracking records.');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
logSuccess(`Termination complete. Total blocks destroyed: ${folders.length} (${folders.join(', ') || 'none'})`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function printFrameworkHelp(fw) {
|
|
152
|
+
console.log(paint('cyan', `=== ${fw.toUpperCase()} Automation Blueprint (Windows) ===`));
|
|
153
|
+
if (!fs.existsSync(path.join(CONFIG_DIR, fw))) {
|
|
154
|
+
logWarn('Status: Not currently injected in this workspace.');
|
|
155
|
+
console.log("Run 'xgem add' to instantly append its structural scripts.\n");
|
|
156
|
+
} else {
|
|
157
|
+
logSuccess(`Status: Active & Mounted inside ${CONFIG_DIR}/${fw}/`);
|
|
158
|
+
console.log('');
|
|
159
|
+
}
|
|
160
|
+
console.log(`To execute scripts, use: xgem run ${fw} <script>\n`);
|
|
161
|
+
console.log('Configured Commands:');
|
|
162
|
+
for (const script of scaffold.frameworkScripts(fw) || []) {
|
|
163
|
+
console.log(` ${script}`);
|
|
164
|
+
}
|
|
165
|
+
if (fw === 'flutter') {
|
|
166
|
+
console.log('');
|
|
167
|
+
logInfo("Flutter builds on Windows support APK and Windows desktop targets. Run 'xgem doctor ios' for why iOS/macOS aren't available here.");
|
|
168
|
+
}
|
|
169
|
+
console.log('');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function main() {
|
|
173
|
+
const rawArgs = process.argv.slice(2);
|
|
174
|
+
const args = [];
|
|
175
|
+
for (const arg of rawArgs) {
|
|
176
|
+
if (arg === '--yes') process.env.XGEM_YES = '1';
|
|
177
|
+
else if (arg === '--dry-run') process.env.XGEM_DRY_RUN = '1';
|
|
178
|
+
else if (arg === '--verbose') process.env.XGEM_VERBOSE = '1';
|
|
179
|
+
else args.push(arg);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const [cmd, a2, a3] = args;
|
|
183
|
+
|
|
184
|
+
switch (cmd) {
|
|
185
|
+
case 'init': return cmdInit();
|
|
186
|
+
case 'add': return cmdAdd();
|
|
187
|
+
case 'run': return cmdRun(a2, a3);
|
|
188
|
+
case 'terminate': return cmdTerminate();
|
|
189
|
+
case 'git':
|
|
190
|
+
if (!a2) die('Usage: xgem git <cmt|init|rm-remote|rm-branch>');
|
|
191
|
+
return cmdGit(a2, a3);
|
|
192
|
+
case 'doctor': return cmdDoctor(a2);
|
|
193
|
+
case '--version':
|
|
194
|
+
case '-V':
|
|
195
|
+
case 'version': {
|
|
196
|
+
const pkg = require('../package.json');
|
|
197
|
+
console.log(`xgem ${pkg.version} (Windows engine)`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
case 'flutter':
|
|
201
|
+
case 'node':
|
|
202
|
+
case 'python':
|
|
203
|
+
case 'react':
|
|
204
|
+
case 'vue':
|
|
205
|
+
case 'angular':
|
|
206
|
+
case 'go':
|
|
207
|
+
case 'rust':
|
|
208
|
+
case 'docker':
|
|
209
|
+
return printFrameworkHelp(cmd);
|
|
210
|
+
default:
|
|
211
|
+
return printUsage();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().catch((err) => {
|
|
216
|
+
logError(err.message || String(err));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// xgem Windows engine doctor — mirrors lib/doctor.sh's general report.
|
|
2
|
+
// No iOS/SwiftPM engine exists here (see flutter.js) since Xcode has no
|
|
3
|
+
// Windows equivalent; `doctor ios` explains that instead of pretending.
|
|
4
|
+
|
|
5
|
+
const { getVersion, detectArch } = require('./utils');
|
|
6
|
+
const { paint } = require('./logger');
|
|
7
|
+
|
|
8
|
+
function printGeneral() {
|
|
9
|
+
console.log(paint('cyan', '=== xgem doctor (Windows) ==='));
|
|
10
|
+
console.log(`OS: win32`);
|
|
11
|
+
console.log(`Arch: ${detectArch()}`);
|
|
12
|
+
console.log(`git: ${getVersion('git') || 'not found'}`);
|
|
13
|
+
console.log(`flutter: ${getVersion('flutter') || 'not found'}`);
|
|
14
|
+
console.log(`dart: ${getVersion('dart') || 'not found'}`);
|
|
15
|
+
console.log(`node: ${getVersion('node') || 'not found'}`);
|
|
16
|
+
console.log(`python: ${getVersion('python') || getVersion('python3') || 'not found'}`);
|
|
17
|
+
console.log(`go: ${getVersion('go', ['version']) || 'not found'}`);
|
|
18
|
+
console.log(`cargo: ${getVersion('cargo') || 'not found'}`);
|
|
19
|
+
console.log(`docker: ${getVersion('docker') || 'not found'}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printIos() {
|
|
23
|
+
console.log(paint('cyan', '=== xgem doctor ios (Windows) ==='));
|
|
24
|
+
console.log(paint('yellow', 'iOS/macOS builds are not available on Windows — Xcode has no Windows equivalent.'));
|
|
25
|
+
console.log('Use WSL2 with a Mac, or a real Mac, for Flutter iOS builds.');
|
|
26
|
+
const flutterVersion = getVersion('flutter');
|
|
27
|
+
if (flutterVersion) {
|
|
28
|
+
console.log(`\nflutter is installed here (${flutterVersion}) and can still build Android/Windows targets:`);
|
|
29
|
+
console.log(' xgem run flutter build (choose APK or Windows)');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cmdDoctor(target) {
|
|
34
|
+
if (target === 'ios') {
|
|
35
|
+
printIos();
|
|
36
|
+
} else if (!target) {
|
|
37
|
+
printGeneral();
|
|
38
|
+
} else {
|
|
39
|
+
const { die } = require('./logger');
|
|
40
|
+
die(`Unknown doctor target '${target}'. Usage: xgem doctor [ios]`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { cmdDoctor, printGeneral, printIos };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// xgem Windows engine flutter commands. hard-clean/build-runner have no
|
|
2
|
+
// platform-specific dependency and work as-is. build only offers targets
|
|
3
|
+
// that are actually buildable from a Windows host (APK, Windows desktop) —
|
|
4
|
+
// iOS/macOS need Xcode (a Mac), and Flutter can't cross-compile Linux
|
|
5
|
+
// desktop from Windows either, so those fail fast with a clear message
|
|
6
|
+
// instead of attempting anything.
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const { spawnSync } = require('node:child_process');
|
|
10
|
+
const { logInfo, logSuccess, logError, die } = require('./logger');
|
|
11
|
+
const { prompt, requireCmd } = require('./utils');
|
|
12
|
+
|
|
13
|
+
function flutter(args) {
|
|
14
|
+
return spawnSync('flutter', args, { stdio: 'inherit' });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function cmdHardClean() {
|
|
18
|
+
requireCmd('flutter', 'Install Flutter: https://docs.flutter.dev/get-started/install/windows');
|
|
19
|
+
logInfo('Cleaning Flutter project...');
|
|
20
|
+
fs.rmSync('pubspec.lock', { force: true });
|
|
21
|
+
flutter(['clean']);
|
|
22
|
+
logInfo('Getting Flutter packages...');
|
|
23
|
+
flutter(['pub', 'get']);
|
|
24
|
+
logInfo('Running Flutter...');
|
|
25
|
+
flutter(['run', '-v']);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function cmdBuildRunner() {
|
|
29
|
+
requireCmd('flutter', 'Install Flutter: https://docs.flutter.dev/get-started/install/windows');
|
|
30
|
+
logInfo('Running Flutter Build Runner...');
|
|
31
|
+
const verboseChoice = ((await prompt('Run in verbose mode? (Y/n)')) || 'Y').toLowerCase();
|
|
32
|
+
const args = ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'];
|
|
33
|
+
if (verboseChoice === 'y') args.push('-v');
|
|
34
|
+
logInfo(`Executing: flutter ${args.join(' ')}`);
|
|
35
|
+
flutter(args);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pubspecVersion() {
|
|
39
|
+
if (!fs.existsSync('pubspec.yaml')) return null;
|
|
40
|
+
const content = fs.readFileSync('pubspec.yaml', 'utf8');
|
|
41
|
+
const match = content.match(/^version:\s*(\S+)/m);
|
|
42
|
+
return match ? match[1] : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function cmdBuild() {
|
|
46
|
+
requireCmd('flutter', 'Install Flutter: https://docs.flutter.dev/get-started/install/windows');
|
|
47
|
+
console.log('--- Flutter Build Orchestrator (Windows) ---');
|
|
48
|
+
|
|
49
|
+
let currentName = '';
|
|
50
|
+
let currentNum = '';
|
|
51
|
+
const currentFull = pubspecVersion();
|
|
52
|
+
if (currentFull) {
|
|
53
|
+
[currentName, currentNum = '1'] = currentFull.split('+');
|
|
54
|
+
logInfo(`Current pubspec.yaml version: ${currentFull}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const buildName = (await prompt('Enter build version name', currentName || '1.0.0'));
|
|
58
|
+
const buildNumber = (await prompt('Enter build number', currentNum || '1'));
|
|
59
|
+
const isRelease = ((await prompt('Is this a release build? (Y/n)', 'y'))).toLowerCase();
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync('pubspec.yaml')) {
|
|
62
|
+
const newVersion = `${buildName}+${buildNumber}`;
|
|
63
|
+
logSuccess(`Updating pubspec.yaml to version: ${newVersion}`);
|
|
64
|
+
const content = fs.readFileSync('pubspec.yaml', 'utf8');
|
|
65
|
+
fs.writeFileSync('pubspec.yaml', content.replace(/^version:.*/m, `version: ${newVersion}`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log('\nSelect Target Platform:');
|
|
69
|
+
console.log('1) APK (Android)');
|
|
70
|
+
console.log('2) Windows');
|
|
71
|
+
console.log('(iOS/macOS require a Mac; Linux desktop can\'t be cross-built from Windows.)');
|
|
72
|
+
const platformChoice = await prompt('Choose [1-2]');
|
|
73
|
+
|
|
74
|
+
const mode = (isRelease === 'n') ? '--debug' : '--release';
|
|
75
|
+
const buildArgs = [`--build-name=${buildName}`, `--build-number=${buildNumber}`];
|
|
76
|
+
|
|
77
|
+
if (platformChoice === '1') {
|
|
78
|
+
logInfo(`Building APK (${mode})...`);
|
|
79
|
+
flutter(['build', 'apk', mode, ...buildArgs]);
|
|
80
|
+
} else if (platformChoice === '2') {
|
|
81
|
+
logInfo(`Building Windows desktop app (${mode})...`);
|
|
82
|
+
flutter(['build', 'windows', mode, ...buildArgs]);
|
|
83
|
+
} else {
|
|
84
|
+
die(`'${platformChoice}' isn't buildable from Windows. iOS/macOS need a Mac (xgem doctor ios explains more); Linux desktop needs a Linux host.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function cmdFlutter(script) {
|
|
89
|
+
switch (script) {
|
|
90
|
+
case 'hard-clean': return cmdHardClean();
|
|
91
|
+
case 'build': return cmdBuild();
|
|
92
|
+
case 'build-runner': return cmdBuildRunner();
|
|
93
|
+
default: die(`No native flutter command for '${script}'.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { cmdFlutter };
|
package/lib-win/git.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// xgem Windows engine git workflow — port of lib/git.sh (cmt/init/rm-remote/rm-branch).
|
|
2
|
+
// git.exe behaves the same on Windows, so this is a straight port; the
|
|
3
|
+
// commit-exit-code check and origin-preference fix already in lib/git.sh
|
|
4
|
+
// carry over here too.
|
|
5
|
+
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { logInfo, logSuccess, logWarn, logError, die } = require('./logger');
|
|
8
|
+
const { prompt } = require('./utils');
|
|
9
|
+
|
|
10
|
+
function git(args, opts = {}) {
|
|
11
|
+
return spawnSync('git', args, { stdio: 'inherit', ...opts });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function gitCapture(args) {
|
|
15
|
+
const result = spawnSync('git', args, { encoding: 'utf8' });
|
|
16
|
+
return (result.stdout || '').trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function remoteExists(name) {
|
|
20
|
+
return gitCapture(['remote']).split(/\r?\n/).includes(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function cmdCmt(commitMsg) {
|
|
24
|
+
if (!commitMsg) die('Missing commit message!');
|
|
25
|
+
|
|
26
|
+
logInfo('Checking repository status...');
|
|
27
|
+
git(['status', '-s']);
|
|
28
|
+
console.log('');
|
|
29
|
+
|
|
30
|
+
const addChoice = (await prompt('Do you want to add ALL files (a) or INDIVIDUAL files (i)? [a/i]')).toLowerCase();
|
|
31
|
+
if (addChoice === 'a') {
|
|
32
|
+
git(['add', '.']);
|
|
33
|
+
logSuccess('All files staged.');
|
|
34
|
+
} else if (addChoice === 'i') {
|
|
35
|
+
const files = await prompt('Enter specific file paths to add (space separated)');
|
|
36
|
+
const fileList = files.split(/\s+/).filter(Boolean);
|
|
37
|
+
if (fileList.length === 0) die('No files given.');
|
|
38
|
+
git(['add', ...fileList]);
|
|
39
|
+
logSuccess('Selected files staged.');
|
|
40
|
+
} else {
|
|
41
|
+
die('Invalid choice. Operation aborted.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logInfo('Committing changes...');
|
|
45
|
+
const commitResult = git(['commit', '-m', commitMsg]);
|
|
46
|
+
if (commitResult.status !== 0) {
|
|
47
|
+
die('Commit failed. Aborting before pull/push.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const currentBranch = gitCapture(['branch', '--show-current']);
|
|
51
|
+
const remoteName = remoteExists('origin') ? 'origin' : gitCapture(['remote']).split(/\r?\n/)[0];
|
|
52
|
+
|
|
53
|
+
if (!remoteName) {
|
|
54
|
+
logWarn('Local commit dropped cleanly, but no remote is configured — sync was skipped.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logInfo(`Pulling updates from remote '${remoteName}' on '${currentBranch}' via rebase...`);
|
|
59
|
+
const pullResult = git(['pull', '--rebase', remoteName, currentBranch]);
|
|
60
|
+
if (pullResult.status !== 0) {
|
|
61
|
+
logError('MERGE CONFLICT DETECTED!');
|
|
62
|
+
logWarn('Execution paused. Resolve conflicts to proceed.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
logSuccess('Clean sync pull achieved. Pushing to upstream target...');
|
|
67
|
+
const pushResult = git(['push', remoteName, currentBranch]);
|
|
68
|
+
if (pushResult.status === 0) {
|
|
69
|
+
logSuccess('Git workflow complete! Code cleanly committed and synchronized.');
|
|
70
|
+
} else {
|
|
71
|
+
die('Push operation failed.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function cmdInit() {
|
|
76
|
+
logInfo('Initializing local Git repository...');
|
|
77
|
+
git(['init']);
|
|
78
|
+
|
|
79
|
+
let remoteName = 'origin';
|
|
80
|
+
const remoteUrl = await prompt('Enter remote repository URL');
|
|
81
|
+
if (remoteUrl) {
|
|
82
|
+
const userRemoteName = await prompt('Enter remote name (default: origin)');
|
|
83
|
+
if (userRemoteName) remoteName = userRemoteName;
|
|
84
|
+
|
|
85
|
+
if (remoteExists(remoteName)) {
|
|
86
|
+
git(['remote', 'set-url', remoteName, remoteUrl]);
|
|
87
|
+
logSuccess(`Remote '${remoteName}' already existed. URL updated.`);
|
|
88
|
+
} else {
|
|
89
|
+
git(['remote', 'add', remoteName, remoteUrl]);
|
|
90
|
+
logSuccess(`Remote '${remoteName}' successfully added.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const branchName = (await prompt('Enter branch name (default: main)')) || 'main';
|
|
95
|
+
git(['branch', '-M', branchName]);
|
|
96
|
+
|
|
97
|
+
git(['add', '.']);
|
|
98
|
+
git(['commit', '-m', 'initial changes']);
|
|
99
|
+
logSuccess('Local baseline configuration setup completed.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function cmdRmRemote() {
|
|
103
|
+
logInfo('Current configured remotes:');
|
|
104
|
+
git(['remote', '-v']);
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
const remoteName = (await prompt('Enter the short remote name to remove (e.g. origin)')) || 'origin';
|
|
108
|
+
if (remoteExists(remoteName)) {
|
|
109
|
+
git(['remote', 'remove', remoteName]);
|
|
110
|
+
logSuccess(`Successfully removed remote reference configuration: ${remoteName}`);
|
|
111
|
+
} else {
|
|
112
|
+
die(`Remote tracking short-name '${remoteName}' does not exist.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function cmdRmBranch() {
|
|
117
|
+
const targetBranch = await prompt('Enter the name of the branch you want to target');
|
|
118
|
+
if (!targetBranch) die('Branch name cannot be empty.');
|
|
119
|
+
|
|
120
|
+
const whereChoice = (await prompt('Where do you want to delete this branch? (l = local only, r = remote only, b = both) [l/r/b]')).toLowerCase();
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
if (whereChoice === 'l' || whereChoice === 'b') {
|
|
124
|
+
const currentBranch = gitCapture(['branch', '--show-current']);
|
|
125
|
+
if (currentBranch === targetBranch) {
|
|
126
|
+
logWarn(`You are currently sitting on '${targetBranch}'. Switching to safe branch...`);
|
|
127
|
+
for (const fallback of ['main', 'master', 'dev']) {
|
|
128
|
+
if (git(['checkout', fallback], { stdio: 'ignore' }).status === 0) break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
logInfo(`Force deleting local branch '${targetBranch}'...`);
|
|
133
|
+
if (git(['branch', '-D', targetBranch]).status === 0) {
|
|
134
|
+
logSuccess('Successfully deleted local copy of branch.');
|
|
135
|
+
} else {
|
|
136
|
+
logWarn('Local branch could not be dropped (it may already be gone).');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (whereChoice === 'r' || whereChoice === 'b') {
|
|
141
|
+
const remoteTarget = (await prompt("Enter remote identifier (short-name like 'origin')")) || 'origin';
|
|
142
|
+
logInfo(`Sending deletion request for remote branch '${targetBranch}' to server...`);
|
|
143
|
+
if (git(['push', remoteTarget, '--delete', targetBranch]).status === 0) {
|
|
144
|
+
logSuccess(`Successfully wiped out remote branch '${targetBranch}' from server.`);
|
|
145
|
+
} else {
|
|
146
|
+
die('Server rejected the branch drop execution request.');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function cmdGit(sub, arg) {
|
|
152
|
+
switch (sub) {
|
|
153
|
+
case 'cmt': return cmdCmt(arg);
|
|
154
|
+
case 'init': return cmdInit();
|
|
155
|
+
case 'rm-remote': return cmdRmRemote();
|
|
156
|
+
case 'rm-branch': return cmdRmBranch();
|
|
157
|
+
default: die(`Unknown git subcommand '${sub}'. Usage: xgem git <cmt|init|rm-remote|rm-branch>`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { cmdGit };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// xgem Windows engine logger — mirrors lib/logger.sh's levels/colors.
|
|
2
|
+
// Manual ANSI codes (no dependency): Windows 10+ cmd.exe and PowerShell
|
|
3
|
+
// both support ANSI escapes natively, and we skip color entirely when
|
|
4
|
+
// stdout isn't a TTY (e.g. piped output).
|
|
5
|
+
|
|
6
|
+
const isTTY = process.stdout.isTTY === true;
|
|
7
|
+
|
|
8
|
+
const colors = {
|
|
9
|
+
red: '\x1b[31m',
|
|
10
|
+
green: '\x1b[32m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
12
|
+
blue: '\x1b[1;34m',
|
|
13
|
+
cyan: '\x1b[1;36m',
|
|
14
|
+
gray: '\x1b[90m',
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function paint(color, text) {
|
|
19
|
+
return isTTY ? `${colors[color]}${text}${colors.reset}` : text;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const VERBOSE = process.env.XGEM_VERBOSE === '1';
|
|
23
|
+
|
|
24
|
+
function logInfo(msg) { console.log(`${paint('blue', '[INFO]')} ${msg}`); }
|
|
25
|
+
function logSuccess(msg) { console.log(`${paint('green', '[ OK ]')} ${msg}`); }
|
|
26
|
+
function logWarn(msg) { console.error(`${paint('yellow', '[WARN]')} ${msg}`); }
|
|
27
|
+
function logError(msg) { console.error(`${paint('red', '[FAIL]')} ${msg}`); }
|
|
28
|
+
function logDebug(msg) { if (VERBOSE) console.error(`${paint('gray', `[DBG ] ${msg}`)}`); }
|
|
29
|
+
|
|
30
|
+
function die(msg, code = 1) {
|
|
31
|
+
logError(msg);
|
|
32
|
+
process.exit(code);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { paint, logInfo, logSuccess, logWarn, logError, logDebug, die, isTTY };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// xgem Windows engine generic scaffold — mirrors lib/scaffold.sh, but
|
|
2
|
+
// writes/runs .mjs templates instead of .sh. `swift` is intentionally
|
|
3
|
+
// omitted: there's no meaningful Windows Swift toolchain story.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
const { logInfo, logSuccess, logWarn, logError, die } = require('./logger');
|
|
9
|
+
|
|
10
|
+
const ALL_FRAMEWORKS = ['flutter', 'node', 'python', 'react', 'vue', 'angular', 'go', 'rust', 'docker'];
|
|
11
|
+
|
|
12
|
+
const FRAMEWORK_SCRIPTS = {
|
|
13
|
+
flutter: ['hard-clean', 'build', 'build-runner'],
|
|
14
|
+
node: ['hard-clean', 'build', 'start'],
|
|
15
|
+
python: ['hard-clean', 'install'],
|
|
16
|
+
react: ['hard-clean', 'build', 'dev'],
|
|
17
|
+
vue: ['hard-clean', 'build', 'dev'],
|
|
18
|
+
angular: ['hard-clean', 'build', 'dev'],
|
|
19
|
+
go: ['hard-clean', 'build'],
|
|
20
|
+
rust: ['hard-clean', 'build'],
|
|
21
|
+
docker: ['hard-clean', 'build-up'],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function frameworkScripts(fw) {
|
|
25
|
+
return FRAMEWORK_SCRIPTS[fw] || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function templateDir(fw) {
|
|
29
|
+
const templatesRoot = path.join(__dirname, '..', 'templates-win');
|
|
30
|
+
if (fw === 'react' || fw === 'vue' || fw === 'angular') {
|
|
31
|
+
return path.join(templatesRoot, 'webframework');
|
|
32
|
+
}
|
|
33
|
+
return path.join(templatesRoot, fw);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function injectTemplates(fw, configDir) {
|
|
37
|
+
const scripts = frameworkScripts(fw);
|
|
38
|
+
if (!scripts) die(`Unknown framework '${fw}'.`);
|
|
39
|
+
|
|
40
|
+
const srcDir = templateDir(fw);
|
|
41
|
+
const destDir = path.join(configDir, fw);
|
|
42
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
43
|
+
|
|
44
|
+
for (const script of scripts) {
|
|
45
|
+
const src = path.join(srcDir, `${script}.mjs.tmpl`);
|
|
46
|
+
const dest = path.join(destDir, `${script}.mjs`);
|
|
47
|
+
if (!fs.existsSync(src)) {
|
|
48
|
+
logWarn(`Missing template ${src}, skipping.`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const content = fs.readFileSync(src, 'utf8').replace(/__FRAMEWORK__/g, fw);
|
|
52
|
+
fs.writeFileSync(dest, content);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runScript(fw, script, configDir) {
|
|
57
|
+
const target = path.join(configDir, fw, `${script}.mjs`);
|
|
58
|
+
if (fs.existsSync(target)) {
|
|
59
|
+
logInfo(`Running script '${script}' for ${fw}...`);
|
|
60
|
+
const result = spawnSync(process.execPath, [target], { stdio: 'inherit' });
|
|
61
|
+
process.exitCode = result.status ?? 0;
|
|
62
|
+
} else {
|
|
63
|
+
logError(`Script not found at ${target}`);
|
|
64
|
+
const fwDir = path.join(configDir, fw);
|
|
65
|
+
if (fs.existsSync(fwDir)) {
|
|
66
|
+
console.log(`Available scripts in '${fw}':`);
|
|
67
|
+
for (const f of fs.readdirSync(fwDir)) {
|
|
68
|
+
console.log(` - ${f.replace(/\.mjs$/, '')}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getRemainingFrameworks(configDir) {
|
|
76
|
+
return ALL_FRAMEWORKS.filter((fw) => !fs.existsSync(path.join(configDir, fw)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { ALL_FRAMEWORKS, frameworkScripts, injectTemplates, runScript, getRemainingFrameworks };
|
package/lib-win/utils.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// xgem Windows engine utilities — mirrors lib/utils.sh.
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const readline = require('node:readline');
|
|
5
|
+
const { logInfo, logDebug } = require('./logger');
|
|
6
|
+
|
|
7
|
+
function detectArch() {
|
|
8
|
+
return process.arch === 'x64' ? 'x64' : process.arch;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// hasCmd(name) -> boolean, checked via `where` (a Windows builtin) so it
|
|
12
|
+
// works regardless of whether the tool itself supports --version.
|
|
13
|
+
function hasCmd(name) {
|
|
14
|
+
const result = spawnSync('where', [name], { stdio: 'ignore', shell: false });
|
|
15
|
+
return result.status === 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function requireCmd(name, hint) {
|
|
19
|
+
if (!hasCmd(name)) {
|
|
20
|
+
const { die } = require('./logger');
|
|
21
|
+
die(hint ? `'${name}' is required but not found. ${hint}` : `'${name}' is required but not found in PATH.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// getVersion(name, args) -> first line of stdout, or null if the command
|
|
26
|
+
// isn't present / errors out.
|
|
27
|
+
function getVersion(name, args = ['--version']) {
|
|
28
|
+
if (!hasCmd(name)) return null;
|
|
29
|
+
const result = spawnSync(name, args, { encoding: 'utf8' });
|
|
30
|
+
if (result.error || result.status !== 0) return null;
|
|
31
|
+
const out = (result.stdout || result.stderr || '').split(/\r?\n/)[0];
|
|
32
|
+
return out ? out.trim() : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// confirm(prompt) -> Promise<boolean>. Honors XGEM_YES (auto-approve) and
|
|
36
|
+
// XGEM_DRY_RUN (always decline, just like lib/utils.sh's confirm()).
|
|
37
|
+
function confirm(prompt) {
|
|
38
|
+
if (process.env.XGEM_DRY_RUN === '1') {
|
|
39
|
+
logInfo(`(dry-run) would prompt: ${prompt}`);
|
|
40
|
+
return Promise.resolve(false);
|
|
41
|
+
}
|
|
42
|
+
if (process.env.XGEM_YES === '1') {
|
|
43
|
+
logDebug(`auto-confirmed (--yes): ${prompt}`);
|
|
44
|
+
return Promise.resolve(true);
|
|
45
|
+
}
|
|
46
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
rl.question(`${prompt} [y/N]: `, (answer) => {
|
|
49
|
+
rl.close();
|
|
50
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// prompt(question, defaultValue) -> Promise<string>
|
|
56
|
+
function prompt(question, defaultValue = '') {
|
|
57
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
58
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
61
|
+
rl.close();
|
|
62
|
+
resolve(answer.trim() || defaultValue);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { detectArch, hasCmd, requireCmd, getVersion, confirm, prompt };
|
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xgem-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
4
|
-
"description": "Framework-aware automation CLI: scaffolds and runs clean/build/dev scripts per project type, an environment doctor, a SwiftPM-aware iOS build engine, and a git workflow helper.",
|
|
3
|
+
"version": "2.0.0-alpha.2",
|
|
4
|
+
"description": "Framework-aware automation CLI: scaffolds and runs clean/build/dev scripts per project type, an environment doctor, a SwiftPM-aware iOS build engine, and a git workflow helper. Runs natively on macOS, Linux, and Windows.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"xgem": "bin/xgem"
|
|
6
|
+
"xgem": "bin/xgem.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/xgem",
|
|
10
|
+
"bin/xgem.js",
|
|
10
11
|
"lib",
|
|
12
|
+
"lib-win",
|
|
11
13
|
"templates",
|
|
12
|
-
"
|
|
14
|
+
"templates-win"
|
|
13
15
|
],
|
|
14
16
|
"engines": {
|
|
15
|
-
"node": ">=
|
|
17
|
+
"node": ">=18"
|
|
16
18
|
},
|
|
17
19
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"test": "bash -n bin/xgem && for f in lib/*.sh; do bash -n \"$f\"; done"
|
|
20
|
+
"test": "bash -n bin/xgem && for f in lib/*.sh; do bash -n \"$f\"; done && for f in bin/xgem.js lib-win/*.js; do node --check \"$f\"; done"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"cli",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Delegates to xgem's native Windows flutter engine — single source of
|
|
3
|
+
// truth instead of a second copy of the logic.
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
const r = spawnSync('xgem', ['run', 'flutter', 'hard-clean'], { stdio: 'inherit', shell: true });
|
|
6
|
+
process.exit(r.status ?? 0);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { rmSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
console.log('Nuking node_modules and resetting package locks...');
|
|
6
|
+
rmSync('node_modules', { recursive: true, force: true });
|
|
7
|
+
rmSync('package-lock.json', { force: true });
|
|
8
|
+
rmSync('yarn.lock', { force: true });
|
|
9
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { rmSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
console.log('Cleaning Python cache and rebuilding environment...');
|
|
6
|
+
rmSync('__pycache__', { recursive: true, force: true });
|
|
7
|
+
rmSync('.venv', { recursive: true, force: true });
|
|
8
|
+
execSync('python -m venv .venv', { stdio: 'inherit' });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
// Windows venvs don't have an "activate && run" equivalent worth using in
|
|
6
|
+
// a spawned process — call the venv's own pip.exe directly instead.
|
|
7
|
+
if (existsSync('.venv\\Scripts\\pip.exe')) {
|
|
8
|
+
execSync('.venv\\Scripts\\pip.exe install -r requirements.txt', { stdio: 'inherit' });
|
|
9
|
+
} else {
|
|
10
|
+
console.error('Error: Virtual environment (.venv) not found.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { rmSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
console.log('Resetting __FRAMEWORK__ dependencies...');
|
|
6
|
+
rmSync('node_modules', { recursive: true, force: true });
|
|
7
|
+
rmSync('package-lock.json', { force: true });
|
|
8
|
+
rmSync('yarn.lock', { force: true });
|
|
9
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Runs as npm's preinstall step. xgem is a bash CLI (osascript/xcodebuild
|
|
3
|
-
// for the iOS engine, sh-isms throughout) with no Windows-native equivalent,
|
|
4
|
-
// so this exists purely to fail with an actionable message instead of npm's
|
|
5
|
-
// generic EBADPLATFORM error.
|
|
6
|
-
|
|
7
|
-
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
8
|
-
console.error('');
|
|
9
|
-
console.error('\x1b[31mxgem does not run natively on Windows.\x1b[0m');
|
|
10
|
-
console.error("It's a bash CLI, and its Flutter iOS build engine shells out to Xcode tools that only exist on macOS.");
|
|
11
|
-
console.error('');
|
|
12
|
-
console.error('\x1b[36mTo use xgem on Windows, install WSL2 first:\x1b[0m');
|
|
13
|
-
console.error(' https://learn.microsoft.com/windows/wsl/install');
|
|
14
|
-
console.error('Then run this same install command again from inside your WSL terminal (it reports itself as Linux).');
|
|
15
|
-
console.error('');
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|