@ovipakla/gm-cli 1.0.4 → 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 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 watch
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 { program } from 'commander';
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
- program.version('26.02.14', '-v, --version, ', 'output the current version');
12
- program.command('init')
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 = "1.0.0";
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 propertyGamemaker = await askQuestion('gamemaker project path: ');
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
- package: propertyPackage === null || propertyPackage === '' ? basename : propertyPackage,
39
- version: propertyVersion === null || propertyVersion === '' ? version : propertyVersion,
131
+ name: propertyPackage === null || propertyPackage === ''
132
+ ? basename
133
+ : propertyPackage,
134
+ version: propertyVersion === null || propertyVersion === ''
135
+ ? version
136
+ : propertyVersion,
40
137
  description: propertyDescription,
41
- main: propertyGamemaker,
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,17 +164,23 @@ program.command('init')
65
164
  process.exit(0);
66
165
  }
67
166
  });
68
- program.command('watch')
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
- program.command('sync')
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
- program.command('install')
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')
81
186
  .option('-s, --shallow', 'git clone will use depth=1 branch=REVISION')
@@ -83,7 +188,6 @@ program.command('install')
83
188
  const options = this.opts();
84
189
  const clean = options.clean !== undefined;
85
190
  const shallow = options.shallow !== undefined;
86
- const packageJsonPath = 'package-gm.json';
87
191
  const modulesDir = 'gm_modules';
88
192
 
89
193
  if (!fs.existsSync(modulesDir)) {
@@ -93,8 +197,8 @@ program.command('install')
93
197
  fs.mkdirSync(modulesDir);
94
198
  }
95
199
 
96
- const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
97
- const dependencies = packageData.dependencies;
200
+ const packageGM = getPackageGM()
201
+ const dependencies = packageGM.data.dependencies;
98
202
  Object.entries(dependencies).forEach(([key, dependency]) => {
99
203
  console.log(`\n📦️ Install ${key}\n===========${"=".repeat(key.length)}`)
100
204
  const modulePath = path.join(modulesDir, key);
@@ -123,7 +227,9 @@ program.command('install')
123
227
  console.log('\n\n✅ All dependencies processed.');
124
228
  process.exit(0);
125
229
  })
126
- program.command('run')
230
+
231
+ program
232
+ .command('run')
127
233
  .description('Run the script named <foo>')
128
234
  .argument('<foo>', 'script name')
129
235
  .action((foo) => {
@@ -133,9 +239,8 @@ program.command('run')
133
239
  return process.exit(1);
134
240
  }
135
241
 
136
- const packageJsonPath = 'package-gm.json';
137
- const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
138
- const scriptData = packageData.scripts[foo]
242
+ const packageGM = getPackageGM()
243
+ const scriptData = packageGM.data.scripts[foo]
139
244
  if (typeof scriptData !== 'string') {
140
245
  console.log(`script ${foo} wasn't found`);
141
246
  console.log(`Exited with code 1`);
@@ -143,6 +248,8 @@ program.command('run')
143
248
  }
144
249
 
145
250
  const shellScript = `#!/bin/bash
251
+ cd ${path.dirname(packageGM.file).replaceAll("\\", "/")}
252
+
146
253
  ${scriptData}
147
254
  `;
148
255
  const bashProcess = spawn("bash", ["-s"], { stdio: ["pipe", "inherit", "inherit"] });
@@ -153,7 +260,9 @@ program.command('run')
153
260
  process.exit(code);
154
261
  });
155
262
  });
156
- program.command('generate')
263
+
264
+ program
265
+ .command('generate')
157
266
  .description('Generate *.yyp IncludedFiles section')
