@ovipakla/gm-cli 1.0.4 → 2.0.1

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.1', '-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,18 @@ 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
+ zip: packageGM.data.name,
350
+ yyp: path.basename(packageGM.data.main).replaceAll("\\", "/"),
351
+ path: path.join(path.dirname(packageGM.file), path.dirname(packageGM.data.main)).replaceAll("\\", "/"),
260
352
  };
261
353
 
262
354
  if (options.runtime !== undefined) {
@@ -277,25 +369,25 @@ program.command('make')
277
369
  }
278
370
 
279
371
  if (options.name !== undefined && typeof options.name === 'string' && options.name.trim() !== '') {
280
- config.name = options.name;
372
+ config.zip = options.name;
281
373
  }
282
374
 
283
375
  const shellScript = `#!/bin/bash
284
376
  function log_info {
285
377
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
286
- echo -e "$timestamp INFO [gm-cli::run] $1"
378
+ echo -e "\\\e[90m$timestamp\\\e[0m \\\e[32mINFO\\\e[0m \\\e[35m[gm-cli::make]\\\e[0m $1"
287
379
  }
288
380
 
289
381
  function log_error {
290
382
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
291
- echo -e "$timestamp ERROR [gm-cli::run] $1"
383
+ echo -e "\\\e[90m$timestamp\\\e[0m \\\e[31mERROR\\\e[0m \\\e[35m[gm-cli::make]\\\e[0m $1"
292
384
  }
293
385
 
294
386
  gm_cli_env_path=""
295
387
  dir=$(realpath "$PWD")
296
388
  while [ "$dir" != "/" ]; do
297
- if [ -f "$dir/gm-cli.env" ]; then
298
- gm_cli_env_path="$dir/gm-cli.env"
389
+ if [ -f "$dir/.gm-cli.env" ]; then
390
+ gm_cli_env_path="$dir/.gm-cli.env"
299
391
  log_info "Load configuration '$gm_cli_env_path'"
300
392
  set -a
301
393
  . "$gm_cli_env_path"
@@ -305,60 +397,59 @@ program.command('make')
305
397
  dir=$(dirname "$dir")
306
398
  done
307
399
 
308
- igor_path=$GMS_IGOR_PATH
400
+ runtime_path=$GM_CLI_RUNTIME_PATH
401
+ if [ -z "$runtime_path" ]; then
402
+ log_error "GM_CLI_RUNTIME_PATH must be defined! exit 1"
403
+ exit 1
404
+ fi
405
+ runtime_path=$(realpath $runtime_path)
406
+
407
+ igor_path="$\{GM_CLI_RUNTIME_PATH%/\}/bin/igor/windows/x64/Igor.exe"
309
408
  if [ -z "$igor_path" ]; then
310
- log_error "GMS_IGOR_PATH must be defined! exit 1"
409
+ log_error "GM_CLI_RUNTIME_PATH must be defined! exit 1"
311
410
  exit 1
312
411
  fi
412
+ igor_path=$(realpath $igor_path)
313
413
 
314
- project_name=$GMS_PROJECT_NAME
414
+ project_name=${config.name}
315
415
  if [ -z "$project_name" ]; then
316
- log_error "GMS_PROJECT_NAME must be defined! exit 1"
416
+ log_error "package-gm.json name field must be defined! exit 1"
317
417
  exit 1
318
418
  fi
319
419
 
320
- project_path=$GMS_PROJECT_PATH
420
+ project_yyp=${config.yyp}
421
+ if [ -z "$project_yyp" ]; then
422
+ log_error "package-gm.json yyp field must be defined! exit 1"
423
+ exit 1
424
+ fi
425
+
426
+ project_path=${config.path}
321
427
  if [ -z "$project_path" ]; then
322
- log_error "GMS_PROJECT_PATH must be defined! exit 1"
428
+ log_error "package-gm.json yyp field must be defined! exit 1"
323
429
  exit 1
324
430
  fi
325
-
326
- project_path=$(dirname "$gm_cli_env_path")/$project_path
327
431
  project_path=$(realpath $project_path)
328
432
 
329
- user_path=$GMS_USER_PATH
433
+ user_path=$GM_CLI_USER_PATH
330
434
  if [ -z "$user_path" ]; then
331
- log_error "GMS_USER_PATH must be defined! exit 1"
435
+ log_error "GM_CLI_USER_PATH must be defined! exit 1"
332
436
  exit 1
333
437
  fi
334
438
  user_path=$(realpath $user_path)
335
439
 
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
440
  runtime=${config.runtime}
344
441
  if [ -z "$runtime" ]; then
345
- log_error "GMS_RUNTIME must be defined! exit 1"
442
+ log_error "GM_CLI_DEFAULT_RUNTIME must be defined! exit 1"
346
443
  exit 1
347
444
  fi
348
445
 
349
446
  target=${config.target}
350
447
  if [ -z "$target" ]; then
351
- log_error "GMS_TARGET must be defined! exit 1"
448
+ log_error "GM_CLI_DEFAULT_TARGET must be defined! exit 1"
352
449
  exit 1
353
450
  fi
354
451
 
355
- target_ext=${config.targetExt}
356
- if [ -z "$target_ext" ]; then
357
- log_error "GMS_TARGET_EXT must be defined! exit 1"
358
- exit 1
359
- fi
360
-
361
- zip_name=${config.name}
452
+ zip_name=${config.zip}
362
453
  echo $zip_name
363
454
  if [ -z "$zip_name" ]; then
364
455
  log_error "--name must be defined! exit 1"
@@ -370,20 +461,20 @@ program.command('make')
370
461
  log_info "Clean '$project_path/tmp/igor'"
371
462
  rm -rf $project_path/tmp/igor
372
463
 
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"
464
+ 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
465
  $igor_path \
375
466
  --runtimePath="$runtime_path" \
376
467
  --runtime=$runtime \
377
- --project="$\{project_path\}/$\{project_name\}.yyp" \
378
- -- $target Clean
468
+ --project="$\{project_path\}/$\{project_yyp\}" \
469
+ -- $target Clean | GREP_COLORS='mt=01;31' grep --color=always -E 'Error : |$'
379
470
  fi
380
471
 
381
472
  log_info "Clean '$\{project_path\}/tmp/igor/out'"
382
473
  rm -rf $\{project_path\}/tmp/igor/out
383
474
 
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}"
475
+ 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
476
  $igor_path \
386
- --project="$\{project_path\}/$\{project_name\}.yyp" \
477
+ --project="$\{project_path\}/$\{project_yyp\}" \
387
478
  --user="$user_path" \
388
479
  --runtimePath="$runtime_path" \
389
480
  --runtime=$runtime \
@@ -391,7 +482,7 @@ program.command('make')
391
482
  --temp="$\{project_path\}/tmp/igor/temp" \
392
483
  --of="$\{project_path\}/tmp/igor/out/$\{project_name\}.win" \
393
484
  --tf="$\{zip_name\}.zip" \
394
- -- $target ${config.launch};
485
+ -- $target ${config.launch} | GREP_COLORS='mt=01;31' grep --color=always -E 'Error : |$'
395
486
 
396
487
  exit 0
397
488
  `;
