@scaffscript/core 0.1.5 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Undervolta
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 CHANGED
@@ -1,329 +1,59 @@
1
1
  # ScaffScript
2
2
 
3
- A superset language of **GameMaker Language** (GML) for creating module-based GameMaker source codes. This minimal language is mainly used for developing GML libraries, but can also be used for other purposes.
3
+ ![ScaffScript Banner](./assets/Banner/banner1-4-1.webp)
4
4
 
5
- > [!WARNING]
6
- > This project is still in early development. The syntax and features are subject to change. Use at your own risk.
7
-
8
- ## Key Features
9
-
10
- - Unify multiple source files into a single source file.
11
- - TypeScript-like syntax and module system.
12
- - Flexible configuration options.
13
- - Dev-friendly CLI interface.
14
- - Togglable integration (auto/manual) to your GameMaker project.
15
- - And more... (WIP)
16
-
17
- ## Installation
18
-
19
- 1. Install [Bun](https://bun.sh) (if not already installed).
20
- 2. Clone this repository.
21
- 3. Install dependencies:
22
-
23
- ```bash
24
- bun install
25
- ```
26
-
27
- 4. CLI usage:
28
-
29
- ```bash
30
- scaff <command> [args]
31
- ```
32
-
33
- ## Usage
34
-
35
- Use `*.ss` files to mark a file as a Scaff file. Normal `*.gml` files are still supported, but they are not processed by Scaff.
36
-
37
- ### Export Module
38
-
39
- 1. Use `export` statement to export types (a variable, function, class, interface, type, enum, or arrow function) from a Scaff file.
40
-
41
- ```js
42
- // my_file.ss
43
-
44
- export var my_var = 1;
45
- export let my_let = 2; // `let` will be removed, so it'll become an instance variable
46
- export const MY_CONST = "Hello, World!"; // `const` will be converted to `#macro`
47
-
48
- export function my_func() {
49
- show_debug_message("Hello, World!");
50
- }
51
-
52
- // arrow functions will be converted to function expression or method
53
- export const my_method = (name: string, age: number) => {
54
- show_debug_message(`Hello, ${name}! You are ${age} years old.`);
55
- }
56
-
57
- export enum MY_ENUM {
58
- A,
59
- B,
60
- C
61
- }
62
-
63
- // classes will be converted to struct constructor
64
- export class MyClass {
65
- constructor(name: string, age: number)
66
-
67
- name: string = "";
68
- age: number = 0;
69
-
70
- show_name() {
71
- show_debug_message(this.name);
72
- }
73
- }
74
-
75
- // interfaces and types will be converted to struct
76
- export interface MyInterface {
77
- name: string;
78
- age: number = 0; // default value is allowed, which not allowed in TypeScript
79
- isActive?: boolean; // optional property is allowed
80
- }
81
-
82
- // interfaces can be extended
83
- export interface MyExtInterface extends MyInterface {
84
- address: string;
85
- }
86
-
87
- export type MyType = {
88
- name: string = ""; // default value is allowed, which not allowed in TypeScript
89
- age: number;
90
- isActive?: boolean; // optional property is allowed
91
- }
92
-
93
- // types intersection is allowed
94
- export type MyIntersectedType = MyType & {
95
- address: string;
96
- }
97
- ```
98
-
99
- You can do a barrel export (`export * from "<path>"`) as well, so that you can import modules from the parent directory:
100
-
101
- ```js
102
- // index.ss
103
-
104
- export * from "my_file"; // export all types from "my_file"
105
- ```
106
-
107
- 2. Use `impl` statement to add implementation to a class.
108
-
109
- ```js
110
- // my_file.ss
111
-
112
- export class MyClass {
113
- constructor(name: string, age?: number)
5
+ A minimal superset language of **GameMaker Language** (GML) for creating module-based GameMaker source codes. This minimal language is mainly used for developing GML libraries, but can also be used for other purposes.
114
6
 
115
- name: string = "";
116
- age: number = 0;
117
- }
118
-
119
- impl MyClass {
120
- show_age() {
121
- show_debug_message(age);
122
- }
123
- }
124
- ```
125
-
126
- You can split the implementation into multiple files as well:
127
-
128
- ```js
129
- // set.ss
130
-
131
- impl MyClass {
132
- set_age(age: number) {
133
- age = age;
134
- }
135
-
136
- // static method
137
- static static_method = (name: string) => {
138
- show_debug_message($"Hello, {name}!");
139
- }
140
- }
141
- ```
142
-
143
- ### Import Module
144
-
145
- 1. Use `include` statement to import a module from another Scaff file, and replace the import statement with the actual content of the exported types.
146
-
147
- ```js
148
- // my_other_file.ss
149
-
150
- include { my_var, my_func, MyClass } from "my_file"
151
-
152
- /**
153
- * The generated source code will replace the include statement with the actual content of the exported types.
154
- *
155
- * For example, the above include statement will be replaced with:
156
-
157
- var my_var = 1;
158
- function my_func() {
159
- show_debug_message("Hello, World!");
160
- }
161
- function MyClass(name, age) constructor {
162
- // ...
163
- }
164
-
165
- */
166
-
167
- // non-included lines will be left as is
168
- show_debug_message("This line will be left as is.");
169
-
170
- // you can include multiple modules in a single file
171
- include my_enum from "my_file"
172
- /**
173
- enum MY_ENUM {
174
- A,
175
- B,
176
- C
177
- }
178
- */
179
-
180
- // you can include normal GML files as well
181
- include { "some_script.gml", "another_gml" } from "./scripts"
182
- // must be in curly braces and double quotes, the order of the files will be preserved, the `.gml` extension is optional
183
- ```
184
-
185
- 2. Use `import` statement to import a module from another Scaff file, and then use `@<keyword>` statements to load and replace the content of a Scaff file to certain places in the code.
186
-
187
- | Keyword | Description | Example |
188
- | --- | --- | --- |
189
- | `@content` | Replace the statement with the actual content of the exported type. | `@content my_var` -> `var my_var = 1;` |
190
- | `@nameof` | Replace the statement with the **name** of the exported type. | `@nameof my_var` -> `"my_var"` |
191
- | `@valueof` or `@:` | Replace the statement with the **value** of the exported type. | `@valueof my_var` -> `1`, `@:my_var` -> `1` |
192
- | `@typeof` | Replace the statement with the **type** of the exported type. | `@typeof my_var` -> `"number"` |
193
- | `@use` | Replace the statement with the object shape of the exported type. Only works with `interface` or `type`. | `var obj = @use MyInterface { name: "John" }` -> `var obj = { name: "John", age: 0, isActive: false };` |
194
-
195
- ```js
196
- // my_import.ss
197
-
198
- import { my_var } from "my_file"
199
-
200
- show_debug_message(@valueof my_var); // 1
201
- show_debug_message(@:my_var); // 1
202
- show_debug_message(@typeof my_var); // "number"
203
- show_debug_message(@nameof my_var); // "my_var"
204
- show_debug_message(@content my_var); // var my_var = 1;
205
- ```
206
-
207
- ```js
208
- // my_other_file.ss
209
-
210
- import * from "my_file"
211
-
212
- my_method = function() {
213
- @content my_var; // replace with `var my_var = 1;`
214
- var hello = @valueof MY_CONST; // replace with `var hello = "Hello, World!";`
215
- var obj = @use MyInterface { // replace with `var obj = { name: "John", age: 0, isActive: false };`
216
- name: "John"
217
- };
218
-
219
- show_debug_message(my_var); // 1
220
- show_debug_message(obj.name); // John
221
-
222
- var inst = new MyClass("John", 20);
223
- inst.show_age(); // 20
224
- }
225
- ```
7
+ > [!CAUTION]
8
+ > ScaffScript is **not** affiliated with or endorsed by YoYo Games Ltd. GameMaker and GML are trademarks of YoYo Games Ltd.
226
9
 
227
10
  > [!WARNING]
228
- > If you use any of the `@` statements without importing the module first, the statement will be left as is, which won't be processed by Scaff, and won't be accepted by GameMaker. Even so, there are some exceptions for non-module `@` statements. Check the special `@` statements below (WIP).
229
-
230
- | Keyword | Description | Example |
231
- | --- | --- | --- |
232
- | `@now` | Replace the statement with the current timestamp in ISO 8601 format. | `var now = "@now";` -> `var now = "2021-01-01T00:00:00.000Z";` |
233
- | `@today` | Replace the statement with the current date in ISO 8601 format. | `var today = "@today";` -> `var today = "2021-01-01";` |
234
- | `@version` | Replace the statement with the current version of Scaff. | `var version = "@version";` -> `var version = "0.1.0";` |
235
- | `@file` | Replace the statement with the current file name. | `var file = "@file";` -> `var file = "my_file";` |
236
- | `@line` | Replace the statement with the current line number. | `var line = @line;` -> `var line = 1;` |
237
- | `@counter` | Replace the statement with an increasing number. | `var counter = @counter;` -> `var counter = 1;`, `var counter = @counter;` -> `var counter = 2;`, etc. |
238
-
239
-
240
- ### GameMaker Integration
241
-
242
- Use `intg` statement to mark this file as an integration target, and `#[<name_or_event>]` statement to mark a block of code as a write target. The content of the file will be written to the actual GameMaker project.
243
-
244
- ```js
245
- // my_file.ss
246
-
247
- intg { main, some_mod } to "./scripts/my_script" // integrate to `scripts/my_script/my_script.gml`
11
+ > This project is still in early development. The syntax and features are subject to change. Use at your own risk.
248
12
 
249
- #[main]
250
- show_debug_message("Hello, from my_script!");
13
+ ---
251
14
 
252
- #[some_mod]
253
- show_debug_message("Hello, from my_script (some_mod)!");
15
+ ## Key Features
254
16
 
255
- #[some_mod -- prod]
256
- show_debug_message("Hello, only in production!");
257
- ```
17
+ - **TypeScript-like Module System**. Use `export`, `import`, and `include` to organize code across `.ss` files, fully resolved at compile-time.
18
+ - **Class Syntax**. Define classes with constructors, properties, and methods that compile to GML struct constructors. Extend classes across files with `impl`.
19
+ - **Content Directives**. Inline compiled GML content (`@content`, `@valueof`, `@:`, etc.) directives for dynamic code insertion.
20
+ - **Special Values**. Access compile-time tokens like `@now`, `@today`, `@version`, `@file`, and `@line` for metadata and debugging.
21
+ - **Code Generation Blocks**. Use `#[blockName]` to define named content sections and `intg` to map them to GameMaker asset paths.
22
+ - **GameMaker Integration**. Automatically writes `.gml` files, generates `.yy` metadata, and updates your `.yyp` project file for scripts and objects.
23
+ - **File Scanning & Processing**. Recursive scanning of `.ss` and `.gml` files with dependency-aware processing order.
258
24
 
259
- ```js
260
- // my_other_file.ss
25
+ ---
261
26
 
262
- intg * to "objects/my_object" // integrate to `objects/my_object/*`
263
- intg { Step, keydown:keyboard_d } to "objects/my_other_object"
27
+ ## Installation & Documentation
264
28
 
265
- // method 1
266
- #[main as create] // integrate to `objects/my_object/Create_0.gml`
267
- show_debug_message("Hello, from my_object create event!");
29
+ For more information, please refer to the official [documentation](https://scaffscript.lefinitas.com).
268
30
 
269
- // method 2
270
- #[StepEvent] // <event_name>Event is also supported
271
- show_debug_message("Hello, from my_object step event!");
31
+ ---
272
32
 
273
- // method 3
274
- #[key as KeyPress:KEYBOARD_ENTER] // use `:` to specify the event number KEYBOARD_ENTER in this case)
275
- show_debug_message("Hello, from my_object keypress - enter event!");
33
+ ## Questions & Feature Requests
276
34
 
277
- // method 4
278
- #[keydown:keyboard_d event] // the `event`, event type, and event number are case insensitive, add `event` suffix to mark as event (like in method 2 example)
279
- show_debug_message("Hello, from my_object keydown - d event!");
280
- ```
35
+ Feel free to start a [discussion](https://github.com/undervolta/scaffscript/discussions) or open an [issue](https://github.com/undervolta/scaffscript/issues) for any questions or feature requests.
281
36
 
282
- > [!NOTE]
283
- > If you're using method 2 or 4, the `event` keyword is required to mark the block as an event.
284
- > The `event` keyword will be omitted in the block name, so `#[StepEvent]` will become `Step` in the integration block (example: `intg { Step } to "objects/my_object"`, notice no `Event` suffix).
285
- > If you're creating (not modifying) a new **collision** event, you need to specify the name of the other object (case sensitive, exact name of the object in the IDE, such as `obj_player`). And you need to reopen the IDE if you're creating a new collision event using Scaff integration.
37
+ ---
286
38
 
287
- ### Configuration
39
+ ## Contributing
288
40
 
289
- Create a `scaff.config.<ts\|mjs\|cjs|json>` file in the root of your project with the following content:
41
+ Contributions are welcome! Please open an issue or submit a pull request.
290
42
 
291
- ```ts
292
- // scaff.config.ts
43
+ 1. Fork the [repository](https://github.com/undervolta/scaffscript).
44
+ 2. Clone the forked repository to your local machine.
45
+ 3. Make and test your changes.
46
+ 4. Commit your changes and push it to your forked repository.
47
+ 5. Open a pull request to the main repository.
293
48
 
294
- export default {
295
- // ...
296
- };
297
- ```
49
+ ---
298
50
 
299
- ```js
300
- // scaff.config.mjs
51
+ ## Support
301
52
 
302
- export default {
303
- // ...
304
- };
305
- ```
53
+ If you like this project, or this project helped you in any way, please consider supporting me on [Ko-fi](https://ko-fi.com/undervolta) or [Trakteer](https://trakteer.id/undervolta). Don't forget to leave a star! Your support is greatly appreciated!
306
54
 
307
- ```js
308
- // scaff.config.cjs
55
+ ---
309
56
 
310
- module.exports = {
311
- // ...
312
- };
313
- ```
57
+ ## License
314
58
 
315
- | Option | Type | Default | Description |
316
- | --- | --- | --- | --- |
317
- | `acceptAllIntegrations` | `boolean` | `false` | Accept all generated files to be integrated without manual confirmation. |
318
- | `clearOutputDir` | `boolean` | `false` | Clear the output directory before generating source code. |
319
- | `counterStart` | `number` | `1` | Starting value for the counter special value. |
320
- | `debugLevel` | `0 \| 1 \| 2` | `0` | Debug level. `0` = no debug, `1` = basic debug, `2` = verbose debug. |
321
- | `integrationOption` | `ScaffIntegrationOptions` | `{}` | Integration options. |
322
- | `noBackup` | `boolean` | `false` | Don't backup the original files before integration. |
323
- | `noIntegration` | `boolean` | `false` | Don't integrate the files to GM project. |
324
- | `onNotFound` | `"error" \| "ignore"` | `"error"` | What to do when something is not found. |
325
- | `path` | `Record<string, string>` | `{}` | Path aliases. |
326
- | `production` | `boolean` | `false` | Whether the script is running in production mode. |
327
- | `tabType` | `"1t" \| "2s" \| "4s"` | `"1t"` | Tab type to use when generating source code. |
328
- | `targetPlatform` | `ScaffIntegrationTargetPlatform` | `"all"` | Target platform for the generated code. |
329
- | `useGmAssetPath` | `boolean` | `false` | Whether to use GM asset path when integrating files. |
59
+ ScaffScript is **free** and **open-source**. It's licensed under the [MIT License](./LICENSE).
package/dist/index.cjs CHANGED
@@ -110,17 +110,7 @@ async function parseArgs(...args) {
110
110
  switch (cmd) {
111
111
  case "gen":
112
112
  case "generate":
113
- const path3 = args[1];
114
- if (!path3) {
115
- log.error("No source path specified. Please specify a valid source path. Aborting...");
116
- return null;
117
- }
118
- const target = args[2];
119
- if (target !== "to") {
120
- log.error(`Invalid target: \x1B[33m${target}\x1B[0m. Please replace it with \x1B[32mto\x1B[0m after the source path argument. Aborting...`);
121
- return null;
122
- }
123
- const yypPath = args[3];
113
+ const yypPath = args[1];
124
114
  if (!yypPath) {
125
115
  log.error("No project path specified. Please specify a valid project path. Aborting...");
126
116
  return null;
@@ -128,6 +118,15 @@ async function parseArgs(...args) {
128
118
  log.error(`Invalid project path: \x1B[33m${yypPath}\x1B[0m. Please specify a valid \x1B[32m.yyp\x1B[0m file. Aborting...`);
129
119
  return null;
130
120
  }
121
+ const optionList = [...args].slice(2);
122
+ const options = {
123
+ integrate: optionList.includes("-i") || optionList.includes("--integrate"),
124
+ noIntegration: optionList.includes("-!i") || optionList.includes("--no-integration")
125
+ };
126
+ if (options.integrate && options.noIntegration) {
127
+ log.error("Cannot specify both \x1B[33m--integrate\x1B[0m and \x1B[33m--no-integrate\x1B[0m. Aborting...");
128
+ return null;
129
+ }
131
130
  const exists = await fileExists(normalizePath(resolvePath(yypPath)));
132
131
  if (!exists) {
133
132
  log.error(`Project path \x1B[33m${yypPath}\x1B[0m not found. Aborting...`);
@@ -135,7 +134,7 @@ async function parseArgs(...args) {
135
134
  }
136
135
  return {
137
136
  cmd: "generate",
138
- scanPath: normalizePath(resolvePath(path3)),
137
+ options,
139
138
  projectPath: normalizePath(resolvePath(yypPath))
140
139
  };
141
140
  case "help":
@@ -146,39 +145,15 @@ async function parseArgs(...args) {
146
145
  console.log(`\x1B[34m[args]\x1B[0m: Optional arguments.`);
147
146
  console.log("");
148
147
  console.log("Commands:");
149
- console.log(" gen(erate) <source_path> to <project_path> Generate source code from the given path to the given project");
150
- console.log(" help(-h, --help) Show this help message");
151
- console.log(" init <target_path> [options] Initialize a new ScaffScript project");
152
- console.log(" options:");
153
- console.log(" -t, --template=<template> Specify the template to use (bun, pnpm, npm). Default: npm");
154
- console.log(" --git Initialize a new Git repository");
155
- console.log(" --new Create a new GameMaker project");
148
+ console.log(" generate <source_path> <project_path> Generate source code from the given path to the given project");
149
+ console.log(" aliases: gen");
150
+ console.log(" example: generate ./src ./my-game.yyp");
151
+ console.log(" help Show this help message");
152
+ console.log(" aliases: -h, --help");
156
153
  console.log("");
157
154
  return {
158
155
  cmd: "help"
159
156
  };
160
- case "init":
161
- const targetPath = args[1];
162
- const options = [...args];
163
- options.shift();
164
- const template = options.find((opt) => opt.startsWith("--template") || opt.startsWith("-t"))?.split("=")[1] ?? "npm";
165
- const initGit = options.includes("--git");
166
- const isNew = options.includes("--new");
167
- if (!targetPath) {
168
- log.error("No path specified. Please specify a valid path. Aborting...");
169
- return null;
170
- }
171
- if (!["bun", "pnpm", "npm"].includes(template)) {
172
- log.error(`Invalid template: \x1B[33m${template}\x1B[0m. Please specify a valid template (\x1B[32mbun\x1B[0m or \x1B[32mnode\x1B[0m). Aborting...`);
173
- return null;
174
- }
175
- return {
176
- cmd: "init",
177
- targetPath,
178
- template,
179
- initGit,
180
- isNew
181
- };
182
157
  default:
183
158
  log.error(`Invalid command: \x1B[33m${cmd}\x1B[0m. Aborting...`);
184
159
  return null;
@@ -324,10 +299,11 @@ async function getScaffConfig() {
324
299
  debugLevel: conf.debugLevel ?? 0,
325
300
  integrationOption: conf.integrationOption ?? {},
326
301
  noBackup: conf.noBackup ?? false,
327
- noIntegration: conf.noIntegration ?? false,
302
+ noIntegration: conf.noIntegration ?? true,
328
303
  onNotFound: conf.onNotFound ?? "error",
329
304
  path: conf.path ?? {},
330
305
  production: conf.production ?? false,
306
+ source: conf.source ?? "./src",
331
307
  tabType: conf.tabType ?? "1t",
332
308
  targetPlatform: conf.targetPlatform ?? "all",
333
309
  useGmAssetPath: conf.useGmAssetPath ?? false
@@ -363,30 +339,45 @@ function getTabLevels(str, tabType) {
363
339
  }
364
340
  // package.json
365
341
  var package_default = {
366
- name: "scaffscript",
367
- version: "0.1.2",
368
- description: "A superset language of GML with TypeScript-like module system",
342
+ name: "@scaffscript/core",
343
+ version: "0.1.6",
344
+ repository: {
345
+ type: "git",
346
+ url: "https://github.com/undervolta/scaffscript"
347
+ },
369
348
  main: "dist/index.cjs",
349
+ devDependencies: {
350
+ "@types/bun": "latest"
351
+ },
352
+ peerDependencies: {
353
+ typescript: "^5"
354
+ },
370
355
  bin: {
371
356
  scaff: "./dist/index.cjs"
372
357
  },
373
- type: "module",
374
- files: ["dist"],
358
+ description: "A minimal superset language of GML with TypeScript-like module system",
359
+ files: [
360
+ "dist"
361
+ ],
362
+ keywords: [
363
+ "gamemaker",
364
+ "gml",
365
+ "scaff",
366
+ "script",
367
+ "superset",
368
+ "module",
369
+ "cli"
370
+ ],
371
+ license: "MIT",
375
372
  scripts: {
376
- build: "bun run build:node",
373
+ build: "bun run build:all",
377
374
  "build:node": "bun build src/index-node.ts --outfile dist/index.cjs --target node --format cjs",
378
- "build:bun": "bun build src/index-bun.ts --outfile dist/index.mjs --target bun --format esm",
375
+ "build:bun": "bun build src/index-bun.ts --outfile build/index.mjs --target bun --format esm",
379
376
  "build:all": "bun run build:node && bun run build:bun",
380
- dev: "bun run src/index-node.ts"
381
- },
382
- keywords: ["gamemaker", "gml", "scaff", "script", "superset", "module", "cli"],
383
- license: "MIT",
384
- devDependencies: {
385
- "@types/bun": "latest"
377
+ dev: "bun run src/index-node.ts",
378
+ prelink: "bun run build"
386
379
  },
387
- peerDependencies: {
388
- typescript: "^5"
389
- }
380
+ type: "module"
390
381
  };
391
382
 
392
383
  // src/parser/special-value.ts
@@ -456,6 +447,7 @@ async function readAndSplitFiles(files, config) {
456
447
  continue;
457
448
  }
458
449
  const matchExport = [...file.content.matchAll(modControlRegex)];
450
+ implRegex.lastIndex = 0;
459
451
  if (matchExport.length)
460
452
  exports2.push({ file, depth: file.path.split("/").filter(Boolean).length });
461
453
  else if (implRegex.test(file.content))
@@ -470,6 +462,7 @@ async function readAndSplitFiles(files, config) {
470
462
  exports2.sort((a, b) => b.depth - a.depth);
471
463
  indexes.sort((a, b) => b.depth - a.depth);
472
464
  for (const fileHandle of exports2) {
465
+ implRegex.lastIndex = 0;
473
466
  if (implRegex.test(fileHandle.file.content))
474
467
  implFiles.push(fileHandle.file);
475
468
  else if (fileHandle.file.isScaff && fileHandle.file.toGenerate)
@@ -478,6 +471,7 @@ async function readAndSplitFiles(files, config) {
478
471
  res.scaff.push(fileHandle.file);
479
472
  }
480
473
  for (const fileHandle of indexes) {
474
+ implRegex.lastIndex = 0;
481
475
  if (implRegex.test(fileHandle.file.content))
482
476
  implFiles.push(fileHandle.file);
483
477
  else if (fileHandle.file.isScaff && fileHandle.file.toGenerate)
@@ -846,14 +840,14 @@ ${insertTabs(1, config.tabType)}${classBody}
846
840
  const parsedStr = `${name} = function(${params.combined.join(", ")}) ${arrowBlock}`;
847
841
  if (!module2[filePath])
848
842
  module2[filePath] = {};
849
- module2[filePath][name] = { name, value: arrowBlock.slice(1, -1), type: "method", parsedStr };
843
+ module2[filePath][name] = { name, value: arrowBlock.slice(1, -1), type: "arrow-fn", parsedStr };
850
844
  } else {
851
845
  const params = parseFnParams(valuePart);
852
846
  const body = valuePart.split("=>")[1].trim();
853
847
  const parsedStr = `${name} = function(${params.combined.join(", ")}) { return ${body}; }`;
854
848
  if (!module2[filePath])
855
849
  module2[filePath] = {};
856
- module2[filePath][name] = { name, value: body, type: "method", parsedStr };
850
+ module2[filePath][name] = { name, value: body, type: "arrow-fn", parsedStr };
857
851
  }
858
852
  } else if (valuePart.startsWith("function")) {
859
853
  if (valuePart.trim().endsWith("{")) {
@@ -894,7 +888,7 @@ ${insertTabs(1, config.tabType)}${classBody}
894
888
  if (!module2[filePath])
895
889
  module2[filePath] = {};
896
890
  const parsedStr = varType !== "const" ? `${varType === "let" ? "" : "var "}${name} = ${valuePart};` : `#macro ${name} ${valuePart}`;
897
- module2[filePath][name] = { name, value: valuePart, type: valueType, parsedStr };
891
+ module2[filePath][name] = { name, value: valuePart, type: varType === "const" ? "constant" : "variable", parsedStr };
898
892
  }
899
893
  }
900
894
  }
@@ -991,7 +985,7 @@ function implementClass(module2, fileGroup, config) {
991
985
  if (file.childs.length > 0)
992
986
  file.childs.forEach((child) => toImpl.push({ parent: file, file: child }));
993
987
  }
994
- for (const fileImpl of toImpl) {
988
+ for (const [idx, fileImpl] of toImpl.entries()) {
995
989
  const filePath = fileImpl.parent.isIndex ? fileImpl.parent.path : `${fileImpl.parent.path}/${fileImpl.parent.name}`;
996
990
  const match = parseHeader(fileImpl.file.content);
997
991
  for (const m of match) {
@@ -1002,9 +996,11 @@ function implementClass(module2, fileGroup, config) {
1002
996
  if (!className || !body)
1003
997
  continue;
1004
998
  module2[filePath][className].parsedStr = module2[filePath][className].parsedStr.slice(0, -1) + `${body.replace(`
1005
- `, "")}
999
+ `, "")}` + (idx < toImpl.length - 1 ? `
1000
+
1001
+ ` : `
1006
1002
  }
1007
- `;
1003
+ `);
1008
1004
  }
1009
1005
  }
1010
1006
  return true;
@@ -1588,6 +1584,7 @@ function getEventFile(eventInput, numInput) {
1588
1584
  dynNum = true;
1589
1585
  break;
1590
1586
  case "keydown":
1587
+ case "key_down":
1591
1588
  case "keyboard":
1592
1589
  event = 5 /* KEY_DOWN */;
1593
1590
  break;
@@ -1595,18 +1592,22 @@ function getEventFile(eventInput, numInput) {
1595
1592
  event = 6 /* MOUSE */;
1596
1593
  break;
1597
1594
  case "other":
1595
+ case "async":
1598
1596
  event = 7 /* OTHER */;
1599
1597
  break;
1600
1598
  case "draw":
1601
1599
  event = 8 /* DRAW */;
1602
1600
  break;
1603
1601
  case "keypress":
1602
+ case "key_press":
1604
1603
  event = 9 /* KEY_PRESS */;
1605
1604
  break;
1606
1605
  case "keyrelease":
1606
+ case "key_release":
1607
1607
  event = 10 /* KEY_RELEASE */;
1608
1608
  break;
1609
1609
  case "cleanup":
1610
+ case "clean_up":
1610
1611
  event = 12 /* CLEAN_UP */;
1611
1612
  break;
1612
1613
  case "gesture":
@@ -1943,7 +1944,7 @@ function createYYScript(projectYyp, rescName, dir, options) {
1943
1944
  "name":"${rescName}",
1944
1945
  "parent":{
1945
1946
  "name":"${dir !== "" ? dirSplit.pop() : projectYyp.replace(".yyp", "")}",
1946
- "path":"${dir !== "" ? `folders/${dir}.yy` : projectYyp}",
1947
+ "path":"${dir !== "" ? `folders/${dir}.yy` : `${projectYyp}.yyp`}",
1947
1948
  },
1948
1949
  "resourceType":"GMScript",
1949
1950
  "resourceVersion":"2.0",
@@ -1964,7 +1965,7 @@ function createYYObject(projectYyp, rescName, dir, eventList, options) {
1964
1965
  "overriddenProperties":[],
1965
1966
  "parent":{
1966
1967
  "name":"${dir !== "" ? dirSplit.pop() : projectYyp.replace(".yyp", "")}",
1967
- "path":"${dir !== "" ? `folders/${dir}.yy` : projectYyp}",
1968
+ "path":"${dir !== "" ? `folders/${dir}.yy` : `${projectYyp}.yyp`}",
1968
1969
  },
1969
1970
  "parentObjectId":null,
1970
1971
  "persistent":false,
@@ -2105,9 +2106,9 @@ async function modifyYyProject(type, projectPath, resource = null, folder = null
2105
2106
  if (type === "add") {
2106
2107
  let addedFolderCnt = 0;
2107
2108
  if (toAddFolders) {
2108
- const folderStartIdx = rawLines.findIndex((line) => line.includes("Folders"));
2109
- const folderEndIdx = rawLines.findIndex((line, idx) => idx > folderStartIdx && line.includes("]"));
2110
- const existsFolders = rawLines.slice(folderStartIdx + 1, folderEndIdx);
2109
+ let folderStartIdx = rawLines.findIndex((line) => line.includes("Folders"));
2110
+ let folderEndIdx = rawLines.findIndex((line, idx) => idx >= folderStartIdx && line.includes("]"));
2111
+ const existsFolders = folderEndIdx > folderStartIdx ? rawLines.slice(folderStartIdx + 1, folderEndIdx) : [];
2111
2112
  for (const folderDir of toAddFolders) {
2112
2113
  const folderSplit = folderDir.split("/");
2113
2114
  for (let i = 0;i < folderSplit.length; i++) {
@@ -2118,6 +2119,11 @@ async function modifyYyProject(type, projectPath, resource = null, folder = null
2118
2119
  name = name.slice(0, -1);
2119
2120
  const dupe = existsFolders.find((line) => line.includes(`"%Name":"${name}"`));
2120
2121
  if (!dupe) {
2122
+ if (!existsFolders.length) {
2123
+ rawLines[folderStartIdx] = ' "Folders":[';
2124
+ rawLines.splice(folderStartIdx + 1, 0, " ],");
2125
+ folderEndIdx = folderStartIdx + 1;
2126
+ }
2121
2127
  rawLines.splice(folderEndIdx + addedFolderCnt, 0, createGMFolderStr(name));
2122
2128
  addedFolderCnt++;
2123
2129
  }
@@ -2191,7 +2197,8 @@ async function integrateSourceCodes(genFile, config, projectPath) {
2191
2197
  log.debug(`File \x1B[32m${relPath}\x1B[0m not found. Creating new resource...`);
2192
2198
  const { type, name } = await createGMResource(gmProject, path3, data, config.integrationOption);
2193
2199
  if (type && name) {
2194
- newFolders.push(data.dirPath);
2200
+ if (data.dirPath !== "")
2201
+ newFolders.push(data.dirPath);
2195
2202
  newResources.push(createGMResourceStr(type, name));
2196
2203
  data.isNew = true;
2197
2204
  }
@@ -2210,7 +2217,7 @@ async function integrateSourceCodes(genFile, config, projectPath) {
2210
2217
  await modifyYyProject("add", projectPath, newResources, newFolders);
2211
2218
  if (!config.acceptAllIntegrations) {
2212
2219
  console.log("---");
2213
- log.info(`\x1B[34macceptAllIntegrations\x1B[0m flag is set to \x1B[33mfalse\x1B[0m in the \x1B[32mscaff.config.ts\x1B[0m. Please review the generated source codes before integrating.`);
2220
+ log.info(`\x1B[34macceptAllIntegrations\x1B[0m flag is set to \x1B[33mfalse\x1B[0m in the \x1B[32mscaff.config\x1B[0m. Please review the generated source codes before integrating.`);
2214
2221
  const deleteResources = [];
2215
2222
  for (const [path3, data] of integrations) {
2216
2223
  const pathSlice = path3.split("/");
@@ -2258,7 +2265,7 @@ async function main() {
2258
2265
  case "generate":
2259
2266
  log.debug("Getting Scaff config...");
2260
2267
  const config = await getScaffConfig();
2261
- const files = await getScaffFiles(resolvePath(input.scanPath));
2268
+ const files = await getScaffFiles(resolvePath(config.source));
2262
2269
  log.debug("Processing files...");
2263
2270
  const fileGroup = await readAndSplitFiles(files, config);
2264
2271
  if (!fileGroup) {
@@ -2295,7 +2302,7 @@ async function main() {
2295
2302
  }, []);
2296
2303
  log.debug("Integration data extracted successfully.");
2297
2304
  const genFiles = await generateSourceCode(intgData, config, input.projectPath);
2298
- if (!config.noIntegration) {
2305
+ if (!config.noIntegration && !input.options.noIntegration) {
2299
2306
  log.debug("Integrating source code...");
2300
2307
  const modified = await integrateSourceCodes(genFiles, config, input.projectPath);
2301
2308
  if (modified === null) {
@@ -2310,14 +2317,13 @@ async function main() {
2310
2317
  log.info("Program executed successfully. Thanks for using ScaffScript!");
2311
2318
  } else {
2312
2319
  console.log("---");
2313
- log.info("\x1B[34mnoIntegration\x1B[0m flag is set to \x1B[33mtrue\x1B[0m in the \x1B[32mscaff.config.ts\x1B[0m. No source code will be integrated. Thanks for using ScaffScript!");
2320
+ if (input.options.noIntegration)
2321
+ log.info("\x1B[34m--no-integration\x1B[0m option is set. No source code will be integrated. Thanks for using ScaffScript!");
2322
+ else
2323
+ log.info("\x1B[34mnoIntegration\x1B[0m flag is set to \x1B[33mtrue\x1B[0m in the \x1B[32mscaff.config\x1B[0m. No source code will be integrated. Thanks for using ScaffScript!");
2314
2324
  }
2315
2325
  console.log("");
2316
2326
  break;
2317
- case "init":
2318
- log.info("ScaffScript project initializer hasn't been implemented yet. Please use the installation guide in the documentation.");
2319
- return;
2320
- break;
2321
2327
  }
2322
2328
  console.log("");
2323
2329
  }
@@ -0,0 +1,95 @@
1
+ export type ScaffConfig = {
2
+ /**
3
+ * accept all generated files to be integrated without manual confirmation (default = false)
4
+ */
5
+ acceptAllIntegrations: boolean;
6
+
7
+ /**
8
+ * clear the output directory before generating source code (default = false)
9
+ */
10
+ clearOutputDir: boolean;
11
+
12
+ /**
13
+ * starting value for the counter special value (default = 1)
14
+ */
15
+ counterStart: number;
16
+
17
+ /**
18
+ * debug level (default = 0, 0 = no debug, 1 = basic debug, 2 = verbose debug)
19
+ */
20
+ debugLevel: 0 | 1 | 2;
21
+
22
+ /**
23
+ * integration options
24
+ */
25
+ integrationOption: ScaffIntegrationOptions;
26
+
27
+ /**
28
+ * don't backup the original files before integration (default = false)
29
+ */
30
+ noBackup: boolean;
31
+
32
+ /**
33
+ * don't integrate the files to GM project (default = false)
34
+ */
35
+ noIntegration: boolean;
36
+
37
+ /**
38
+ * what to do when something is not found (default = "error")
39
+ */
40
+ onNotFound: "error" | "ignore";
41
+
42
+ /**
43
+ * path aliases (default = {})
44
+ */
45
+ path: Record<string, string>;
46
+
47
+ /**
48
+ * whether the script is running in production mode (default = false)
49
+ */
50
+ production: boolean;
51
+
52
+ /**
53
+ * source directory (default = "./src")
54
+ */
55
+ source: string;
56
+
57
+ /**
58
+ * tab type to use when generating source code (default = "1t")
59
+ */
60
+ tabType: "1t" | "2s" | "4s";
61
+
62
+ /**
63
+ * target platform for the generated code (default = "all"). only used for tree-shaking purpose
64
+ */
65
+ targetPlatform: ScaffIntegrationTargetPlatform;
66
+
67
+ /**
68
+ * whether to use GM asset path when integrating files (default = false). asset path: `scripts` and `objects`
69
+ */
70
+ useGmAssetPath: boolean;
71
+ };
72
+
73
+ type ScaffIntegrationOptions = Partial<{
74
+ isDnd: boolean;
75
+ }>;
76
+
77
+ type ScaffIntegrationTargetPlatform =
78
+ | "all"
79
+ | "android"
80
+ | "gxgames"
81
+ | "html5"
82
+ | "ios"
83
+ | "linux"
84
+ | "mac"
85
+ | "ps4"
86
+ | "ps5"
87
+ | "reddit"
88
+ | "switch"
89
+ | "switch2"
90
+ | "tvos"
91
+ | "ubuntu"
92
+ | "windows"
93
+ | "xboxone"
94
+ | "xboxseries"
95
+ ;
package/package.json CHANGED
@@ -1,40 +1,41 @@
1
- {
2
- "name": "@scaffscript/core",
3
- "version": "0.1.5",
4
- "description": "A minimal superset language of GML with TypeScript-like module system",
5
- "main": "dist/index.cjs",
6
- "bin": {
7
- "scaff": "./dist/index.cjs"
8
- },
9
- "type": "module",
10
- "files": [
11
- "dist"
12
- ],
13
- "scripts": {
14
- "build": "bun run build:all",
15
- "build:node": "bun build src/index-node.ts --outfile dist/index.cjs --target node --format cjs",
16
- "build:bun": "bun build src/index-bun.ts --outfile build/index.mjs --target bun --format esm",
17
- "build:all": "bun run build:node && bun run build:bun",
18
- "dev": "bun run src/index-node.ts"
19
- },
20
- "keywords": [
21
- "gamemaker",
22
- "gml",
23
- "scaff",
24
- "script",
25
- "superset",
26
- "module",
27
- "cli"
28
- ],
29
- "license": "MIT",
30
- "repository": {
31
- "type": "git",
32
- "url": "https://github.com/undervolta/scaffscript"
33
- },
34
- "devDependencies": {
35
- "@types/bun": "latest"
36
- },
37
- "peerDependencies": {
38
- "typescript": "^5"
39
- }
40
- }
1
+ {
2
+ "name": "@scaffscript/core",
3
+ "version": "0.2.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/undervolta/scaffscript"
7
+ },
8
+ "main": "dist/index.cjs",
9
+ "devDependencies": {
10
+ "@types/bun": "latest"
11
+ },
12
+ "peerDependencies": {
13
+ "typescript": "^5"
14
+ },
15
+ "bin": {
16
+ "scaff": "./dist/index.cjs"
17
+ },
18
+ "description": "A minimal superset language of GML with TypeScript-like module system",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "keywords": [
23
+ "gamemaker",
24
+ "gml",
25
+ "scaff",
26
+ "script",
27
+ "superset",
28
+ "module",
29
+ "cli"
30
+ ],
31
+ "license": "MIT",
32
+ "scripts": {
33
+ "build": "bun run build:all",
34
+ "build:node": "bun build src/index-node.ts --outfile dist/index.cjs --target node --format cjs",
35
+ "build:bun": "bun build src/index-bun.ts --outfile build/index.mjs --target bun --format esm",
36
+ "build:all": "bun run build:node && bun run build:bun",
37
+ "dev": "bun run src/index-node.ts",
38
+ "prelink": "bun run build"
39
+ },
40
+ "type": "module"
41
+ }