158
267
  .action(function() {
159
268
  function getFilesRecursively(dir, root) {
@@ -171,25 +280,6 @@ program.command('generate')
171
280
  return files;
172
281
  }
173
282
 
174
- function findFileUpwardsSync(filename = "gm-cli.env", maxLevels = 99) {
175
- let currentDir = process.cwd();
176
- for (let i = 0; i < maxLevels; i++) {
177
- const candidate = path.join(currentDir, filename);
178
- if (fs.existsSync(candidate)) {
179
- return candidate;
180
- }
181
-
182
- const parentDir = path.dirname(currentDir);
183
- if (parentDir === currentDir) {
184
- break;
185
- }
186
-
187
- currentDir = parentDir;
188
- }
189
-
190
- return null;
191
- }
192
-
193
283
  function parseEnvFile(filePath) {
194
284
  const content = fs.readFileSync(filePath, "utf8");
195
285
  const result = new Map();
@@ -209,28 +299,24 @@ program.command('generate')
209
299
  return result;
210
300
  }
211
301
 
212
- const envFile = findFileUpwardsSync();
302
+ const envFile = findFileUpwardsSync(".gm-cli.env");
213
303
  if (envFile === null) {
214
- console.error('gm-cli.env was not found')
304
+ console.error('.gm-cli.env was not found')
215
305
  return
216
306
  }
217
307
 
218
- const envPath = path.dirname(envFile).replaceAll("\\", "/");
219
- const envMap = parseEnvFile(envFile);
220
- if (!envMap.has("GMS_PROJECT_PATH")) {
221
- console.error(`GMS_PROJECT_PATH was not defined in ${envFile}`)
222
- return
308
+ const packageGM = getPackageGM()
309
+ if (packageGM === null) {
310
+ return null
223
311
  }
224
312
 
225
- if (!envMap.has("GMS_PROJECT_NAME")) {
226
- console.error(`GMS_PROJECT_NAME was not defined in ${envFile}`)
227
- return
228
- }
229
-
230
- const projectPath = path.join(envPath, envMap.get("GMS_PROJECT_PATH")).replaceAll("\\", "/");
231
- const yypPath = path.join(projectPath, `${envMap.get("GMS_PROJECT_NAME")}.yyp`).replaceAll("\\", "/");
232
- 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`
233
318
  const yyp = fs.readFileSync(yypPath, "utf8");
319
+ console.log(`📝 Backup yyp:`, yypOldPath);
234
320
  fs.copyFileSync(yypPath, yypOldPath);
235
321
 
236
322
  const datafilesPath = path.join(projectPath, "datafiles").replaceAll("\\", "/")
@@ -238,9 +324,12 @@ program.command('generate')
238
324
  const replaced = yyp.replace(/"IncludedFiles"\s*:\s*\[(.*?)\]/s, `"IncludedFiles":[
239
325
  ${datafiles.join("\n ")}
240
326
  ]`);
327
+ console.log(`📝 Save yyp:`, yypPath);
241
328
  fs.writeFileSync(yypPath, replaced, "utf8");
242
329
  });
243
- program.command('make')
330
+
331
+ program
332
+ .command('make')
244
333
  .description('Build and run gamemaker project')
245
334
  .option('-t, --target <target>', 'available targets: windows')
246
335
  .option('-r, --runtime <type>', 'use VM or YYC runtime')
@@ -248,15 +337,17 @@ program.command('make')
248
337
  .option('-l, --launch', 'launch the executable after building')
249
338
  .option('-c, --clean', 'make clean build')
250
339
  .action(function() {
340
+ const packageGM = getPackageGM()
251
341
  const targetMap = new Map([ [ 'windows', 'win' ] ])
252
342
  const options = this.opts();
253
343
  const config = {
254
- runtime: '$GMS_RUNTIME',
255
- target: '$GMS_TARGET',
256
- targetExt: 'win',
344
+ runtime: '$GM_CLI_DEFAULT_RUNTIME',
345
+ target: '$GM_CLI_DEFAULT_TARGET',
257
346
  clean: 'false',
258
347
  launch: 'PackageZip',
259
- name: '$GMS_PROJECT_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("\\", "/"),
260
351
  };
261
352
 
262
353
  if (options.runtime !== undefined) {
@@ -283,19 +374,19 @@ program.command('make')
283
374
  const shellScript = `#!/bin/bash
284
375
  function log_info {
285
376
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
286
- echo -e "$timestamp INFO [gm-cli::run] $1"
377
+ echo -e "\\\e[90m$timestamp\\\e[0m \\\e[32mINFO\\\e[0m \\\e[35m[gm-cli::make]\\\e[0m $1"
287
378
  }
288
379
 
289
380
  function log_error {
290
381
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
291
- echo -e "$timestamp ERROR [gm-cli::run] $1"
382
+ echo -e "\\\e[90m$timestamp\\\e[0m \\\e[31mERROR\\\e[0m \\\e[35m[gm-cli::make]\\\e[0m $1"
292
383
  }
293
384
 
294
385
  gm_cli_env_path=""
295
386
  dir=$(realpath "$PWD")
296
387
  while [ "$dir" != "/" ]; do
297
- if [ -f "$dir/gm-cli.env" ]; then
298
- gm_cli_env_path="$dir/gm-cli.env"
388
+ if [ -f "$dir/.gm-cli.env" ]; then
389
+ gm_cli_env_path="$dir/.gm-cli.env"
299
390
  log_info "Load configuration '$gm_cli_env_path'"
300
391
  set -a
301
392
  . "$gm_cli_env_path"
@@ -305,56 +396,55 @@ program.command('make')
305
396
  dir=$(dirname "$dir")
306
397
  done
307
398
 
308
- igor_path=$GMS_IGOR_PATH
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"
309
407
  if [ -z "$igor_path" ]; then
310
- log_error "GMS_IGOR_PATH must be defined! exit 1"
408
+ log_error "GM_CLI_RUNTIME_PATH must be defined! exit 1"
311
409
  exit 1
312
410
  fi
411
+ igor_path=$(realpath $igor_path)
313
412
 
314
- project_name=$GMS_PROJECT_NAME
413
+ project_name=${config.name}
315
414
  if [ -z "$project_name" ]; then
316
- log_error "GMS_PROJECT_NAME must be defined! exit 1"
415
+ log_error "package-gm.json name field must be defined! exit 1"
317
416
  exit 1
318
417
  fi
319
418
 
320
- project_path=$GMS_PROJECT_PATH
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}
321
426
  if [ -z "$project_path" ]; then
322
- log_error "GMS_PROJECT_PATH must be defined! exit 1"
427
+ log_error "package-gm.json yyp field must be defined! exit 1"
323
428
  exit 1
324
429
  fi
325
-
326
- project_path=$(dirname "$gm_cli_env_path")/$project_path
327
430
  project_path=$(realpath $project_path)
328
431
 
329
- user_path=$GMS_USER_PATH
432
+ user_path=$GM_CLI_USER_PATH
330
433
  if [ -z "$user_path" ]; then
331
- log_error "GMS_USER_PATH must be defined! exit 1"
434
+ log_error "GM_CLI_USER_PATH must be defined! exit 1"
332
435
  exit 1
333
436
  fi
334
437
  user_path=$(realpath $user_path)
335
438
 
336
- runtime_path=$GMS_RUNTIME_PATH
337
- if [ -z "$runtime_path" ]; then
338
- log_error "GMS_RUNTIME_PATH must be defined! exit 1"
339
- exit 1
340
- fi
341
- runtime_path=$(realpath $runtime_path)
342
-
343
439
  runtime=${config.runtime}
344
440
  if [ -z "$runtime" ]; then
345
- log_error "GMS_RUNTIME must be defined! exit 1"
441
+ log_error "GM_CLI_DEFAULT_RUNTIME must be defined! exit 1"
346
442
  exit 1
347
443
  fi
348
444
 
349
445
  target=${config.target}
350
446
  if [ -z "$target" ]; then
351
- log_error "GMS_TARGET must be defined! exit 1"
352
- exit 1
353
- fi
354
-
355
- target_ext=${config.targetExt}
356
- if [ -z "$target_ext" ]; then
357
- log_error "GMS_TARGET_EXT must be defined! exit 1"
447
+ log_error "GM_CLI_DEFAULT_TARGET must be defined! exit 1"
358
448
  exit 1
359
449
  fi
360
450
 
@@ -370,20 +460,20 @@ program.command('make')
370
460
  log_info "Clean '$project_path/tmp/igor'"
371
461
  rm -rf $project_path/tmp/igor
372
462
 
373
- log_info "Execute shell command:\n$igor_path \\ \n --runtimePath="$runtime_path" \\ \n --runtime=$runtime \\ \n --project="$\{project_path\}/$\{project_name\}.yyp" \\ \n -- $target Clean\n"
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"
374
464
  $igor_path \
375
465
  --runtimePath="$runtime_path" \
376
466
  --runtime=$runtime \
377
- --project="$\{project_path\}/$\{project_name\}.yyp" \
378
- -- $target Clean
467
+ --project="$\{project_path\}/$\{project_yyp\}" \
468
+ -- $target Clean | GREP_COLORS='mt=01;31' grep --color=always -E 'Error : |$'
379
469
  fi
380
470
 
381
471
  log_info "Clean '$\{project_path\}/tmp/igor/out'"
382
472
  rm -rf $\{project_path\}/tmp/igor/out
383
473
 
384
- log_info "Execute shell command:\n$igor_path \\ \n --project="$\{project_path\}/$\{project_name\}.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}"
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"
385
475
  $igor_path \
386
- --project="$\{project_path\}/$\{project_name\}.yyp" \
476
+ --project="$\{project_path\}/$\{project_yyp\}" \
387
477
  --user="$user_path" \
388
478
  --runtimePath="$runtime_path" \
389
479
  --runtime=$runtime \
@@ -391,7 +481,7 @@ program.command('make')
391
481
  --temp="$\{project_path\}/tmp/igor/temp" \
392
482
  --of="$\{project_path\}/tmp/igor/out/$\{project_name\}.win" \
393
483
  --tf="$\{zip_name\}.zip" \
394
- -- $target ${config.launch};
484
+ -- $target ${config.launch} | GREP_COLORS='mt=01;31' grep --color=always -E 'Error : |$'
395
485
 
396
486
  exit 0
397
487
  `;
@@ -404,4 +494,270 @@ program.command('make')
404
494
  process.exit(code);
405
495
  });
406
496
  });
407
- program.parse();
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);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.4",
6
+ "version": "2.0.0",
7
7
  "description": "Gamemaker CLI toolkit. Watch &amp; sync gml sources with yyp project.",
8
8
  "main": "app.js",
9
9
  "scripts": {},