brick-engine-cli 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +75 -0
- package/README.adoc +98 -0
- package/bin/brick-cli.js +12 -0
- package/dist/commands/init/index.d.ts +1 -0
- package/dist/commands/init/index.js +67 -0
- package/dist/commands/init/package/dependencies.d.ts +2 -0
- package/dist/commands/init/package/dependencies.js +12 -0
- package/dist/commands/init/package/devDependencies.d.ts +2 -0
- package/dist/commands/init/package/devDependencies.js +77 -0
- package/dist/commands/init/package/package.d.ts +18 -0
- package/dist/commands/init/package/package.js +43 -0
- package/dist/commands/init/package/scripts.d.ts +2 -0
- package/dist/commands/init/package/scripts.js +37 -0
- package/dist/commands/init/template.d.ts +2 -0
- package/dist/commands/init/template.js +55 -0
- package/dist/commands/init/types.d.ts +15 -0
- package/dist/commands/init/types.js +2 -0
- package/dist/commands/publish/index.d.ts +4 -0
- package/dist/commands/publish/index.js +154 -0
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/constants.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +43 -0
- package/package.json +28 -0
- package/src/commands/init/index.ts +91 -0
- package/src/commands/init/package/dependencies.ts +11 -0
- package/src/commands/init/package/devDependencies.ts +76 -0
- package/src/commands/init/package/package.ts +48 -0
- package/src/commands/init/package/scripts.ts +36 -0
- package/src/commands/init/template.ts +32 -0
- package/src/commands/init/types.ts +17 -0
- package/src/commands/publish/index.ts +201 -0
- package/src/config/constants.ts +2 -0
- package/src/index.ts +39 -0
- package/templates/.prettierrc +8 -0
- package/templates/eslint.config.mjs +18 -0
- package/templates/src/bootstrap.ts +4 -0
- package/templates/src/index.ts +104 -0
- package/templates/tsconfig.json +13 -0
- package/templates/webpack.config.js +98 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import { SUPABASE_URL, SUPABASE_ANON_KEY } from "../../config/constants";
|
|
7
|
+
|
|
8
|
+
export async function publishCommand(options: { url?: string; key?: string }) {
|
|
9
|
+
const currentDir = process.cwd();
|
|
10
|
+
const pkgPath = path.join(currentDir, "package.json");
|
|
11
|
+
const bundlePath = path.join(currentDir, "dist", "game.bundle.js");
|
|
12
|
+
|
|
13
|
+
console.log(
|
|
14
|
+
chalk.bold.blue("\n🧱 Brick Engine CLI") +
|
|
15
|
+
chalk.gray(" - Publishing your masterpiece\n"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(pkgPath)) {
|
|
19
|
+
console.error(
|
|
20
|
+
chalk.red(
|
|
21
|
+
"✖ Error: No package.json found. Are you in a Brick Engine project directory?",
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The bundle path will be checked after the build step now
|
|
28
|
+
|
|
29
|
+
// Resolve Supabase URL and Key
|
|
30
|
+
let supabaseUrl = options.url || process.env.SUPABASE_URL;
|
|
31
|
+
if (!supabaseUrl && SUPABASE_URL && !SUPABASE_URL.startsWith("%%")) {
|
|
32
|
+
supabaseUrl = SUPABASE_URL;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let supabaseAnonKey = options.key || process.env.SUPABASE_ANON_KEY;
|
|
36
|
+
if (
|
|
37
|
+
!supabaseAnonKey &&
|
|
38
|
+
SUPABASE_ANON_KEY &&
|
|
39
|
+
!SUPABASE_ANON_KEY.startsWith("%%")
|
|
40
|
+
) {
|
|
41
|
+
supabaseAnonKey = SUPABASE_ANON_KEY;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const prompts = [];
|
|
45
|
+
if (!supabaseUrl) {
|
|
46
|
+
prompts.push({
|
|
47
|
+
type: "input",
|
|
48
|
+
name: "supabaseUrl",
|
|
49
|
+
message: "Enter your Supabase Project URL:",
|
|
50
|
+
validate: (input: string) =>
|
|
51
|
+
input.length > 0 ? true : "Supabase URL is required.",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!supabaseAnonKey) {
|
|
56
|
+
prompts.push({
|
|
57
|
+
type: "password",
|
|
58
|
+
name: "supabaseAnonKey",
|
|
59
|
+
message: "Enter your Supabase Anon Key:",
|
|
60
|
+
validate: (input: string) =>
|
|
61
|
+
input.length > 0 ? true : "Supabase Anon Key is required.",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
prompts.push({
|
|
66
|
+
type: "input",
|
|
67
|
+
name: "gameName",
|
|
68
|
+
message: "Enter your game name:",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
prompts.push({
|
|
72
|
+
type: "input",
|
|
73
|
+
name: "developerEmail",
|
|
74
|
+
message:
|
|
75
|
+
"Enter your developer email (to receive approval/rejection reasons):",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const pkg = await fs.readJson(pkgPath);
|
|
79
|
+
const gameId = pkg.name;
|
|
80
|
+
const version = pkg.version;
|
|
81
|
+
|
|
82
|
+
prompts.push({
|
|
83
|
+
type: "confirm",
|
|
84
|
+
name: "confirmPublish",
|
|
85
|
+
message: `Ready to publish ${chalk.cyan(gameId)} v${version}?`,
|
|
86
|
+
default: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const answers = await inquirer.prompt(prompts);
|
|
90
|
+
const gameName = answers.gameName;
|
|
91
|
+
|
|
92
|
+
if (!answers.confirmPublish) {
|
|
93
|
+
console.log(chalk.yellow("\nPublishing cancelled."));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
supabaseUrl = supabaseUrl || answers.supabaseUrl;
|
|
98
|
+
supabaseAnonKey = supabaseAnonKey || answers.supabaseAnonKey;
|
|
99
|
+
const developerEmail = pkg.author || answers.developerEmail;
|
|
100
|
+
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(
|
|
103
|
+
`${chalk.green("✔")} Preparing to publish ${chalk.cyan(gameName)} v${version}...`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const formData = new FormData();
|
|
107
|
+
formData.append("game_id", gameId);
|
|
108
|
+
formData.append("game_name", gameName);
|
|
109
|
+
formData.append("version", version);
|
|
110
|
+
if (developerEmail) {
|
|
111
|
+
formData.append("developer_email", developerEmail);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(chalk.gray("\n Running build script..."));
|
|
115
|
+
try {
|
|
116
|
+
execSync("rm -rf dist && npm run build:bundle", {
|
|
117
|
+
stdio: "inherit",
|
|
118
|
+
cwd: currentDir,
|
|
119
|
+
});
|
|
120
|
+
console.log(`${chalk.green("✔")} Build completed successfully.\n`);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(
|
|
123
|
+
chalk.red(
|
|
124
|
+
"\n✖ Error: Build failed. Please fix the errors in your project before publishing.",
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!fs.existsSync(bundlePath)) {
|
|
131
|
+
console.error(
|
|
132
|
+
chalk.red(
|
|
133
|
+
"✖ Error: Game bundle not found. The build script did not generate 'dist/game.bundle.js'.",
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const bundleBuffer = await fs.readFile(bundlePath);
|
|
140
|
+
const bundleBlob = new Blob([bundleBuffer], {
|
|
141
|
+
type: "application/javascript",
|
|
142
|
+
});
|
|
143
|
+
formData.append("bundle", bundleBlob, "game.bundle.js");
|
|
144
|
+
|
|
145
|
+
const endpoint = `${supabaseUrl}/functions/v1/publish`;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetch(endpoint, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
Authorization: `Bearer ${supabaseAnonKey}`,
|
|
152
|
+
},
|
|
153
|
+
body: formData,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
const errorText = await response.text();
|
|
158
|
+
let errorMessage = errorText;
|
|
159
|
+
try {
|
|
160
|
+
const errorJson = JSON.parse(errorText);
|
|
161
|
+
errorMessage = errorJson.error || errorJson.message || errorText;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
// use raw text
|
|
164
|
+
}
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Failed to publish: ${response.status} ${response.statusText}\n${errorMessage}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result = await response.json();
|
|
171
|
+
console.log(`${chalk.green("✔")} Bundle uploaded securely.`);
|
|
172
|
+
|
|
173
|
+
console.log(`\n✨ ${chalk.bold.green("Success!")} ${result.message}\n`);
|
|
174
|
+
|
|
175
|
+
if (result.request && result.request.id) {
|
|
176
|
+
console.log(chalk.bold("Publish Details:"));
|
|
177
|
+
console.log(chalk.gray("───────────────────────────────────"));
|
|
178
|
+
console.log(` Request ID: ${chalk.cyan(result.request.id)}`);
|
|
179
|
+
console.log(` Game ID: ${chalk.cyan(gameId)}`);
|
|
180
|
+
console.log(` Game Name: ${chalk.cyan(gameName)}`);
|
|
181
|
+
console.log(` Version: ${chalk.cyan(version)}`);
|
|
182
|
+
console.log(` Bundle: ${chalk.cyan(result.request.bundle_url)}`);
|
|
183
|
+
console.log(` Status: ${chalk.yellow("Under Review")}`);
|
|
184
|
+
console.log(chalk.gray("───────────────────────────────────\n"));
|
|
185
|
+
|
|
186
|
+
console.log(chalk.blue("ℹ️ Your game is now under review."));
|
|
187
|
+
if (developerEmail) {
|
|
188
|
+
console.log(
|
|
189
|
+
chalk.blue(
|
|
190
|
+
`An email will be sent to ${chalk.bold(
|
|
191
|
+
developerEmail,
|
|
192
|
+
)} informing if it was approved or rejected (with reasons).`,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (error: any) {
|
|
198
|
+
console.error(chalk.red(`\n✖ Publish failed: ${error.message}`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const SUPABASE_URL = "https://uiexmpkqvbkprhifiyaj.supabase.co";
|
|
2
|
+
export const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVpZXhtcGtxdmJrcHJoaWZpeWFqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzE4NTY3ODIsImV4cCI6MjA4NzQzMjc4Mn0.OHI8RBANokqtgRBytBTO0FiB_p_JHjq0mW2wwfzlZDs";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import { initCommand } from "./commands/init";
|
|
3
|
+
import { publishCommand } from "./commands/publish";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
export function run() {
|
|
7
|
+
program
|
|
8
|
+
.name("brick-engine-cli")
|
|
9
|
+
.description("CLI to scaffold Brick Engine projects")
|
|
10
|
+
.version("1.0.0");
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command("init <name>")
|
|
14
|
+
.description("Initialize a new Brick Engine project")
|
|
15
|
+
.action(async (name) => {
|
|
16
|
+
try {
|
|
17
|
+
await initCommand(name);
|
|
18
|
+
} catch (error: any) {
|
|
19
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.command("publish")
|
|
26
|
+
.description("Publish your game to the Brick Engine Artifactory")
|
|
27
|
+
.option("-u, --url <url>", "Supabase URL")
|
|
28
|
+
.option("-k, --key <key>", "Supabase Anon Key")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
try {
|
|
31
|
+
await publishCommand(options);
|
|
32
|
+
} catch (error: any) {
|
|
33
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program.parse(process.argv);
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import globals from 'globals';
|
|
2
|
+
import pluginJs from '@eslint/js';
|
|
3
|
+
import tseslint from 'typescript-eslint';
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: ['dist/', 'node_modules/'],
|
|
8
|
+
},
|
|
9
|
+
// Base config for all files
|
|
10
|
+
pluginJs.configs.recommended,
|
|
11
|
+
...tseslint.configs.recommended,
|
|
12
|
+
|
|
13
|
+
// Browser globals for most files
|
|
14
|
+
{
|
|
15
|
+
files: ['**/*.{js,ts}'],
|
|
16
|
+
languageOptions: { globals: globals.browser },
|
|
17
|
+
},
|
|
18
|
+
];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Game, FontSize, ControlKey, ControlEventType, Sound, FontAlign, FontVerticalAlign, Color } from 'brick-engine-js';
|
|
2
|
+
|
|
3
|
+
export default class MyGame extends Game {
|
|
4
|
+
y = 0;
|
|
5
|
+
x = 5;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Called once after the engine and its modules are fully initialized.
|
|
9
|
+
* Ideal for setting initial state, text sizes, and subscribing to controls.
|
|
10
|
+
*/
|
|
11
|
+
setupGame(): void {
|
|
12
|
+
const { grid, control, sound } = this.modules;
|
|
13
|
+
|
|
14
|
+
// Example: Subscriber to ACTION button
|
|
15
|
+
control.subscribe(ControlKey.ACTION, ControlEventType.PRESSED, () => {
|
|
16
|
+
sound.play(Sound.ACTION_1);
|
|
17
|
+
grid.stampCell({
|
|
18
|
+
coordinate: { x: this.x, y: this.y },
|
|
19
|
+
color: Color.CYAN,
|
|
20
|
+
value: 1,
|
|
21
|
+
});
|
|
22
|
+
this.modules.time.decrementTickInterval(this.y);
|
|
23
|
+
this.y++;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.modules.session.register({
|
|
27
|
+
serialId: 'coords',
|
|
28
|
+
deserialize: data => {
|
|
29
|
+
const { x, y } = JSON.parse(data);
|
|
30
|
+
this.x = x;
|
|
31
|
+
this.y = y;
|
|
32
|
+
},
|
|
33
|
+
serialize: () => {
|
|
34
|
+
return JSON.stringify({ x: this.x, y: this.y });
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Called on every logic "tick". The frequency is defined by the game's tickInterval.
|
|
41
|
+
* This is where movement, collision, and state updates should happen.
|
|
42
|
+
* Logic here ONLY runs when the game is in the PLAYING state.
|
|
43
|
+
*/
|
|
44
|
+
update(deltaTime: number): void {
|
|
45
|
+
const actualCoordinate = { x: this.x, y: this.y };
|
|
46
|
+
|
|
47
|
+
if (!this.modules.grid.isValidCoordinate(actualCoordinate)) {
|
|
48
|
+
this.modules.state.triggerGameOver();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.modules.score.increaseScore(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called every rendering frame (60fps).
|
|
56
|
+
* Used for visual-only effects that don't impact game state (e.g. animations).
|
|
57
|
+
* Runs during PLAYING and PAUSED states.
|
|
58
|
+
*/
|
|
59
|
+
render(): void {
|
|
60
|
+
const { text } = this.modules;
|
|
61
|
+
|
|
62
|
+
text.setTextSize(FontSize.EXTRA_SMALL);
|
|
63
|
+
text.setTextAlign(FontAlign.LEFT, FontVerticalAlign.TOP);
|
|
64
|
+
text.textOnDisplay('press', { x: 0.05, y: 0.05 });
|
|
65
|
+
text.textOnDisplay('action', { x: 0.05, y: 0.1 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Rendered every frame when the device is ON but the game hasn't started.
|
|
70
|
+
* Usually displays the title and "Press Start" instructions.
|
|
71
|
+
*/
|
|
72
|
+
drawTitleScreen(): void {
|
|
73
|
+
const { text } = this.modules;
|
|
74
|
+
|
|
75
|
+
text.setTextSize(FontSize.MEDIUM);
|
|
76
|
+
text.setTextAlign(FontAlign.CENTER, FontVerticalAlign.CENTER);
|
|
77
|
+
text.textOnDisplay('MY GAME', { x: 0.5, y: 0.2 });
|
|
78
|
+
|
|
79
|
+
text.setTextSize(FontSize.SMALL);
|
|
80
|
+
text.textOnDisplay('running on', { x: 0.5, y: 0.48 });
|
|
81
|
+
text.textOnDisplay('drawTitleScreen', { x: 0.5, y: 0.55 });
|
|
82
|
+
|
|
83
|
+
text.setTextSize(FontSize.MEDIUM);
|
|
84
|
+
text.textOnDisplay('PRESS START', { x: 0.5, y: 0.8 });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Rendered every frame when the game state reaches GAME OVER.
|
|
89
|
+
* Replaces standard `render()` calls to show scores or restart messages.
|
|
90
|
+
* Usually displays the game over message and "Press START to restart" instructions, and the final score.
|
|
91
|
+
*/
|
|
92
|
+
drawGameOverScreen(): void {
|
|
93
|
+
this.modules.text.setTextAlign(FontAlign.CENTER, FontVerticalAlign.CENTER);
|
|
94
|
+
|
|
95
|
+
this.modules.text.textOnDisplay('GAME OVER', { x: 0.5, y: 0.5 });
|
|
96
|
+
|
|
97
|
+
this.modules.text.setTextSize(FontSize.MEDIUM);
|
|
98
|
+
this.modules.text.textOnDisplay('PRESS START', { x: 0.5, y: 0.8 });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getGameId() {
|
|
102
|
+
return 'tetris';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "./dist/",
|
|
4
|
+
"noImplicitAny": true,
|
|
5
|
+
"module": "es6",
|
|
6
|
+
"target": "es5",
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*", "global.d.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import webpack from 'webpack';
|
|
4
|
+
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
|
5
|
+
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
|
6
|
+
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
export default (env = {}, argv) => {
|
|
14
|
+
const isProduction = argv.mode === 'production';
|
|
15
|
+
const bundleMode = env.bundle || 'bundle'; // 'standalone' or 'bundle'
|
|
16
|
+
|
|
17
|
+
// Dynamically find the engine root using require.resolve
|
|
18
|
+
const engineRoot = path.dirname(require.resolve('brick-engine-js/package.json'));
|
|
19
|
+
|
|
20
|
+
const config = {
|
|
21
|
+
mode: isProduction ? 'production' : 'development',
|
|
22
|
+
// In 'bundle' mode, we bundle ONLY the game class
|
|
23
|
+
// In 'standalone' mode, we load the engine with the user's game
|
|
24
|
+
entry: {
|
|
25
|
+
app: path.resolve(__dirname, 'src/bootstrap.ts'),
|
|
26
|
+
},
|
|
27
|
+
output: {
|
|
28
|
+
filename: 'game.bundle.js',
|
|
29
|
+
path: path.resolve(__dirname, 'dist'),
|
|
30
|
+
clean: true,
|
|
31
|
+
},
|
|
32
|
+
devtool: isProduction ? false : 'source-map',
|
|
33
|
+
module: {
|
|
34
|
+
rules: [
|
|
35
|
+
{
|
|
36
|
+
test: /\.tsx?$/,
|
|
37
|
+
use: 'ts-loader',
|
|
38
|
+
exclude: /node_modules\/(?!brick-engine-js)/,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
test: /\.css$/i,
|
|
42
|
+
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
resolve: {
|
|
47
|
+
extensions: ['.tsx', '.ts', '.js'],
|
|
48
|
+
symlinks: true,
|
|
49
|
+
alias: {
|
|
50
|
+
'brick-engine-js': path.resolve(engineRoot, 'dist/game.bundle.js'),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
externals: {
|
|
54
|
+
p5: 'p5',
|
|
55
|
+
},
|
|
56
|
+
plugins: [
|
|
57
|
+
new webpack.DefinePlugin({
|
|
58
|
+
'process.env.APP_MODE': JSON.stringify('client'),
|
|
59
|
+
'process.env.SUPABASE_URL': JSON.stringify(process.env.SUPABASE_URL || ''),
|
|
60
|
+
'process.env.SUPABASE_ANON_KEY': JSON.stringify(process.env.SUPABASE_ANON_KEY || ''),
|
|
61
|
+
}),
|
|
62
|
+
new MiniCssExtractPlugin({
|
|
63
|
+
filename: 'style.css',
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
devServer: {
|
|
67
|
+
static: path.resolve(__dirname, 'dist'),
|
|
68
|
+
port: 8080,
|
|
69
|
+
open: true,
|
|
70
|
+
hot: true,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (bundleMode === 'standalone') {
|
|
75
|
+
// Plugins only for standalone mode
|
|
76
|
+
config.plugins.push(
|
|
77
|
+
new HtmlWebpackPlugin({
|
|
78
|
+
template: path.resolve(engineRoot, 'dist/index.html'),
|
|
79
|
+
favicon: path.resolve(engineRoot, 'dist/favicon.ico'),
|
|
80
|
+
inject: false,
|
|
81
|
+
}),
|
|
82
|
+
new CopyWebpackPlugin({
|
|
83
|
+
patterns: [
|
|
84
|
+
{
|
|
85
|
+
from: path.resolve(engineRoot, 'dist/vendor/p5.min.js'),
|
|
86
|
+
to: 'vendor/p5.min.js',
|
|
87
|
+
},
|
|
88
|
+
{ from: path.resolve(engineRoot, 'dist/css'), to: 'css' },
|
|
89
|
+
{ from: path.resolve(engineRoot, 'dist/images'), to: 'images' },
|
|
90
|
+
{ from: path.resolve(engineRoot, 'dist/sounds'), to: 'sounds' },
|
|
91
|
+
{ from: path.resolve(engineRoot, 'dist/fonts'), to: 'fonts' },
|
|
92
|
+
],
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return config;
|
|
98
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|