@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 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,23 +164,30 @@ 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')
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 packageJsonPath = 'package-gm.json';
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 packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
95
- const dependencies = packageData.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 -e', { cwd: modulePath, stdio: 'inherit' });
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
- program.command('run')
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 packageJsonPath = 'package-gm.json';
134
- const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
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
- program.command('generate')
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 envPath = path.dirname(envFile).replaceAll("\\", "/");
216
- const envMap = parseEnvFile(envFile);
217
- if (!envMap.has("GMS_PROJECT_PATH")) {
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
- if (!envMap.has("GMS_PROJECT_NAME")) {
223
- console.error(`GMS_PROJECT_NAME was not defined in ${envFile}`)
224
- return
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
- program.command('make')
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: '$GMS_RUNTIME',
252
- target: '$GMS_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: '$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("\\", "/"),
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 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"
284
378
  }
285
379
 
286
380
  function log_error {
287
381
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
288
- 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"
289
383
  }
290
384
 
291
385
  gm_cli_env_path=""
292
386
  dir=$(realpath "$PWD")
293
387
  while [ "$dir" != "/" ]; do
294
- if [ -f "$dir/gm-cli.env" ]; then
295
- 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"
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
- 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"
306
407
  if [ -z "$igor_path" ]; then
307
- log_error "GMS_IGOR_PATH must be defined! exit 1"
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=$GMS_PROJECT_NAME
413
+ project_name=${config.name}
312
414
  if [ -z "$project_name" ]; then
313
- log_error "GMS_PROJECT_NAME must be defined! exit 1"
415
+ log_error "package-gm.json name field must be defined! exit 1"
314
416
  exit 1
315
417
  fi
316
418
 
317
- 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}
318
426
  if [ -z "$project_path" ]; then
319
- log_error "GMS_PROJECT_PATH must be defined! exit 1"
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=$GMS_USER_PATH
432
+ user_path=$GM_CLI_USER_PATH
327
433
  if [ -z "$user_path" ]; then
328
- log_error "GMS_USER_PATH must be defined! exit 1"
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 "GMS_RUNTIME must be defined! exit 1"
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 "GMS_TARGET must be defined! exit 1"
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\}/$\{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"
371
464
  $igor_path \
372
465
  --runtimePath="$runtime_path" \
373
466
  --runtime=$runtime \
374
- --project="$\{project_path\}/$\{project_name\}.yyp" \
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\}/$\{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"
382
475
  $igor_path \
383
- --project="$\{project_path\}/$\{project_name\}.yyp" \
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
- 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.3",
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": {},