@ovipakla/gm-cli 1.0.3 → 2.0.0
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/GMFileWatcher.js +1 -1
- package/README.md +2 -41
- package/app.js +458 -99
- package/package.json +1 -1
package/GMFileWatcher.js
CHANGED
|
@@ -83,7 +83,7 @@ function syncWatcherHook() {
|
|
|
83
83
|
* --------------------------------------------------------- */
|
|
84
84
|
class GMFileWatcher {
|
|
85
85
|
constructor(gmPackage, modulesDir, watch = false) {
|
|
86
|
-
this.gmPath = resolvePath(gmPackage.main);
|
|
86
|
+
this.gmPath = path.dirname(resolvePath(gmPackage.main));
|
|
87
87
|
this.modulesDirName = modulesDir;
|
|
88
88
|
this.modulesDirPath = resolvePath(modulesDir);
|
|
89
89
|
this.timestamp = "";
|
package/README.md
CHANGED
|
@@ -7,49 +7,10 @@ Gamemaker CLI toolkit. Watch & sync gml sources with yyp project.
|
|
|
7
7
|
|
|
8
8
|
# Install
|
|
9
9
|
```bash
|
|
10
|
-
npm install
|
|
10
|
+
npm install -g
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
# Usage
|
|
14
14
|
```bash
|
|
15
|
-
gm-cli
|
|
15
|
+
gm-cli
|
|
16
16
|
```
|
|
17
|
-
|
|
18
|
-
# Project structure
|
|
19
|
-
```
|
|
20
|
-
- gm_modules:
|
|
21
|
-
- core: git repo with gml `*.gml` files
|
|
22
|
-
- visu: git repo with gml `*.gml` files
|
|
23
|
-
- yyp:
|
|
24
|
-
- datafiles: directory created by gamemaker
|
|
25
|
-
- extensions: directory created by gamemaker
|
|
26
|
-
- fonts: directory created by gamemaker
|
|
27
|
-
- objects: directory created by gamemaker
|
|
28
|
-
- options: directory created by gamemaker
|
|
29
|
-
- rooms: directory created by gamemaker
|
|
30
|
-
- scripts: directory created by gamemaker
|
|
31
|
-
- shaders: directory created by gamemaker
|
|
32
|
-
- sounds: directory created by gamemaker
|
|
33
|
-
- sprites: directory created by gamemaker
|
|
34
|
-
- game.resource_order: file created by gamemaker
|
|
35
|
-
- game.yyp: file created by gamemaker
|
|
36
|
-
- package-gm.json: Equivalent of `npm` "package.json"
|
|
37
|
-
```
|
|
38
|
-
Content of `package-gm.json`:
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"name": "visu",
|
|
42
|
-
"version": "1.0.0",
|
|
43
|
-
"description": "Visu",
|
|
44
|
-
"main": "yyp",
|
|
45
|
-
"author": "Alkapivo",
|
|
46
|
-
"license": "ISC",
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"core": "^1.0.0",
|
|
49
|
-
"visu": "^1.0.0"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
Note:
|
|
54
|
-
- `main` is a relative directory path, where `*.yyp` file (gamemaker studio 2.3 project).
|
|
55
|
-
- `dependencies` - keys should match names in `gm_modules` folder
|
package/app.js
CHANGED
|
@@ -2,14 +2,107 @@
|
|
|
2
2
|
|
|
3
3
|
import { watch, sync } from './GMFileWatcher.js';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { Command } from 'commander';
|
|
6
6
|
import { spawn, execSync } from 'child_process';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import readline from 'readline';
|
|
9
9
|
|
|
10
|
+
function findFileUpwardsSync(filename, maxLevels = 99) {
|
|
11
|
+
let currentDir = process.cwd();
|
|
12
|
+
for (let i = 0; i < maxLevels; i++) {
|
|
13
|
+
const candidate = path.join(currentDir, filename);
|
|
14
|
+
if (fs.existsSync(candidate)) {
|
|
15
|
+
return candidate;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parentDir = path.dirname(currentDir);
|
|
19
|
+
if (parentDir === currentDir) {
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
currentDir = parentDir;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getPackageGM() {
|
|
30
|
+
const file = findFileUpwardsSync("package-gm.json");
|
|
31
|
+
if (file === null) {
|
|
32
|
+
throw new Error('❌ package-gm.json was not found')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
file: file,
|
|
37
|
+
data: JSON.parse(fs.readFileSync(file, "utf8")),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function backupPackageGM(packageGM) {
|
|
42
|
+
console.log(`📝 Backup package-gm.json: ${packageGM.file}.old`);
|
|
43
|
+
fs.copyFileSync(packageGM.file, `${packageGM.file}.old`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function savePackageGM(packageGM) {
|
|
47
|
+
fs.writeFileSync(packageGM.file, JSON.stringify(packageGM.data, null, 2), "utf8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clamp(value, min, max) {
|
|
51
|
+
return Math.min(Math.max(value, min), max);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getPSMonitorRAMCommand(processName, interval, name) {
|
|
55
|
+
const psCommand = `
|
|
56
|
+
\$processName = "${processName}"
|
|
57
|
+
\$interval = ${interval}
|
|
58
|
+
\$reportFile = "${name}"
|
|
59
|
+
\$timestamp = Get-Date -Format "yyyy-MM-dd_hh-mm"
|
|
60
|
+
if (\$reportFile -eq "") {
|
|
61
|
+
\$reportFile = "\$processName_\$timestamp-ram-report.csv"
|
|
62
|
+
}
|
|
10
63
|
|
|
11
|
-
|
|
12
|
-
|
|
64
|
+
if (-not (Test-Path \$reportFile)) {
|
|
65
|
+
"date,RAM_MB" | Out-File -FilePath \$reportFile -Encoding UTF8
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
echo "Monitoring RAM usage..."
|
|
69
|
+
|
|
70
|
+
while (\$true) {
|
|
71
|
+
\$date = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
72
|
+
try {
|
|
73
|
+
\$process = Get-Process -Name \$processName -ErrorAction Stop
|
|
74
|
+
\$ramMB = [math]::Round(\$process.WorkingSet64 / 1MB, 2)
|
|
75
|
+
"\$date,\$ramMB" | Out-File -Append -Encoding utf8 -FilePath \$reportFile
|
|
76
|
+
echo "\$date TEST [${processName}::monitor-ram]: \$ramMB"
|
|
77
|
+
} catch {
|
|
78
|
+
#"\$date,PROCESS_NOT_RUNNING" | Out-File -Append -Encoding utf8 -FilePath \$reportFile
|
|
79
|
+
echo "\$date TEST [${processName}::monitor-ram]: PROCESS_NOT_RUNNING"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Start-Sleep -Milliseconds \$interval
|
|
83
|
+
}
|
|
84
|
+
`
|
|
85
|
+
|
|
86
|
+
return psCommand
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const program = new Command()
|
|
90
|
+
.version('2.0.0', '-v, --version, ', 'output the current version');
|
|
91
|
+
|
|
92
|
+
const config = program
|
|
93
|
+
.command('config')
|
|
94
|
+
.description('Manage configuration');
|
|
95
|
+
|
|
96
|
+
const configSet = config
|
|
97
|
+
.command('set')
|
|
98
|
+
.description('Set config values');
|
|
99
|
+
|
|
100
|
+
const configUnset = config
|
|
101
|
+
.command('unset')
|
|
102
|
+
.description('Unset config values');
|
|
103
|
+
|
|
104
|
+
program
|
|
105
|
+
.command('init')
|
|
13
106
|
.description('CLI creator for package-gm.json')
|
|
14
107
|
.action(async () => {
|
|
15
108
|
const rl = readline.createInterface({
|
|
@@ -24,28 +117,34 @@ program.command('init')
|
|
|
24
117
|
|
|
25
118
|
const projectPath = process.cwd();
|
|
26
119
|
const basename = path.basename(projectPath);
|
|
27
|
-
const version = "
|
|
120
|
+
const version = "0.0.1";
|
|
28
121
|
const propertyPackage = await askQuestion(`package name: (${basename}) `);
|
|
29
122
|
const propertyVersion = await askQuestion(`version: (${version}) `);
|
|
30
123
|
const propertyDescription = await askQuestion('description: ');
|
|
31
|
-
const
|
|
124
|
+
const propertyYYP = await askQuestion('gamemaker project file (.yyp): ');
|
|
32
125
|
const propertyTest = await askQuestion('test command: ');
|
|
33
126
|
const propertyGit = await askQuestion('git repository: ');
|
|
34
127
|
const propertyKeywords = await askQuestion('keywords: ');
|
|
35
128
|
const propertyAuthor = await askQuestion('author: ');
|
|
36
129
|
const propertyLicense = await askQuestion('license: (ISC) ');
|
|
37
130
|
const data = {
|
|
38
|
-
|
|
39
|
-
|
|
131
|
+
name: propertyPackage === null || propertyPackage === ''
|
|
132
|
+
? basename
|
|
133
|
+
: propertyPackage,
|
|
134
|
+
version: propertyVersion === null || propertyVersion === ''
|
|
135
|
+
? version
|
|
136
|
+
: propertyVersion,
|
|
40
137
|
description: propertyDescription,
|
|
41
|
-
main:
|
|
42
|
-
test: propertyTest,
|
|
138
|
+
main: propertyYYP,
|
|
43
139
|
git: propertyGit,
|
|
44
140
|
keywords: propertyKeywords,
|
|
45
141
|
author: propertyAuthor,
|
|
46
142
|
license: propertyLicense,
|
|
47
|
-
scripts: {
|
|
143
|
+
scripts: {
|
|
144
|
+
test: propertyTest
|
|
145
|
+
},
|
|
48
146
|
dependencies: {},
|
|
147
|
+
runtimes: {},
|
|
49
148
|
};
|
|
50
149
|
|
|
51
150
|
const filePath = path.join(projectPath, 'package-gm.json');
|
|
@@ -65,23 +164,30 @@ program.command('init')
|
|
|
65
164
|
process.exit(0);
|
|
66
165
|
}
|
|
67
166
|
});
|
|
68
|
-
|
|
167
|
+
|
|
168
|
+
program
|
|
169
|
+
.command('watch')
|
|
69
170
|
.description('Watch modules dir and copy code to gamemaker project')
|
|
70
171
|
.action(() => {
|
|
71
172
|
watch(path.normalize(path.join(process.cwd(), 'package-gm.json')))
|
|
72
173
|
});
|
|
73
|
-
|
|
174
|
+
|
|
175
|
+
program
|
|
176
|
+
.command('sync')
|
|
74
177
|
.description('Copy code from modules dir to gamemaker project')
|
|
75
178
|
.action(() => {
|
|
76
179
|
sync(path.normalize(path.join(process.cwd(), 'package-gm.json')))
|
|
77
180
|
});
|
|
78
|
-
|
|
181
|
+
|
|
182
|
+
program
|
|
183
|
+
.command('install')
|
|
79
184
|
.description('Install dependencies listed in package-gm.json to gm_modules folder')
|
|
80
185
|
.option('-c, --clean', 'remove existing gm_modules')
|
|
186
|
+
.option('-s, --shallow', 'git clone will use depth=1 branch=REVISION')
|
|
81
187
|
.action(function() {
|
|
82
188
|
const options = this.opts();
|
|
83
189
|
const clean = options.clean !== undefined;
|
|
84
|
-
const
|
|
190
|
+
const shallow = options.shallow !== undefined;
|
|
85
191
|
const modulesDir = 'gm_modules';
|
|
86
192
|
|
|
87
193
|
if (!fs.existsSync(modulesDir)) {
|
|
@@ -91,28 +197,29 @@ program.command('install')
|
|
|
91
197
|
fs.mkdirSync(modulesDir);
|
|
92
198
|
}
|
|
93
199
|
|
|
94
|
-
const
|
|
95
|
-
const dependencies =
|
|
200
|
+
const packageGM = getPackageGM()
|
|
201
|
+
const dependencies = packageGM.data.dependencies;
|
|
96
202
|
Object.entries(dependencies).forEach(([key, dependency]) => {
|
|
97
203
|
console.log(`\n📦️ Install ${key}\n===========${"=".repeat(key.length)}`)
|
|
98
204
|
const modulePath = path.join(modulesDir, key);
|
|
205
|
+
const cloneOptions = shallow ? `--depth 1 --branch ${dependency.revision}` : ''
|
|
99
206
|
if (fs.existsSync(modulePath)) {
|
|
100
207
|
try {
|
|
101
208
|
execSync('git rev-parse --is-inside-work-tree', { cwd: modulePath, stdio: 'ignore' });
|
|
102
209
|
console.log(`🌐 Syncing ${modulePath} to revision ${dependency.revision}`);
|
|
103
210
|
execSync('git reset --hard HEAD', { cwd: modulePath, stdio: 'inherit' });
|
|
104
|
-
execSync('git clean -fdx
|
|
211
|
+
execSync('git clean -fdx', { cwd: modulePath, stdio: 'inherit' });
|
|
105
212
|
execSync(`git checkout ${dependency.revision}`, { cwd: modulePath, stdio: 'inherit' });
|
|
106
213
|
} catch (error) {
|
|
107
214
|
console.log(`🗑️ Removing ${modulePath} because it's not a git repository`);
|
|
108
215
|
fs.rmSync(modulePath, { recursive: true, force: true });
|
|
109
216
|
console.log(`🔧 Initializing ${modulePath} to revision ${dependency.revision}`);
|
|
110
|
-
execSync(`git clone ${dependency.remote} ${modulePath}`, { stdio: 'inherit' });
|
|
217
|
+
execSync(`git clone ${cloneOptions} ${dependency.remote} ${modulePath}`, { stdio: 'inherit' });
|
|
111
218
|
execSync(`git checkout ${dependency.revision}`, { cwd: modulePath, stdio: 'inherit' });
|
|
112
219
|
}
|
|
113
220
|
} else {
|
|
114
221
|
console.log(`🔧 Initializing ${modulePath} to revision ${dependency.revision}`);
|
|
115
|
-
execSync(`git clone ${dependency.remote} ${modulePath}`, { stdio: 'inherit' });
|
|
222
|
+
execSync(`git clone ${cloneOptions} ${dependency.remote} ${modulePath}`, { stdio: 'inherit' });
|
|
116
223
|
execSync(`git checkout ${dependency.revision}`, { cwd: modulePath, stdio: 'inherit' });
|
|
117
224
|
}
|
|
118
225
|
});
|
|
@@ -120,7 +227,9 @@ program.command('install')
|
|
|
120
227
|
console.log('\n\n✅ All dependencies processed.');
|
|
121
228
|
process.exit(0);
|
|
122
229
|
})
|
|
123
|
-
|
|
230
|
+
|
|
231
|
+
program
|
|
232
|
+
.command('run')
|
|
124
233
|
.description('Run the script named <foo>')
|
|
125
234
|
.argument('<foo>', 'script name')
|
|
126
235
|
.action((foo) => {
|
|
@@ -130,9 +239,8 @@ program.command('run')
|
|
|
130
239
|
return process.exit(1);
|
|
131
240
|
}
|
|
132
241
|
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const scriptData = packageData.scripts[foo]
|
|
242
|
+
const packageGM = getPackageGM()
|
|
243
|
+
const scriptData = packageGM.data.scripts[foo]
|
|
136
244
|
if (typeof scriptData !== 'string') {
|
|
137
245
|
console.log(`script ${foo} wasn't found`);
|
|
138
246
|
console.log(`Exited with code 1`);
|
|
@@ -140,6 +248,8 @@ program.command('run')
|
|
|
140
248
|
}
|
|
141
249
|
|
|
142
250
|
const shellScript = `#!/bin/bash
|
|
251
|
+
cd ${path.dirname(packageGM.file).replaceAll("\\", "/")}
|
|
252
|
+
|
|
143
253
|
${scriptData}
|
|
144
254
|
`;
|
|
145
255
|
const bashProcess = spawn("bash", ["-s"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
@@ -150,7 +260,9 @@ program.command('run')
|
|
|
150
260
|
process.exit(code);
|
|
151
261
|
});
|
|
152
262
|
});
|
|
153
|
-
|
|
263
|
+
|
|
264
|
+
program
|
|
265
|
+
.command('generate')
|
|
154
266
|
.description('Generate *.yyp IncludedFiles section')
|
|
155
267
|
.action(function() {
|
|
156
268
|
function getFilesRecursively(dir, root) {
|
|
@@ -168,25 +280,6 @@ program.command('generate')
|
|
|
168
280
|
return files;
|
|
169
281
|
}
|
|
170
282
|
|
|
171
|
-
function findFileUpwardsSync(filename = "gm-cli.env", maxLevels = 99) {
|
|
172
|
-
let currentDir = process.cwd();
|
|
173
|
-
for (let i = 0; i < maxLevels; i++) {
|
|
174
|
-
const candidate = path.join(currentDir, filename);
|
|
175
|
-
if (fs.existsSync(candidate)) {
|
|
176
|
-
return candidate;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const parentDir = path.dirname(currentDir);
|
|
180
|
-
if (parentDir === currentDir) {
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
currentDir = parentDir;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
283
|
function parseEnvFile(filePath) {
|
|
191
284
|
const content = fs.readFileSync(filePath, "utf8");
|
|
192
285
|
const result = new Map();
|
|
@@ -206,28 +299,24 @@ program.command('generate')
|
|
|
206
299
|
return result;
|
|
207
300
|
}
|
|
208
301
|
|
|
209
|
-
const envFile = findFileUpwardsSync();
|
|
302
|
+
const envFile = findFileUpwardsSync(".gm-cli.env");
|
|
210
303
|
if (envFile === null) {
|
|
211
|
-
console.error('gm-cli.env was not found')
|
|
304
|
+
console.error('.gm-cli.env was not found')
|
|
212
305
|
return
|
|
213
306
|
}
|
|
214
307
|
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
console.error(`GMS_PROJECT_PATH was not defined in ${envFile}`)
|
|
219
|
-
return
|
|
308
|
+
const packageGM = getPackageGM()
|
|
309
|
+
if (packageGM === null) {
|
|
310
|
+
return null
|
|
220
311
|
}
|
|
221
312
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const projectPath = path.join(envPath, envMap.get("GMS_PROJECT_PATH")).replaceAll("\\", "/");
|
|
228
|
-
const yypPath = path.join(projectPath, `${envMap.get("GMS_PROJECT_NAME")}.yyp`).replaceAll("\\", "/");
|
|
229
|
-
const yypOldPath = path.join(projectPath, `${envMap.get("GMS_PROJECT_NAME")}.yyp.old`).replaceAll("\\", "/");
|
|
313
|
+
const envPath = path.dirname(envFile).replaceAll("\\", "/");
|
|
314
|
+
const envMap = parseEnvFile(envFile);
|
|
315
|
+
const projectPath = path.dirname(path.join(path.dirname(packageGM.file), packageGM.data.main.replaceAll("\\", "/")));
|
|
316
|
+
const yypPath = path.join(path.dirname(packageGM.file), packageGM.data.main.replaceAll("\\", "/"));
|
|
317
|
+
const yypOldPath = `${yypPath}.old`
|
|
230
318
|
const yyp = fs.readFileSync(yypPath, "utf8");
|
|
319
|
+
console.log(`📝 Backup yyp:`, yypOldPath);
|
|
231
320
|
fs.copyFileSync(yypPath, yypOldPath);
|
|
232
321
|
|
|
233
322
|
const datafilesPath = path.join(projectPath, "datafiles").replaceAll("\\", "/")
|
|
@@ -235,9 +324,12 @@ program.command('generate')
|
|
|
235
324
|
const replaced = yyp.replace(/"IncludedFiles"\s*:\s*\[(.*?)\]/s, `"IncludedFiles":[
|
|
236
325
|
${datafiles.join("\n ")}
|
|
237
326
|
]`);
|
|
327
|
+
console.log(`📝 Save yyp:`, yypPath);
|
|
238
328
|
fs.writeFileSync(yypPath, replaced, "utf8");
|
|
239
329
|
});
|
|
240
|
-
|
|
330
|
+
|
|
331
|
+
program
|
|
332
|
+
.command('make')
|
|
241
333
|
.description('Build and run gamemaker project')
|
|
242
334
|
.option('-t, --target <target>', 'available targets: windows')
|
|
243
335
|
.option('-r, --runtime <type>', 'use VM or YYC runtime')
|
|
@@ -245,15 +337,17 @@ program.command('make')
|
|
|
245
337
|
.option('-l, --launch', 'launch the executable after building')
|
|
246
338
|
.option('-c, --clean', 'make clean build')
|
|
247
339
|
.action(function() {
|
|
340
|
+
const packageGM = getPackageGM()
|
|
248
341
|
const targetMap = new Map([ [ 'windows', 'win' ] ])
|
|
249
342
|
const options = this.opts();
|
|
250
343
|
const config = {
|
|
251
|
-
runtime: '$
|
|
252
|
-
target: '$
|
|
253
|
-
targetExt: 'win',
|
|
344
|
+
runtime: '$GM_CLI_DEFAULT_RUNTIME',
|
|
345
|
+
target: '$GM_CLI_DEFAULT_TARGET',
|
|
254
346
|
clean: 'false',
|
|
255
347
|
launch: 'PackageZip',
|
|
256
|
-
name:
|
|
348
|
+
name: packageGM.data.name,
|
|
349
|
+
yyp: path.basename(packageGM.data.main).replaceAll("\\", "/"),
|
|
350
|
+
path: path.join(path.dirname(packageGM.file), path.dirname(packageGM.data.main)).replaceAll("\\", "/"),
|
|
257
351
|
};
|
|
258
352
|
|
|
259
353
|
if (options.runtime !== undefined) {
|
|
@@ -280,19 +374,19 @@ program.command('make')
|
|
|
280
374
|
const shellScript = `#!/bin/bash
|
|
281
375
|
function log_info {
|
|
282
376
|
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
283
|
-
echo -e "$timestamp
|
|
377
|
+
echo -e "\\\e[90m$timestamp\\\e[0m \\\e[32mINFO\\\e[0m \\\e[35m[gm-cli::make]\\\e[0m $1"
|
|
284
378
|
}
|
|
285
379
|
|
|
286
380
|
function log_error {
|
|
287
381
|
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
288
|
-
echo -e "$timestamp
|
|
382
|
+
echo -e "\\\e[90m$timestamp\\\e[0m \\\e[31mERROR\\\e[0m \\\e[35m[gm-cli::make]\\\e[0m $1"
|
|
289
383
|
}
|
|
290
384
|
|
|
291
385
|
gm_cli_env_path=""
|
|
292
386
|
dir=$(realpath "$PWD")
|
|
293
387
|
while [ "$dir" != "/" ]; do
|
|
294
|
-
if [ -f "$dir
|
|
295
|
-
gm_cli_env_path="$dir
|
|
388
|
+
if [ -f "$dir/.gm-cli.env" ]; then
|
|
389
|
+
gm_cli_env_path="$dir/.gm-cli.env"
|
|
296
390
|
log_info "Load configuration '$gm_cli_env_path'"
|
|
297
391
|
set -a
|
|
298
392
|
. "$gm_cli_env_path"
|
|
@@ -302,56 +396,55 @@ program.command('make')
|
|
|
302
396
|
dir=$(dirname "$dir")
|
|
303
397
|
done
|
|
304
398
|
|
|
305
|
-
|
|
399
|
+
runtime_path=$GM_CLI_RUNTIME_PATH
|
|
400
|
+
if [ -z "$runtime_path" ]; then
|
|
401
|
+
log_error "GM_CLI_RUNTIME_PATH must be defined! exit 1"
|
|
402
|
+
exit 1
|
|
403
|
+
fi
|
|
404
|
+
runtime_path=$(realpath $runtime_path)
|
|
405
|
+
|
|
406
|
+
igor_path="$\{GM_CLI_RUNTIME_PATH%/\}/bin/igor/windows/x64/Igor.exe"
|
|
306
407
|
if [ -z "$igor_path" ]; then
|
|
307
|
-
log_error "
|
|
408
|
+
log_error "GM_CLI_RUNTIME_PATH must be defined! exit 1"
|
|
308
409
|
exit 1
|
|
309
410
|
fi
|
|
411
|
+
igor_path=$(realpath $igor_path)
|
|
310
412
|
|
|
311
|
-
project_name=$
|
|
413
|
+
project_name=${config.name}
|
|
312
414
|
if [ -z "$project_name" ]; then
|
|
313
|
-
log_error "
|
|
415
|
+
log_error "package-gm.json name field must be defined! exit 1"
|
|
314
416
|
exit 1
|
|
315
417
|
fi
|
|
316
418
|
|
|
317
|
-
|
|
419
|
+
project_yyp=${config.yyp}
|
|
420
|
+
if [ -z "$project_yyp" ]; then
|
|
421
|
+
log_error "package-gm.json yyp field must be defined! exit 1"
|
|
422
|
+
exit 1
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
project_path=${config.path}
|
|
318
426
|
if [ -z "$project_path" ]; then
|
|
319
|
-
log_error "
|
|
427
|
+
log_error "package-gm.json yyp field must be defined! exit 1"
|
|
320
428
|
exit 1
|
|
321
429
|
fi
|
|
322
|
-
|
|
323
|
-
project_path=$(dirname "$gm_cli_env_path")/$project_path
|
|
324
430
|
project_path=$(realpath $project_path)
|
|
325
431
|
|
|
326
|
-
user_path=$
|
|
432
|
+
user_path=$GM_CLI_USER_PATH
|
|
327
433
|
if [ -z "$user_path" ]; then
|
|
328
|
-
log_error "
|
|
434
|
+
log_error "GM_CLI_USER_PATH must be defined! exit 1"
|
|
329
435
|
exit 1
|
|
330
436
|
fi
|
|
331
437
|
user_path=$(realpath $user_path)
|
|
332
438
|
|
|
333
|
-
runtime_path=$GMS_RUNTIME_PATH
|
|
334
|
-
if [ -z "$runtime_path" ]; then
|
|
335
|
-
log_error "GMS_RUNTIME_PATH must be defined! exit 1"
|
|
336
|
-
exit 1
|
|
337
|
-
fi
|
|
338
|
-
runtime_path=$(realpath $runtime_path)
|
|
339
|
-
|
|
340
439
|
runtime=${config.runtime}
|
|
341
440
|
if [ -z "$runtime" ]; then
|
|
342
|
-
log_error "
|
|
441
|
+
log_error "GM_CLI_DEFAULT_RUNTIME must be defined! exit 1"
|
|
343
442
|
exit 1
|
|
344
443
|
fi
|
|
345
444
|
|
|
346
445
|
target=${config.target}
|
|
347
446
|
if [ -z "$target" ]; then
|
|
348
|
-
log_error "
|
|
349
|
-
exit 1
|
|
350
|
-
fi
|
|
351
|
-
|
|
352
|
-
target_ext=${config.targetExt}
|
|
353
|
-
if [ -z "$target_ext" ]; then
|
|
354
|
-
log_error "GMS_TARGET_EXT must be defined! exit 1"
|
|
447
|
+
log_error "GM_CLI_DEFAULT_TARGET must be defined! exit 1"
|
|
355
448
|
exit 1
|
|
356
449
|
fi
|
|
357
450
|
|
|
@@ -367,20 +460,20 @@ program.command('make')
|
|
|
367
460
|
log_info "Clean '$project_path/tmp/igor'"
|
|
368
461
|
rm -rf $project_path/tmp/igor
|
|
369
462
|
|
|
370
|
-
log_info "Execute shell command:\n$igor_path \\ \n --runtimePath="$runtime_path" \\ \n --runtime=$runtime \\ \n --project="$\{project_path\}/$\{
|
|
463
|
+
log_info "Execute shell command:\n\\\e[33m$igor_path \\ \n --runtimePath="$runtime_path" \\ \n --runtime=$runtime \\ \n --project="$\{project_path\}/$\{project_yyp\}" \\ \n -- $target Clean\n\\\e[0m"
|
|
371
464
|
$igor_path \
|
|
372
465
|
--runtimePath="$runtime_path" \
|
|
373
466
|
--runtime=$runtime \
|
|
374
|
-
--project="$\{project_path\}/$\{
|
|
375
|
-
-- $target Clean
|
|
467
|
+
--project="$\{project_path\}/$\{project_yyp\}" \
|
|
468
|
+
-- $target Clean | GREP_COLORS='mt=01;31' grep --color=always -E 'Error : |$'
|
|
376
469
|
fi
|
|
377
470
|
|
|
378
471
|
log_info "Clean '$\{project_path\}/tmp/igor/out'"
|
|
379
472
|
rm -rf $\{project_path\}/tmp/igor/out
|
|
380
473
|
|
|
381
|
-
log_info "Execute shell command:\n$igor_path \\ \n --project="$\{project_path\}/$\{
|
|
474
|
+
log_info "Execute shell command:\n\\\e[33m$igor_path \\ \n --project="$\{project_path\}/$\{project_yyp\}" \\ \n --user="$user_path" \\ \n --runtimePath="$runtime_path" \\ \n --runtime=$runtime \\ \n --cache="$\{project_path\}/tmp/igor/cache" \\ \n --temp="$\{project_path\}/tmp/igor/temp" \\ \n --of="$\{project_path\}/tmp/igor/out/$\{project_name\}.win" \\ \n --tf="$\{zip_name\}.zip" \\ \n -- $target ${config.launch}\\\e[0m"
|
|
382
475
|
$igor_path \
|
|
383
|
-
--project="$\{project_path\}/$\{
|
|
476
|
+
--project="$\{project_path\}/$\{project_yyp\}" \
|
|
384
477
|
--user="$user_path" \
|
|
385
478
|
--runtimePath="$runtime_path" \
|
|
386
479
|
--runtime=$runtime \
|
|
@@ -388,7 +481,7 @@ program.command('make')
|
|
|
388
481
|
--temp="$\{project_path\}/tmp/igor/temp" \
|
|
389
482
|
--of="$\{project_path\}/tmp/igor/out/$\{project_name\}.win" \
|
|
390
483
|
--tf="$\{zip_name\}.zip" \
|
|
391
|
-
-- $target ${config.launch};
|
|
484
|
+
-- $target ${config.launch} | GREP_COLORS='mt=01;31' grep --color=always -E 'Error : |$'
|
|
392
485
|
|
|
393
486
|
exit 0
|
|
394
487
|
`;
|
|
@@ -401,4 +494,270 @@ program.command('make')
|
|
|
401
494
|
process.exit(code);
|
|
402
495
|
});
|
|
403
496
|
});
|
|
404
|
-
|
|
497
|
+
|
|
498
|
+
program
|
|
499
|
+
.command('env')
|
|
500
|
+
.description('CLI creator for .gm-cli.env')
|
|
501
|
+
.action(async () => {
|
|
502
|
+
const rl = readline.createInterface({
|
|
503
|
+
input: process.stdin,
|
|
504
|
+
output: process.stdout
|
|
505
|
+
});
|
|
506
|
+
const askQuestion = (query) => new Promise(resolve => rl.question(query, resolve));
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
console.log("This utility will walk you through creating a .gm-cli.env file.");
|
|
510
|
+
|
|
511
|
+
const runtimes = [ "VM", "YYC" ]
|
|
512
|
+
const targets = [ "windows" ]
|
|
513
|
+
|
|
514
|
+
const projectPath = process.cwd();
|
|
515
|
+
const propertyDefaultRuntime = await askQuestion('Default runtime [ VM, YYC ]: ');
|
|
516
|
+
const propertyDefaultTarget = await askQuestion('Default target [ windows ]: ');
|
|
517
|
+
const propertyRuntimePath = await askQuestion('Path to gamemaker runtime: ');
|
|
518
|
+
const propertyUserPath = await askQuestion('Path to gamemaker user: ');
|
|
519
|
+
const data = {
|
|
520
|
+
GM_CLI_DEFAULT_RUNTIME: runtimes.includes(propertyDefaultRuntime) ? propertyDefaultRuntime : runtimes[0],
|
|
521
|
+
GM_CLI_DEFAULT_TARGET: runtimes.includes(propertyDefaultTarget) ? propertyDefaultTarget : targets[0],
|
|
522
|
+
GM_CLI_RUNTIME_PATH: path.normalize(propertyRuntimePath),
|
|
523
|
+
GM_CLI_USER_PATH: path.normalize(propertyUserPath)
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const filePath = path.join(projectPath, '.gm-cli.env');
|
|
527
|
+
const dataString = Object.entries(data)
|
|
528
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
529
|
+
.join("\n");
|
|
530
|
+
|
|
531
|
+
console.log(`About to write to ${filePath}:\n\n${dataString}\n\n`);
|
|
532
|
+
const response = await askQuestion(`Is this OK? (yes) `)
|
|
533
|
+
if (typeof response === 'string' && (response.includes('y') || response.includes('Y'))) {
|
|
534
|
+
fs.writeFileSync(filePath, dataString, 'utf8');
|
|
535
|
+
} else {
|
|
536
|
+
console.log('Aborted.\n');
|
|
537
|
+
}
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error('An error occurred:', error);
|
|
540
|
+
} finally {
|
|
541
|
+
rl.close();
|
|
542
|
+
process.exit(0);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
program
|
|
547
|
+
.command('monitor-ram')
|
|
548
|
+
.description('Monitor RAM usage and save it to csv file')
|
|
549
|
+
.option('-i, --interval <interval>', 'step value in seconds (default = 15)')
|
|
550
|
+
.option('-n, --name <name>', 'name of binary')
|
|
551
|
+
.option('-r, --report <report>', 'name of CSV report file')
|
|
552
|
+
.action((options) => {
|
|
553
|
+
const packageGM = getPackageGM()
|
|
554
|
+
const interval = clamp((Number.isNaN(Number(options.interval)) ? 15.0 : Number(options.interval)), 1.0 / 60.0, 999.0)
|
|
555
|
+
const name = options.name === undefined ? packageGM.data.name : options.name
|
|
556
|
+
const report = options.report === undefined ? '' : options.report
|
|
557
|
+
const psCommand = getPSMonitorRAMCommand(name, 1000.0 * interval, report)
|
|
558
|
+
const ps = spawn('powershell.exe', [
|
|
559
|
+
'-NoProfile',
|
|
560
|
+
'-Command',
|
|
561
|
+
psCommand
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
ps.stdout.on('data', data => {
|
|
565
|
+
console.log(data.toString().replace(/\r?\n$/, ''));
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
ps.stderr.on('data', data => {
|
|
569
|
+
console.error(data.toString().replace(/\r?\n$/, ''));
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
ps.on('close', code => {
|
|
573
|
+
console.log(`Exited with code ${code}`);
|
|
574
|
+
});
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
program
|
|
578
|
+
.command('test')
|
|
579
|
+
.description('Run tests')
|
|
580
|
+
.option('-t, --tests <tests>', 'List of paths to json test cases')
|
|
581
|
+
.option('-b, --build <build>', 'Path to executable')
|
|
582
|
+
.option('-m, --monitorRAM', 'Monitor RAM while testing')
|
|
583
|
+
.action((options) => {
|
|
584
|
+
const packageGM = getPackageGM()
|
|
585
|
+
|
|
586
|
+
const shellMonitorRAMScript = options.monitorRAM === undefined ? `` : `
|
|
587
|
+
set -m
|
|
588
|
+
gm-cli monitor-ram --name \$\{EXE_FILE%.exe\} &
|
|
589
|
+
pid=\$!
|
|
590
|
+
trap "kill -- -\$pid 2>/dev/null" EXIT INT TERM
|
|
591
|
+
`;
|
|
592
|
+
|
|
593
|
+
const shellBuildScript = options.build !== undefined ? `
|
|
594
|
+
cd ${path.dirname(options.build).replaceAll("\\", "/")}
|
|
595
|
+
build_name="\$\{PWD##*/\}"
|
|
596
|
+
|
|
597
|
+
EXE_FILE="${path.basename(options.build)}"
|
|
598
|
+
EXE_COUNT=1
|
|
599
|
+
` : `
|
|
600
|
+
build_name="${packageGM.data.name}_test"
|
|
601
|
+
rm -rf \$build_name.zip
|
|
602
|
+
rm -rf \$build_name
|
|
603
|
+
gm-cli make --name \$build_name
|
|
604
|
+
unzip \$build_name.zip -d \$build_name
|
|
605
|
+
cd \$build_name
|
|
606
|
+
|
|
607
|
+
EXE_FILE=\$(find . -maxdepth 1 -type f -name "*.exe" -printf "%f\n" 2>/dev/null)
|
|
608
|
+
EXE_COUNT=\$(printf "%s\n" "\$EXE_FILE" | grep -c .)
|
|
609
|
+
`;
|
|
610
|
+
|
|
611
|
+
const shellTestScript = options.tests === undefined ? `
|
|
612
|
+
TESTS=\$(find . -type f -name "*test.json" -print0 | xargs -0 echo | sed 's/ /, /g')
|
|
613
|
+
` : `
|
|
614
|
+
TESTS=\"${options.tests}\"
|
|
615
|
+
`
|
|
616
|
+
|
|
617
|
+
const shellCommandScript = `
|
|
618
|
+
TIMESTAMP=\$(date +"%Y-%m-%d_%H-%M")
|
|
619
|
+
OUTPUT_FILE="\$\{TIMESTAMP\}_\$\{EXE_FILE%.exe\}_run-test.log"
|
|
620
|
+
COMMAND="./\$EXE_FILE -output \"\$OUTPUT_FILE\" --tests \\\"\$TESTS\\\""
|
|
621
|
+
|
|
622
|
+
if [ "\$EXE_COUNT" -eq 0 ]; then
|
|
623
|
+
echo "ERROR 1: binary was not found"
|
|
624
|
+
exit 1
|
|
625
|
+
elif [ "\$EXE_COUNT" -gt 1 ]; then
|
|
626
|
+
echo "ERROR 2: found more executables: \$EXE_FILE"
|
|
627
|
+
exit 2
|
|
628
|
+
fi
|
|
629
|
+
`;
|
|
630
|
+
|
|
631
|
+
const shellScript = `#!/bin/bash
|
|
632
|
+
cd ${path.dirname(packageGM.file).replaceAll("\\", "/")}
|
|
633
|
+
|
|
634
|
+
${shellBuildScript}
|
|
635
|
+
|
|
636
|
+
${shellTestScript}
|
|
637
|
+
|
|
638
|
+
${shellCommandScript}
|
|
639
|
+
|
|
640
|
+
${shellMonitorRAMScript}
|
|
641
|
+
|
|
642
|
+
eval "\$COMMAND" | cat
|
|
643
|
+
`
|
|
644
|
+
|
|
645
|
+
const bashProcess = spawn("bash", ["-s"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
646
|
+
bashProcess.stdin.write(shellScript);
|
|
647
|
+
bashProcess.stdin.end();
|
|
648
|
+
bashProcess.on("exit", (code) => {
|
|
649
|
+
console.log(`Exited with code ${code}`);
|
|
650
|
+
process.exit(code);
|
|
651
|
+
});
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
configSet
|
|
655
|
+
.command('dependency <name> <revision>')
|
|
656
|
+
.description('Manage dependencies in package-gm.json')
|
|
657
|
+
.option('--remote <remote>')
|
|
658
|
+
.action((name, revision, options) => {
|
|
659
|
+
const resolve = () => {
|
|
660
|
+
const current = packageGM.data.dependencies[name] ?? {};
|
|
661
|
+
|
|
662
|
+
packageGM.data.dependencies[name] = {
|
|
663
|
+
...current,
|
|
664
|
+
...(options.remote !== undefined && { remote: options.remote }),
|
|
665
|
+
...(revision !== undefined && { revision }),
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
console.log("🔨 Set dependency", name, "as", packageGM.data.dependencies[name])
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const packageGM = getPackageGM()
|
|
672
|
+
|
|
673
|
+
backupPackageGM(packageGM)
|
|
674
|
+
resolve()
|
|
675
|
+
savePackageGM(packageGM)
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
configSet
|
|
679
|
+
.command('script <name> [command]')
|
|
680
|
+
.description('Manage scripts in package-gm.json')
|
|
681
|
+
.action((name, command = '') => {
|
|
682
|
+
const resolve = () => {
|
|
683
|
+
packageGM.data.scripts[name] = command !== undefined ? command : ''
|
|
684
|
+
console.log("🔨 Set script", name, "as", packageGM.data.scripts[name])
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const packageGM = getPackageGM()
|
|
688
|
+
|
|
689
|
+
backupPackageGM(packageGM)
|
|
690
|
+
resolve()
|
|
691
|
+
savePackageGM(packageGM)
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
configSet
|
|
695
|
+
.command('runtime <name> [supported]')
|
|
696
|
+
.description('Manage runtimes in package-gm.json')
|
|
697
|
+
.action((name, supported = 'true') => {
|
|
698
|
+
const resolve = () => {
|
|
699
|
+
packageGM.data.runtimes[name] = supported === "false" ? supported : "true"
|
|
700
|
+
console.log("🔨 Set runtime", name, "as", supported === "false" ? "false" : "true")
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const packageGM = getPackageGM()
|
|
704
|
+
|
|
705
|
+
backupPackageGM(packageGM)
|
|
706
|
+
resolve()
|
|
707
|
+
savePackageGM(packageGM)
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
configUnset
|
|
711
|
+
.command('dependency <name>')
|
|
712
|
+
.description('Remove dependencies from package-gm.json')
|
|
713
|
+
.action((name) => {
|
|
714
|
+
const resolve = () => {
|
|
715
|
+
if (name in packageGM.data.dependencies) {
|
|
716
|
+
console.log("🗑️ Unset dependency", name)
|
|
717
|
+
delete packageGM.data.dependencies[name]
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const packageGM = getPackageGM()
|
|
722
|
+
|
|
723
|
+
backupPackageGM(packageGM)
|
|
724
|
+
resolve()
|
|
725
|
+
savePackageGM(packageGM)
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
configUnset
|
|
729
|
+
.command('script <name>')
|
|
730
|
+
.description('Remove scripts from package-gm.json')
|
|
731
|
+
.action((name) => {
|
|
732
|
+
const resolve = () => {
|
|
733
|
+
if (name in packageGM.data.scripts) {
|
|
734
|
+
console.log("🗑️ Unset script", name)
|
|
735
|
+
delete packageGM.data.scripts[name]
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const packageGM = getPackageGM()
|
|
740
|
+
|
|
741
|
+
backupPackageGM(packageGM)
|
|
742
|
+
resolve()
|
|
743
|
+
savePackageGM(packageGM)
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
configUnset
|
|
747
|
+
.command('runtime <name>')
|
|
748
|
+
.description('Remove runtimes from package-gm.json')
|
|
749
|
+
.action((name) => {
|
|
750
|
+
const resolve = () => {
|
|
751
|
+
if (name in packageGM.data.scripts) {
|
|
752
|
+
console.log("🗑️ Unset runtime", name)
|
|
753
|
+
delete packageGM.data.runtimes[name]
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const packageGM = getPackageGM()
|
|
758
|
+
backupPackageGM(packageGM)
|
|
759
|
+
resolve()
|
|
760
|
+
savePackageGM(packageGM)
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
program.parse(process.argv);
|