@ovipakla/gm-cli 1.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/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/GMFileWatcher.js +353 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/app.js +397 -0
- package/package.json +22 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
### Description
|
|
11
|
+
|
|
12
|
+
A clear and concise description of what the bug is.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
### Reproducing steps
|
|
16
|
+
|
|
17
|
+
Steps to reproduce the behavior:
|
|
18
|
+
1. Go to `...`
|
|
19
|
+
2. Click on `...`
|
|
20
|
+
3. See error `...`
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
### Expected behavior
|
|
24
|
+
|
|
25
|
+
A clear and concise description of what you expected to happen.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
### Environment
|
|
29
|
+
|
|
30
|
+
- Version: `vYYMMDD`
|
|
31
|
+
- Target: `win-yyp` | `wasm`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
### Description
|
|
11
|
+
|
|
12
|
+
What is the issue/feature? Why is it needed?
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
### Requirements
|
|
16
|
+
|
|
17
|
+
What is the expected behavior or outcome?
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
### Test case scenario
|
|
21
|
+
|
|
22
|
+
How will it be tested or validated?
|
package/GMFileWatcher.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import childProcess from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import chokidar from 'chokidar';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
|
|
7
|
+
/* ---------------------------------------------------------
|
|
8
|
+
* GMModule
|
|
9
|
+
* --------------------------------------------------------- */
|
|
10
|
+
class GMModule {
|
|
11
|
+
constructor(dir, version, hook = defaultWatcherHook()) {
|
|
12
|
+
this.dir = dir;
|
|
13
|
+
this.version = version;
|
|
14
|
+
|
|
15
|
+
this.scriptWatcher = this.createWatcher("src", hook);
|
|
16
|
+
this.testWatcher = this.createWatcher("test", hook);
|
|
17
|
+
this.shaderWatcher = this.createWatcher("resource/shader", hook);
|
|
18
|
+
|
|
19
|
+
this.objectWatchers = this.findTopFolders(path.join(dir, "resource/object"))
|
|
20
|
+
.map(entry => ({
|
|
21
|
+
...entry,
|
|
22
|
+
watcher: chokidar.watch(entry.dir, hook)
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
this.sceneWatchers = this.findTopFolders(path.join(dir, "resource/scene"))
|
|
26
|
+
.map(entry => ({
|
|
27
|
+
...entry,
|
|
28
|
+
watcher: chokidar.watch(entry.dir, hook)
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
console.log(`♻️ Watching [${dir}] for changes...`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
createWatcher(subdir, hook) {
|
|
35
|
+
return chokidar.watch(path.join(this.dir, subdir), hook);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
findTopFolders(dir, results = []) {
|
|
39
|
+
if (!fs.existsSync(dir)) return results;
|
|
40
|
+
|
|
41
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
42
|
+
const gmlFiles = entries
|
|
43
|
+
.filter(e => e.isFile() && e.name.endsWith(".gml"))
|
|
44
|
+
.map(e => e.name);
|
|
45
|
+
|
|
46
|
+
if (gmlFiles.length > 0) {
|
|
47
|
+
results.push({
|
|
48
|
+
name: path.basename(dir),
|
|
49
|
+
dir: dir.replace(/^\.\//, ""),
|
|
50
|
+
files: gmlFiles,
|
|
51
|
+
});
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
entries
|
|
56
|
+
.filter(e => e.isDirectory())
|
|
57
|
+
.forEach(subdir => this.findTopFolders(path.join(dir, subdir.name), results));
|
|
58
|
+
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function defaultWatcherHook() {
|
|
64
|
+
return {
|
|
65
|
+
ignored: /[\/\\]\./,
|
|
66
|
+
persistent: true,
|
|
67
|
+
ignoreInitial: true,
|
|
68
|
+
depth: 99,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ---------------------------------------------------------
|
|
73
|
+
* GMFileWatcher
|
|
74
|
+
* --------------------------------------------------------- */
|
|
75
|
+
class GMFileWatcher {
|
|
76
|
+
constructor(gmPackage, modulesDir, watch = false) {
|
|
77
|
+
this.gmPath = resolvePath(gmPackage.main);
|
|
78
|
+
this.modulesDirName = modulesDir;
|
|
79
|
+
this.modulesDirPath = resolvePath(modulesDir);
|
|
80
|
+
this.timestamp = "";
|
|
81
|
+
|
|
82
|
+
const dependencyEntries = Object.entries(gmPackage.dependencies);
|
|
83
|
+
|
|
84
|
+
this.modules = watch
|
|
85
|
+
? this.initializeWatchedModules(dependencyEntries)
|
|
86
|
+
: this.initializeSyncModules(dependencyEntries);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
initializeWatchedModules(dependencies) {
|
|
90
|
+
return dependencies.map(([name, version]) => {
|
|
91
|
+
const gmModule = this.parseModule(name, version);
|
|
92
|
+
this.syncModuleFiles(name, gmModule.dir, gmModule.objectWatchers, gmModule.sceneWatchers);
|
|
93
|
+
return gmModule;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
initializeSyncModules(dependencies) {
|
|
98
|
+
dependencies.forEach(([name]) => {
|
|
99
|
+
const modulePath = path.join(this.modulesDirPath, name);
|
|
100
|
+
this.syncModuleFiles(name, modulePath);
|
|
101
|
+
});
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ---------------------------------------------
|
|
106
|
+
* Sync helpers
|
|
107
|
+
* --------------------------------------------- */
|
|
108
|
+
syncModuleFiles(name, moduleDir, objectWatchers = [], sceneWatchers = []) {
|
|
109
|
+
const src = path.join(moduleDir, 'src');
|
|
110
|
+
const test = path.join(moduleDir, 'test');
|
|
111
|
+
const shader = path.join(moduleDir, 'resource/shader');
|
|
112
|
+
|
|
113
|
+
this.syncFiles(src, this.gmPath, `${name}/src`, 'scripts', 'gml');
|
|
114
|
+
this.syncFiles(test, this.gmPath, `${name}/test`, 'scripts', 'gml');
|
|
115
|
+
this.syncFiles(shader, this.gmPath, `${name}/resource/shader`, 'scripts', 'gml');
|
|
116
|
+
this.syncFiles(shader, this.gmPath, `${name}/resource/shader`, 'shaders', 'fsh');
|
|
117
|
+
this.syncFiles(shader, this.gmPath, `${name}/resource/shader`, 'shaders', 'vsh');
|
|
118
|
+
|
|
119
|
+
objectWatchers?.forEach(entry => {
|
|
120
|
+
this.syncFiles(
|
|
121
|
+
entry.dir,
|
|
122
|
+
path.join(this.gmPath, 'objects', entry.name),
|
|
123
|
+
`${name}/resource/object/../${entry.name}`,
|
|
124
|
+
`objects/${entry.name}`,
|
|
125
|
+
'gml',
|
|
126
|
+
'/.*',
|
|
127
|
+
true
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
sceneWatchers?.forEach(entry => {
|
|
132
|
+
this.syncFiles(
|
|
133
|
+
entry.dir,
|
|
134
|
+
path.join(this.gmPath, 'rooms', entry.name),
|
|
135
|
+
`${name}/resource/scene/${entry.name}`,
|
|
136
|
+
`rooms/${entry.name}`,
|
|
137
|
+
'gml',
|
|
138
|
+
'/.*',
|
|
139
|
+
true
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
syncFiles = function (source, target, pkgName, gmFolderName, extension, suffix = '/../*.', isObject = false) {
|
|
145
|
+
childProcess.exec(`find ${source} -iname "*.${extension}"`, (err, stdout) => {
|
|
146
|
+
const loc = stdout.split('\n')
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.map(f => this.readFileMetadata(f, extension))
|
|
149
|
+
.map(script => this.writeIfChanged(script, extension, target, gmFolderName, isObject))
|
|
150
|
+
.reduce(sumLineChanges, { before: 0, after: 0 });
|
|
151
|
+
|
|
152
|
+
const timestamp = new Intl.DateTimeFormat('pl-PL', {
|
|
153
|
+
year: 'numeric',
|
|
154
|
+
month: '2-digit',
|
|
155
|
+
day: '2-digit',
|
|
156
|
+
hour: '2-digit',
|
|
157
|
+
minute: '2-digit',
|
|
158
|
+
second: '2-digit',
|
|
159
|
+
hour12: false,
|
|
160
|
+
}).format(new Date()).replaceAll(',', '');
|
|
161
|
+
|
|
162
|
+
if (timestamp !== this.timestamp) {
|
|
163
|
+
this.timestamp = timestamp
|
|
164
|
+
console.log(`⌚ ${timestamp}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
logSyncSummary(pkgName, suffix, extension, loc);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* ---------------------------------------------
|
|
172
|
+
* Watcher handlers
|
|
173
|
+
* --------------------------------------------- */
|
|
174
|
+
parseModule(name, version) {
|
|
175
|
+
const gmModule = new GMModule(resolvePath(path.join(this.modulesDirName, name)), version);
|
|
176
|
+
|
|
177
|
+
const moduleFilter = (p, module) =>
|
|
178
|
+
path.normalize(p).includes(path.normalize(module.dir));
|
|
179
|
+
|
|
180
|
+
gmModule.scriptWatcher.on('change', p => this.modules.filter(m => moduleFilter(p, m)).forEach(() => this.hook(p)));
|
|
181
|
+
gmModule.testWatcher.on('change', p => this.modules.filter(m => moduleFilter(p, m)).forEach(() => this.hook(p)));
|
|
182
|
+
gmModule.shaderWatcher.on('change', p => this.modules.filter(m => moduleFilter(p, m)).forEach(() => this.hook(p)));
|
|
183
|
+
|
|
184
|
+
gmModule.objectWatchers.forEach(entry => {
|
|
185
|
+
entry.watcher.on('change', () => this.objectHook(entry));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
gmModule.sceneWatchers.forEach(entry => {
|
|
189
|
+
entry.watcher.on('change', () => this.sceneHook(entry));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return gmModule;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async hook(p) {
|
|
196
|
+
const pkgName = extractPkgName(p, this.modulesDirName);
|
|
197
|
+
const source = path.join(this.modulesDirPath, pkgName);
|
|
198
|
+
|
|
199
|
+
this.syncModuleFiles(pkgName, source);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async objectHook(entry) {
|
|
203
|
+
this.syncFiles(
|
|
204
|
+
entry.dir,
|
|
205
|
+
path.join(this.gmPath, 'objects', entry.name),
|
|
206
|
+
`${this.modulesDirName}/resource/object/../${entry.name}`,
|
|
207
|
+
`objects/${entry.name}`,
|
|
208
|
+
'gml',
|
|
209
|
+
'/.*',
|
|
210
|
+
true
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async sceneHook(entry) {
|
|
215
|
+
this.syncFiles(
|
|
216
|
+
entry.dir,
|
|
217
|
+
path.join(this.gmPath, 'rooms', entry.name),
|
|
218
|
+
`${this.modulesDirName}/resource/scene/${entry.name}`,
|
|
219
|
+
`rooms/${entry.name}`,
|
|
220
|
+
'gml',
|
|
221
|
+
'/.*',
|
|
222
|
+
true
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
readFileMetadata(filePath, extension) {
|
|
227
|
+
return {
|
|
228
|
+
name: path.basename(filePath).replace(`.${extension}`, ''),
|
|
229
|
+
dir: path.normalize(filePath.replaceAll('./', '')),
|
|
230
|
+
content: fs.readFileSync(path.normalize(filePath), 'utf8'),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
writeIfChanged(script, extension, target, gmFolderName, isObject) {
|
|
235
|
+
const diffStats = (oldLines, newLines) => {
|
|
236
|
+
const m = oldLines.length;
|
|
237
|
+
const n = newLines.length;
|
|
238
|
+
|
|
239
|
+
// tablica LCS
|
|
240
|
+
const dp = Array.from({ length: m + 1 }, () =>
|
|
241
|
+
Array(n + 1).fill(0)
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
for (let i = 1; i <= m; i++) {
|
|
245
|
+
for (let j = 1; j <= n; j++) {
|
|
246
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
247
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
248
|
+
} else {
|
|
249
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const same = dp[m][n];
|
|
255
|
+
const removed = m - same;
|
|
256
|
+
const added = n - same;
|
|
257
|
+
const total = same + added
|
|
258
|
+
|
|
259
|
+
return { added, removed, total };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const removeZeroFields = (obj) => {
|
|
263
|
+
return Object.fromEntries(
|
|
264
|
+
Object.entries(obj).filter(([_, value]) => value !== 0)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const dst = isObject
|
|
269
|
+
? path.join(target, `${script.name}.${extension}`)
|
|
270
|
+
: path.join(target, gmFolderName, `${script.name}/${script.name}.${extension}`);
|
|
271
|
+
|
|
272
|
+
if (!fs.existsSync(dst)) {
|
|
273
|
+
const dstYY = path.join(target, gmFolderName, `${script.name}/${script.name}.yy`);
|
|
274
|
+
if (!fs.existsSync(dstYY)) {
|
|
275
|
+
console.log(`⚠️ File ${dstYY} does not exists, cannot sync.`)
|
|
276
|
+
return {
|
|
277
|
+
before: 0,
|
|
278
|
+
after: 0,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fs.writeFileSync(dst, "");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const existing = fs.readFileSync(dst, 'utf8');
|
|
286
|
+
if (existing !== script.content) {
|
|
287
|
+
const result = removeZeroFields(diffStats(existing.split('\n'), script.content.split('\n')))
|
|
288
|
+
console.log(`➡️ Save ${script.name}.${extension}:`, result);
|
|
289
|
+
fs.writeFileSync(dst, script.content, { encoding: 'utf8', flag: 'w' });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
before: existing.split('\n').length,
|
|
294
|
+
after: script.content.split('\n').length,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* ---------------------------------------------------------
|
|
300
|
+
* Helpers (pure functions)
|
|
301
|
+
* --------------------------------------------------------- */
|
|
302
|
+
function resolvePath(p) {
|
|
303
|
+
return path.join(process.cwd(), path.normalize(p));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function extractPkgName(file, modulesDir) {
|
|
307
|
+
return file
|
|
308
|
+
.split(modulesDir)[1]
|
|
309
|
+
.replaceAll('\\', '/')
|
|
310
|
+
.split('/')
|
|
311
|
+
.filter(Boolean)[0];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function sumLineChanges(acc, cur) {
|
|
315
|
+
acc.before += cur.before;
|
|
316
|
+
acc.after += cur.after;
|
|
317
|
+
return acc;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function logSyncSummary(pkgName, suffix, extension, loc) {
|
|
321
|
+
const newLines = loc.after - loc.before;
|
|
322
|
+
if (newLines !== 0) {
|
|
323
|
+
console.log(`🚀 [${pkgName}${suffix}${extension}]:`, { diff: newLines, total: loc.after } );
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* ---------------------------------------------------------
|
|
328
|
+
* Public API
|
|
329
|
+
* --------------------------------------------------------- */
|
|
330
|
+
export async function watch(gmPackagePath, modulesDir = 'gm_modules') {
|
|
331
|
+
try {
|
|
332
|
+
return new GMFileWatcher(
|
|
333
|
+
JSON.parse(await readFile(gmPackagePath, 'utf8')),
|
|
334
|
+
modulesDir,
|
|
335
|
+
true
|
|
336
|
+
);
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.error(`❌ Unable to parse package-gm.json at: ${gmPackagePath}\n${e.message}`);
|
|
339
|
+
console.error(e.stack);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export async function sync(gmPackagePath, modulesDir = 'gm_modules') {
|
|
344
|
+
try {
|
|
345
|
+
return new GMFileWatcher(
|
|
346
|
+
JSON.parse(await readFile(gmPackagePath, 'utf8')),
|
|
347
|
+
modulesDir
|
|
348
|
+
);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
console.error(`❌ Unable to parse package-gm.json at: ${gmPackagePath}\n${e.message}`);
|
|
351
|
+
console.error(e.stack);
|
|
352
|
+
}
|
|
353
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Alkapivo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# gm-cli
|
|
2
|
+
Gamemaker CLI toolkit. Watch & sync gml sources with yyp project.
|
|
3
|
+
|
|
4
|
+
# Requirements
|
|
5
|
+
- [Node.js](https://nodejs.org) 18.16^
|
|
6
|
+
- [find](https://en.wikipedia.org/wiki/Find_(Unix))
|
|
7
|
+
|
|
8
|
+
# Install
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
# Usage
|
|
14
|
+
```bash
|
|
15
|
+
gm-cli watch
|
|
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
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { watch, sync } from './GMFileWatcher.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { program } from 'commander';
|
|
6
|
+
import { spawn, execSync } from 'child_process';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
program.version('26.02.14', '-v, --version, ', 'output the current version');
|
|
12
|
+
program.command('init')
|
|
13
|
+
.description('CLI creator for package-gm.json')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout
|
|
18
|
+
});
|
|
19
|
+
const askQuestion = (query) => new Promise(resolve => rl.question(query, resolve));
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
console.log("This utility will walk you through creating a package-gm.json file.");
|
|
23
|
+
console.log("It only covers the most common items, and tries to guess sensible defaults.");
|
|
24
|
+
|
|
25
|
+
const projectPath = process.cwd();
|
|
26
|
+
const basename = path.basename(projectPath);
|
|
27
|
+
const version = "1.0.0";
|
|
28
|
+
const propertyPackage = await askQuestion(`package name: (${basename}) `);
|
|
29
|
+
const propertyVersion = await askQuestion(`version: (${version}) `);
|
|
30
|
+
const propertyDescription = await askQuestion('description: ');
|
|
31
|
+
const propertyGamemaker = await askQuestion('gamemaker project path: ');
|
|
32
|
+
const propertyTest = await askQuestion('test command: ');
|
|
33
|
+
const propertyGit = await askQuestion('git repository: ');
|
|
34
|
+
const propertyKeywords = await askQuestion('keywords: ');
|
|
35
|
+
const propertyAuthor = await askQuestion('author: ');
|
|
36
|
+
const propertyLicense = await askQuestion('license: (ISC) ');
|
|
37
|
+
const data = {
|
|
38
|
+
package: propertyPackage === null || propertyPackage === '' ? basename : propertyPackage,
|
|
39
|
+
version: propertyVersion === null || propertyVersion === '' ? version : propertyVersion,
|
|
40
|
+
description: propertyDescription,
|
|
41
|
+
main: propertyGamemaker,
|
|
42
|
+
test: propertyTest,
|
|
43
|
+
git: propertyGit,
|
|
44
|
+
keywords: propertyKeywords,
|
|
45
|
+
author: propertyAuthor,
|
|
46
|
+
license: propertyLicense,
|
|
47
|
+
scripts: {},
|
|
48
|
+
dependencies: {},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const filePath = path.join(projectPath, 'package-gm.json');
|
|
52
|
+
const dataString = JSON.stringify(data, null, 2);
|
|
53
|
+
|
|
54
|
+
console.log(`About to write to ${filePath}:\n\n${dataString}\n\n`);
|
|
55
|
+
const response = await askQuestion(`Is this OK? (yes) `)
|
|
56
|
+
if (typeof response === 'string' && (response.includes('y') || response.includes('Y'))) {
|
|
57
|
+
fs.writeFileSync(filePath, dataString, 'utf8');
|
|
58
|
+
} else {
|
|
59
|
+
console.log('Aborted.\n');
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('An error occurred:', error);
|
|
63
|
+
} finally {
|
|
64
|
+
rl.close();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
program.command('watch')
|
|
69
|
+
.description('Watch modules dir and copy code to gamemaker project')
|
|
70
|
+
.action(() => {
|
|
71
|
+
watch(path.normalize(path.join(process.cwd(), 'package-gm.json')))
|
|
72
|
+
});
|
|
73
|
+
program.command('sync')
|
|
74
|
+
.description('Copy code from modules dir to gamemaker project')
|
|
75
|
+
.action(() => {
|
|
76
|
+
sync(path.normalize(path.join(process.cwd(), 'package-gm.json')))
|
|
77
|
+
});
|
|
78
|
+
program.command('install')
|
|
79
|
+
.description('Install dependencies listed in package-gm.json to gm_modules folder')
|
|
80
|
+
.action(function() {
|
|
81
|
+
const packageJsonPath = 'package-gm.json';
|
|
82
|
+
const modulesDir = 'gm_modules';
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(modulesDir)) {
|
|
85
|
+
fs.mkdirSync(modulesDir);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
89
|
+
const dependencies = packageData.dependencies;
|
|
90
|
+
Object.entries(dependencies).forEach(([key, dependency]) => {
|
|
91
|
+
const modulePath = path.join(modulesDir, key);
|
|
92
|
+
if (fs.existsSync(modulePath)) {
|
|
93
|
+
try {
|
|
94
|
+
execSync('git rev-parse --is-inside-work-tree', { cwd: modulePath, stdio: 'ignore' });
|
|
95
|
+
console.log(`Syncing ${modulePath} to revision ${dependency.revision}`);
|
|
96
|
+
execSync('git reset --hard HEAD', { cwd: modulePath, stdio: 'inherit' });
|
|
97
|
+
execSync('git clean -fdx -e', { cwd: modulePath, stdio: 'inherit' });
|
|
98
|
+
execSync(`git checkout ${dependency.revision}`, { cwd: modulePath, stdio: 'inherit' });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.log(`Removing ${modulePath} because it's not a git repository`);
|
|
101
|
+
fs.rmSync(modulePath, { recursive: true, force: true });
|
|
102
|
+
console.log(`Initializing ${modulePath} to revision ${dependency.revision}`);
|
|
103
|
+
execSync(`git clone ${dependency.remote} ${modulePath}`, { stdio: 'inherit' });
|
|
104
|
+
execSync(`git checkout ${dependency.revision}`, { cwd: modulePath, stdio: 'inherit' });
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`Initializing ${modulePath} to revision ${dependency.revision}`);
|
|
108
|
+
execSync(`git clone ${dependency.remote} ${modulePath}`, { stdio: 'inherit' });
|
|
109
|
+
execSync(`git checkout ${dependency.revision}`, { cwd: modulePath, stdio: 'inherit' });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log('All dependencies processed.');
|
|
114
|
+
process.exit(0);
|
|
115
|
+
})
|
|
116
|
+
program.command('run')
|
|
117
|
+
.description('Run the script named <foo>')
|
|
118
|
+
.argument('<foo>', 'script name')
|
|
119
|
+
.action((foo) => {
|
|
120
|
+
if (typeof foo !== 'string') {
|
|
121
|
+
console.log(`missing argument`);
|
|
122
|
+
console.log(`Exited with code 1`);
|
|
123
|
+
return process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const packageJsonPath = 'package-gm.json';
|
|
127
|
+
const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
128
|
+
const scriptData = packageData.scripts[foo]
|
|
129
|
+
if (typeof scriptData !== 'string') {
|
|
130
|
+
console.log(`script ${foo} wasn't found`);
|
|
131
|
+
console.log(`Exited with code 1`);
|
|
132
|
+
return process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const shellScript = `#!/bin/bash
|
|
136
|
+
${scriptData}
|
|
137
|
+
`;
|
|
138
|
+
const bashProcess = spawn("bash", ["-s"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
139
|
+
bashProcess.stdin.write(shellScript);
|
|
140
|
+
bashProcess.stdin.end();
|
|
141
|
+
bashProcess.on("exit", (code) => {
|
|
142
|
+
console.log(`Exited with code ${code}`);
|
|
143
|
+
process.exit(code);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
program.command('generate')
|
|
147
|
+
.description('Generate *.yyp IncludedFiles section')
|
|
148
|
+
.action(function() {
|
|
149
|
+
function getFilesRecursively(dir, root) {
|
|
150
|
+
let files = [];
|
|
151
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
152
|
+
const fullPath = path.join(dir, entry).replaceAll("\\", "/");
|
|
153
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
154
|
+
files = files.concat(getFilesRecursively(fullPath, root));
|
|
155
|
+
} else {
|
|
156
|
+
const filePath = `datafiles${(fullPath.startsWith(root) ? fullPath.slice(root.length) : fullPath)}`.replaceAll(`/${entry}`, '');
|
|
157
|
+
const line = `{"$GMIncludedFile":"","%Name":"${entry}","CopyToMask":-1,"filePath":"${filePath}","name":"${entry}","resourceType":"GMIncludedFile","resourceVersion":"2.0",},`;
|
|
158
|
+
files.push(line);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return files;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function findFileUpwardsSync(filename = "gm-cli.env", maxLevels = 99) {
|
|
165
|
+
let currentDir = process.cwd();
|
|
166
|
+
for (let i = 0; i < maxLevels; i++) {
|
|
167
|
+
const candidate = path.join(currentDir, filename);
|
|
168
|
+
if (fs.existsSync(candidate)) {
|
|
169
|
+
return candidate;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parentDir = path.dirname(currentDir);
|
|
173
|
+
if (parentDir === currentDir) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
currentDir = parentDir;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseEnvFile(filePath) {
|
|
184
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
185
|
+
const result = new Map();
|
|
186
|
+
content.split(/\r?\n/).forEach(line => {
|
|
187
|
+
line = line.trim();
|
|
188
|
+
if (!line || line.startsWith("#")) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const match = line.match(/^([^=]+)="(.*)"$/);
|
|
193
|
+
if (match) {
|
|
194
|
+
const [, key, value] = match;
|
|
195
|
+
result.set(key.trim(), value);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const envFile = findFileUpwardsSync();
|
|
203
|
+
if (envFile === null) {
|
|
204
|
+
console.error('gm-cli.env was not found')
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const envPath = path.dirname(envFile).replaceAll("\\", "/");
|
|
209
|
+
const envMap = parseEnvFile(envFile);
|
|
210
|
+
if (!envMap.has("GMS_PROJECT_PATH")) {
|
|
211
|
+
console.error(`GMS_PROJECT_PATH was not defined in ${envFile}`)
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!envMap.has("GMS_PROJECT_NAME")) {
|
|
216
|
+
console.error(`GMS_PROJECT_NAME was not defined in ${envFile}`)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const projectPath = path.join(envPath, envMap.get("GMS_PROJECT_PATH")).replaceAll("\\", "/");
|
|
221
|
+
const yypPath = path.join(projectPath, `${envMap.get("GMS_PROJECT_NAME")}.yyp`).replaceAll("\\", "/");
|
|
222
|
+
const yypOldPath = path.join(projectPath, `${envMap.get("GMS_PROJECT_NAME")}.yyp.old`).replaceAll("\\", "/");
|
|
223
|
+
const yyp = fs.readFileSync(yypPath, "utf8");
|
|
224
|
+
fs.copyFileSync(yypPath, yypOldPath);
|
|
225
|
+
|
|
226
|
+
const datafilesPath = path.join(projectPath, "datafiles").replaceAll("\\", "/")
|
|
227
|
+
const datafiles = getFilesRecursively(datafilesPath, datafilesPath)
|
|
228
|
+
const replaced = yyp.replace(/"IncludedFiles"\s*:\s*\[(.*?)\]/s, `"IncludedFiles":[
|
|
229
|
+
${datafiles.join("\n ")}
|
|
230
|
+
]`);
|
|
231
|
+
fs.writeFileSync(yypPath, replaced, "utf8");
|
|
232
|
+
});
|
|
233
|
+
program.command('make')
|
|
234
|
+
.description('Build and run gamemaker project')
|
|
235
|
+
.option('-t, --target <target>', 'available targets: windows')
|
|
236
|
+
.option('-r, --runtime <type>', 'use VM or YYC runtime')
|
|
237
|
+
.option('-n, --name <name>', 'The actual file name of the ZIP file that is created')
|
|
238
|
+
.option('-l, --launch', 'launch the executable after building')
|
|
239
|
+
.option('-c, --clean', 'make clean build')
|
|
240
|
+
.action(function() {
|
|
241
|
+
const targetMap = new Map([ [ 'windows', 'win' ] ])
|
|
242
|
+
const options = this.opts();
|
|
243
|
+
const config = {
|
|
244
|
+
runtime: '$GMS_RUNTIME',
|
|
245
|
+
target: '$GMS_TARGET',
|
|
246
|
+
targetExt: 'win',
|
|
247
|
+
clean: 'false',
|
|
248
|
+
launch: 'PackageZip',
|
|
249
|
+
name: '$GMS_PROJECT_NAME',
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (options.runtime !== undefined) {
|
|
253
|
+
config.runtime = options.runtime;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.target !== undefined && targetMap.has(options.target)) {
|
|
257
|
+
config.target = options.target;
|
|
258
|
+
config.targetExt = targetMap.get(config.target);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (options.clean !== undefined) {
|
|
262
|
+
config.clean = 'true';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (options.launch !== undefined) {
|
|
266
|
+
config.launch = 'Run';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (options.name !== undefined && typeof options.name === 'string' && options.name.trim() !== '') {
|
|
270
|
+
config.name = options.name;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const shellScript = `#!/bin/bash
|
|
274
|
+
function log_info {
|
|
275
|
+
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
276
|
+
echo -e "$timestamp INFO [gm-cli::run] $1"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function log_error {
|
|
280
|
+
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
281
|
+
echo -e "$timestamp ERROR [gm-cli::run] $1"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
gm_cli_env_path=""
|
|
285
|
+
dir=$(realpath "$PWD")
|
|
286
|
+
while [ "$dir" != "/" ]; do
|
|
287
|
+
if [ -f "$dir/gm-cli.env" ]; then
|
|
288
|
+
gm_cli_env_path="$dir/gm-cli.env"
|
|
289
|
+
log_info "Load configuration '$gm_cli_env_path'"
|
|
290
|
+
set -a
|
|
291
|
+
. "$gm_cli_env_path"
|
|
292
|
+
set +a
|
|
293
|
+
break
|
|
294
|
+
fi
|
|
295
|
+
dir=$(dirname "$dir")
|
|
296
|
+
done
|
|
297
|
+
|
|
298
|
+
igor_path=$GMS_IGOR_PATH
|
|
299
|
+
if [ -z "$igor_path" ]; then
|
|
300
|
+
log_error "GMS_IGOR_PATH must be defined! exit 1"
|
|
301
|
+
exit 1
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
project_name=$GMS_PROJECT_NAME
|
|
305
|
+
if [ -z "$project_name" ]; then
|
|
306
|
+
log_error "GMS_PROJECT_NAME must be defined! exit 1"
|
|
307
|
+
exit 1
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
project_path=$GMS_PROJECT_PATH
|
|
311
|
+
if [ -z "$project_path" ]; then
|
|
312
|
+
log_error "GMS_PROJECT_PATH must be defined! exit 1"
|
|
313
|
+
exit 1
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
project_path=$(dirname "$gm_cli_env_path")/$project_path
|
|
317
|
+
project_path=$(realpath $project_path)
|
|
318
|
+
|
|
319
|
+
user_path=$GMS_USER_PATH
|
|
320
|
+
if [ -z "$user_path" ]; then
|
|
321
|
+
log_error "GMS_USER_PATH must be defined! exit 1"
|
|
322
|
+
exit 1
|
|
323
|
+
fi
|
|
324
|
+
user_path=$(realpath $user_path)
|
|
325
|
+
|
|
326
|
+
runtime_path=$GMS_RUNTIME_PATH
|
|
327
|
+
if [ -z "$runtime_path" ]; then
|
|
328
|
+
log_error "GMS_RUNTIME_PATH must be defined! exit 1"
|
|
329
|
+
exit 1
|
|
330
|
+
fi
|
|
331
|
+
runtime_path=$(realpath $runtime_path)
|
|
332
|
+
|
|
333
|
+
runtime=${config.runtime}
|
|
334
|
+
if [ -z "$runtime" ]; then
|
|
335
|
+
log_error "GMS_RUNTIME must be defined! exit 1"
|
|
336
|
+
exit 1
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
target=${config.target}
|
|
340
|
+
if [ -z "$target" ]; then
|
|
341
|
+
log_error "GMS_TARGET must be defined! exit 1"
|
|
342
|
+
exit 1
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
target_ext=${config.targetExt}
|
|
346
|
+
if [ -z "$target_ext" ]; then
|
|
347
|
+
log_error "GMS_TARGET_EXT must be defined! exit 1"
|
|
348
|
+
exit 1
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
zip_name=${config.name}
|
|
352
|
+
echo $zip_name
|
|
353
|
+
if [ -z "$zip_name" ]; then
|
|
354
|
+
log_error "--name must be defined! exit 1"
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
clean=${config.clean}
|
|
359
|
+
if [ "$clean" = "true" ]; then
|
|
360
|
+
log_info "Clean '$project_path/tmp/igor'"
|
|
361
|
+
rm -rf $project_path/tmp/igor
|
|
362
|
+
|
|
363
|
+
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"
|
|
364
|
+
$igor_path \
|
|
365
|
+
--runtimePath="$runtime_path" \
|
|
366
|
+
--runtime=$runtime \
|
|
367
|
+
--project="$\{project_path\}/$\{project_name\}.yyp" \
|
|
368
|
+
-- $target Clean
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
log_info "Clean '$\{project_path\}/tmp/igor/out'"
|
|
372
|
+
rm -rf $\{project_path\}/tmp/igor/out
|
|
373
|
+
|
|
374
|
+
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}"
|
|
375
|
+
$igor_path \
|
|
376
|
+
--project="$\{project_path\}/$\{project_name\}.yyp" \
|
|
377
|
+
--user="$user_path" \
|
|
378
|
+
--runtimePath="$runtime_path" \
|
|
379
|
+
--runtime=$runtime \
|
|
380
|
+
--cache="$\{project_path\}/tmp/igor/cache" \
|
|
381
|
+
--temp="$\{project_path\}/tmp/igor/temp" \
|
|
382
|
+
--of="$\{project_path\}/tmp/igor/out/$\{project_name\}.win" \
|
|
383
|
+
--tf="$\{zip_name\}.zip" \
|
|
384
|
+
-- $target ${config.launch};
|
|
385
|
+
|
|
386
|
+
exit 0
|
|
387
|
+
`;
|
|
388
|
+
|
|
389
|
+
const bashProcess = spawn("bash", ["-s"], { stdio: ["pipe", "inherit", "inherit"] });
|
|
390
|
+
bashProcess.stdin.write(shellScript);
|
|
391
|
+
bashProcess.stdin.end();
|
|
392
|
+
bashProcess.on("exit", (code) => {
|
|
393
|
+
console.log(`Exited with code ${code}`);
|
|
394
|
+
process.exit(code);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ovipakla/gm-cli",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"description": "Gamemaker CLI toolkit. Watch & sync gml sources with yyp project.",
|
|
8
|
+
"main": "app.js",
|
|
9
|
+
"scripts": {},
|
|
10
|
+
"author": "Alkapivo",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"chalk": "^5.3.0",
|
|
14
|
+
"chokidar": "^3.5.3",
|
|
15
|
+
"commander": "^11.1.0",
|
|
16
|
+
"conf": "^12.0.0"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"gm-cli": "app.js"
|
|
20
|
+
},
|
|
21
|
+
"type": "module"
|
|
22
|
+
}
|