@@ -404,4 +495,270 @@ program.command('make')
404
495
  process.exit(code);
405
496
  });
406
497
  });
407
- program.parse();
498
+
499
+ program
500
+ .command('env')
501
+ .description('CLI creator for .gm-cli.env')
502
+ .action(async () => {
503
+ const rl = readline.createInterface({
504
+ input: process.stdin,
505
+ output: process.stdout
506
+ });
507
+ const askQuestion = (query) => new Promise(resolve => rl.question(query, resolve));
508
+
509
+ try {
510
+ console.log("This utility will walk you through creating a .gm-cli.env file.");
511
+
512
+ const runtimes = [ "VM", "YYC" ]
513
+ const targets = [ "windows" ]
514
+
515
+ const projectPath = process.cwd();
516
+ const propertyDefaultRuntime = await askQuestion('Default runtime [ VM, YYC ]: ');
517
+ const propertyDefaultTarget = await askQuestion('Default target [ windows ]: ');
518
+ const propertyRuntimePath = await askQuestion('Path to gamemaker runtime: ');
519
+ const propertyUserPath = await askQuestion('Path to gamemaker user: ');
520
+ const data = {
521
+ GM_CLI_DEFAULT_RUNTIME: runtimes.includes(propertyDefaultRuntime) ? propertyDefaultRuntime : runtimes[0],
522
+ GM_CLI_DEFAULT_TARGET: runtimes.includes(propertyDefaultTarget) ? propertyDefaultTarget : targets[0],
523
+ GM_CLI_RUNTIME_PATH: path.normalize(propertyRuntimePath),
524
+ GM_CLI_USER_PATH: path.normalize(propertyUserPath)
525
+ };
526
+
527
+ const filePath = path.join(projectPath, '.gm-cli.env');
528
+ const dataString = Object.entries(data)
529
+ .map(([key, value]) => `${key}="${value}"`)
530
+ .join("\n");
531
+
532
+ console.log(`About to write to ${filePath}:\n\n${dataString}\n\n`);
533
+ const response = await askQuestion(`Is this OK? (yes) `)
534
+ if (typeof response === 'string' && (response.includes('y') || response.includes('Y'))) {
535
+ fs.writeFileSync(filePath, dataString, 'utf8');
536
+ } else {
537
+ console.log('Aborted.\n');
538
+ }
539
+ } catch (error) {
540
+ console.error('An error occurred:', error);
541
+ } finally {
542
+ rl.close();
543
+ process.exit(0);
544
+ }
545
+ });
546
+
547
+ program
548
+ .command('monitor-ram')
549
+ .description('Monitor RAM usage and save it to csv file')
550
+ .option('-i, --interval <interval>', 'step value in seconds (default = 15)')
551
+ .option('-n, --name <name>', 'name of binary')
552
+ .option('-r, --report <report>', 'name of CSV report file')
553
+ .action((options) => {
554
+ const packageGM = getPackageGM()
555
+ const interval = clamp((Number.isNaN(Number(options.interval)) ? 15.0 : Number(options.interval)), 1.0 / 60.0, 999.0)
556
+ const name = options.name === undefined ? packageGM.data.name : options.name
557
+ const report = options.report === undefined ? '' : options.report
558
+ const psCommand = getPSMonitorRAMCommand(name, 1000.0 * interval, report)
559
+ const ps = spawn('powershell.exe', [
560
+ '-NoProfile',
561
+ '-Command',
562
+ psCommand
563
+ ]);
564
+
565
+ ps.stdout.on('data', data => {
566
+ console.log(data.toString().replace(/\r?\n$/, ''));
567
+ });
568
+
569
+ ps.stderr.on('data', data => {
570
+ console.error(data.toString().replace(/\r?\n$/, ''));
571
+ });
572
+
573
+ ps.on('close', code => {
574
+ console.log(`Exited with code ${code}`);
575
+ });
576
+ })
577
+
578
+ program
579
+ .command('test')
580
+ .description('Run tests')
581
+ .option('-t, --tests <tests>', 'List of paths to json test cases')
582
+ .option('-b, --build <build>', 'Path to executable')
583
+ .option('-m, --monitorRAM', 'Monitor RAM while testing')
584
+ .action((options) => {
585
+ const packageGM = getPackageGM()
586
+
587
+ const shellMonitorRAMScript = options.monitorRAM === undefined ? `` : `
588
+ set -m
589
+ gm-cli monitor-ram --name \$\{EXE_FILE%.exe\} &
590
+ pid=\$!
591
+ trap "kill -- -\$pid 2>/dev/null" EXIT INT TERM
592
+ `;
593
+
594
+ const shellBuildScript = options.build !== undefined ? `
595
+ cd ${path.dirname(options.build).replaceAll("\\", "/")}
596
+ build_name="\$\{PWD##*/\}"
597
+
598
+ EXE_FILE="${path.basename(options.build)}"
599
+ EXE_COUNT=1
600
+ ` : `
601
+ build_name="${packageGM.data.name}_test"
602
+ rm -rf \$build_name.zip
603
+ rm -rf \$build_name
604
+ gm-cli make --name \$build_name
605
+ unzip \$build_name.zip -d \$build_name
606
+ cd \$build_name
607
+
608
+ EXE_FILE=\$(find . -maxdepth 1 -type f -name "*.exe" -printf "%f\n" 2>/dev/null)
609
+ EXE_COUNT=\$(printf "%s\n" "\$EXE_FILE" | grep -c .)
610
+ `;
611
+
612
+ const shellTestScript = options.tests === undefined ? `
613
+ TESTS=\$(find . -type f -name "*test.json" -print0 | xargs -0 echo | sed 's/ /, /g')
614
+ ` : `
615
+ TESTS=\"${options.tests}\"
616
+ `
617
+
618
+ const shellCommandScript = `
619
+ TIMESTAMP=\$(date +"%Y-%m-%d_%H-%M")
620
+ OUTPUT_FILE="\$\{TIMESTAMP\}_\$\{EXE_FILE%.exe\}_run-test.log"
621
+ COMMAND="./\$EXE_FILE -output \"\$OUTPUT_FILE\" --tests \\\"\$TESTS\\\""
622
+
623
+ if [ "\$EXE_COUNT" -eq 0 ]; then
624
+ echo "ERROR 1: binary was not found"
625
+ exit 1
626
+ elif [ "\$EXE_COUNT" -gt 1 ]; then
627
+ echo "ERROR 2: found more executables: \$EXE_FILE"
628
+ exit 2
629
+ fi
630
+ `;
631
+
632
+ const shellScript = `#!/bin/bash
633
+ cd ${path.dirname(packageGM.file).replaceAll("\\", "/")}
634
+
635
+ ${shellBuildScript}
636
+
637
+ ${shellTestScript}
638
+
639
+ ${shellCommandScript}
640
+
641
+ ${shellMonitorRAMScript}
642
+
643
+ eval "\$COMMAND" | cat
644
+ `
645
+
646
+ const bashProcess = spawn("bash", ["-s"], { stdio: ["pipe", "inherit", "inherit"] });
647
+ bashProcess.stdin.write(shellScript);
648
+ bashProcess.stdin.end();
649
+ bashProcess.on("exit", (code) => {
650
+ console.log(`Exited with code ${code}`);
651
+ process.exit(code);
652
+ });
653
+ })
654
+
655
+ configSet
656
+ .command('dependency <name> <revision>')
657
+ .description('Manage dependencies in package-gm.json')
658
+ .option('--remote <remote>')
659
+ .action((name, revision, options) => {
660
+ const resolve = () => {
661
+ const current = packageGM.data.dependencies[name] ?? {};
662
+
663
+ packageGM.data.dependencies[name] = {
664
+ ...current,
665
+ ...(options.remote !== undefined && { remote: options.remote }),
666
+ ...(revision !== undefined && { revision }),
667
+ };
668
+
669
+ console.log("🔨 Set dependency", name, "as", packageGM.data.dependencies[name])
670
+ };
671
+
672
+ const packageGM = getPackageGM()
673
+
674
+ backupPackageGM(packageGM)
675
+ resolve()
676
+ savePackageGM(packageGM)
677
+ });
678
+
679
+ configSet
680
+ .command('script <name> [command]')
681
+ .description('Manage scripts in package-gm.json')
682
+ .action((name, command = '') => {
683
+ const resolve = () => {
684
+ packageGM.data.scripts[name] = command !== undefined ? command : ''
685
+ console.log("🔨 Set script", name, "as", packageGM.data.scripts[name])
686
+ }
687
+
688
+ const packageGM = getPackageGM()
689
+
690
+ backupPackageGM(packageGM)
691
+ resolve()
692
+ savePackageGM(packageGM)
693
+ });
694
+
695
+ configSet
696
+ .command('runtime <name> [supported]')
697
+ .description('Manage runtimes in package-gm.json')
698
+ .action((name, supported = 'true') => {
699
+ const resolve = () => {
700
+ packageGM.data.runtimes[name] = supported === "false" ? supported : "true"
701
+ console.log("🔨 Set runtime", name, "as", supported === "false" ? "false" : "true")
702
+ }
703
+
704
+ const packageGM = getPackageGM()
705
+
706
+ backupPackageGM(packageGM)
707
+ resolve()
708
+ savePackageGM(packageGM)
709
+ });
710
+
711
+ configUnset
712
+ .command('dependency <name>')
713
+ .description('Remove dependencies from package-gm.json')
714
+ .action((name) => {
715
+ const resolve = () => {
716
+ if (name in packageGM.data.dependencies) {
717
+ console.log("🗑️ Unset dependency", name)
718
+ delete packageGM.data.dependencies[name]
719
+ }
720
+ }
721
+
722
+ const packageGM = getPackageGM()
723
+
724
+ backupPackageGM(packageGM)
725
+ resolve()
726
+ savePackageGM(packageGM)
727
+ });
728
+
729
+ configUnset
730
+ .command('script <name>')
731
+ .description('Remove scripts from package-gm.json')
732
+ .action((name) => {
733
+ const resolve = () => {
734
+ if (name in packageGM.data.scripts) {
735
+ console.log("🗑️ Unset script", name)
736
+ delete packageGM.data.scripts[name]
737
+ }
738
+ }
739
+
740
+ const packageGM = getPackageGM()
741
+
742
+ backupPackageGM(packageGM)
743
+ resolve()
744
+ savePackageGM(packageGM)
745
+ });
746
+
747
+ configUnset
748
+ .command('runtime <name>')
749
+ .description('Remove runtimes from package-gm.json')
750
+ .action((name) => {
751
+ const resolve = () => {
752
+ if (name in packageGM.data.scripts) {
753
+ console.log("🗑️ Unset runtime", name)
754
+ delete packageGM.data.runtimes[name]
755
+ }
756
+ }
757
+
758
+ const packageGM = getPackageGM()
759
+ backupPackageGM(packageGM)
760
+ resolve()
761
+ savePackageGM(packageGM)
762
+ });
763
+
764
+ 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.1",
7
7
  "description": "Gamemaker CLI toolkit. Watch &amp; sync gml sources with yyp project.",
8
8
  "main": "app.js",
9
9
  "scripts": {},