@chrisivey01/builder 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.
Files changed (4) hide show
  1. package/README.md +110 -0
  2. package/cli.js +7 -0
  3. package/index.js +169 -0
  4. package/package.json +25 -0
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @pma/build
2
+
3
+ A build tool for FiveM/RedM resources with watch mode and auto-restart support.
4
+
5
+ ## Features
6
+
7
+ - šŸ”„ Auto-restart resources on file changes
8
+ - šŸŽ® Support for both FiveM and RedM
9
+ - šŸ“¦ Built-in TypeScript/JavaScript bundling with esbuild
10
+ - 🌐 Web UI dev server integration
11
+ - ⚔ Fast rebuilds with watch mode
12
+ - šŸŽÆ Automatic fxmanifest.lua updates
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add -D @pma/build
18
+ # or
19
+ npm install --save-dev @pma/build
20
+ # or
21
+ yarn add -D @pma/build
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Command Line
27
+
28
+ Add to your `package.json` scripts:
29
+
30
+ ```json
31
+ {
32
+ "scripts": {
33
+ "build": "pma-build",
34
+ "watch": "pma-build --watch",
35
+ "build:redm": "pma-build --redm",
36
+ "watch:redm": "pma-build --watch --redm"
37
+ }
38
+ }
39
+ ```
40
+
41
+ Then run:
42
+
43
+ ```bash
44
+ pnpm build # Build for FiveM
45
+ pnpm watch # Watch mode for FiveM
46
+ pnpm build:redm # Build for RedM
47
+ pnpm watch:redm # Watch mode for RedM
48
+ ```
49
+
50
+ ### Programmatic API
51
+
52
+ ```javascript
53
+ import { build } from '@pma/build';
54
+
55
+ await build({
56
+ resourceName: 'my-resource',
57
+ restartEndpoint: 'http://127.0.0.1:4689/rr',
58
+ restartTimeout: 2000,
59
+ debounceDelay: 500,
60
+ webDevPort: 5173,
61
+ builds: {
62
+ client: { platform: 'browser', target: 'es2021', format: 'iife' },
63
+ server: { platform: 'node', target: 'node16', format: 'cjs' }
64
+ }
65
+ });
66
+ ```
67
+
68
+ ## Configuration
69
+
70
+ Create a `build.config.js` in your project root:
71
+
72
+ ```javascript
73
+ export default {
74
+ restartEndpoint: 'http://127.0.0.1:4689/rr',
75
+ restartTimeout: 2000,
76
+ debounceDelay: 500,
77
+ webDevPort: 5173,
78
+ builds: {
79
+ client: { platform: 'browser', target: 'es2021', format: 'iife' },
80
+ server: { platform: 'node', target: 'node16', format: 'cjs' }
81
+ }
82
+ };
83
+ ```
84
+
85
+ ## GAME Constant
86
+
87
+ The build tool automatically injects a `GAME` constant into your code:
88
+
89
+ ```typescript
90
+ // Available globally in your code
91
+ if (GAME === 'REDM') {
92
+ console.log('Running on RedM');
93
+ } else {
94
+ console.log('Running on FiveM');
95
+ }
96
+ ```
97
+
98
+ Add this to your `types/game.d.ts`:
99
+
100
+ ```typescript
101
+ declare global {
102
+ const GAME: "REDM" | "FIVEM";
103
+ }
104
+
105
+ export {};
106
+ ```
107
+
108
+ ## License
109
+
110
+ MIT
package/cli.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { build } from './index.js';
3
+
4
+ build().catch((error) => {
5
+ console.error('Build failed:', error);
6
+ process.exit(1);
7
+ });
package/index.js ADDED
@@ -0,0 +1,169 @@
1
+ import esbuild from 'esbuild';
2
+ import { execSync, spawn } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import { basename, resolve } from 'path';
5
+
6
+ export async function build(options = {}) {
7
+ const cwd = options.cwd || process.cwd();
8
+ const resource_name = options.resourceName || basename(resolve(cwd));
9
+ const args = options.args || process.argv.slice(2);
10
+ const IS_WATCH = args.includes('--watch');
11
+ const IS_REDM = args.includes('--redm');
12
+ const HAS_WEB_DIR = fs.existsSync(resolve(cwd, './web'));
13
+
14
+ // Configuration defaults
15
+ const config = {
16
+ restartEndpoint: options.restartEndpoint || 'http://127.0.0.1:4689/rr',
17
+ restartTimeout: options.restartTimeout || 2000,
18
+ debounceDelay: options.debounceDelay || 500,
19
+ webDevPort: options.webDevPort || 5173,
20
+ builds: options.builds || {
21
+ client: { platform: 'browser', target: 'es2021', format: 'iife' },
22
+ server: { platform: 'node', target: 'node16', format: 'cjs' },
23
+ },
24
+ ...options
25
+ };
26
+
27
+ console.log(`šŸŽ® Building ${resource_name} for ${IS_REDM ? 'RedM' : 'FiveM'}...`);
28
+
29
+ // Update fxmanifest.lua
30
+ const updateFxManifest = () => {
31
+ const fxmanifestPath = resolve(cwd, './fxmanifest.lua');
32
+ if (!fs.existsSync(fxmanifestPath)) return;
33
+
34
+ const contents = fs.readFileSync(fxmanifestPath, 'utf8');
35
+ let updated = contents;
36
+
37
+ // Update game field for RedM
38
+ if (IS_REDM) {
39
+ updated = updated.replace(/game\s+['"]gta5['"]/, "game 'rdr3'");
40
+ if (!updated.includes('rdr3_warning')) {
41
+ updated = updated.replace(
42
+ /(game\s+['"]rdr3['"])/,
43
+ `$1\nrdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'`
44
+ );
45
+ }
46
+ } else {
47
+ updated = updated.replace(/game\s+['"]rdr3['"]/, "game 'gta5'");
48
+ updated = updated.replace(/\n?rdr3_warning\s+['"][^'"]*['"]\s*\n?/g, '\n');
49
+ }
50
+
51
+ // Update ui_page
52
+ if (!HAS_WEB_DIR) {
53
+ updated = updated.replace(/\n?\s*ui_page\s+['"][^'"]*['"]\s*\n?/g, '\n');
54
+ } else {
55
+ const desiredUiPage = IS_WATCH ? `ui_page 'http://127.0.0.1:${config.webDevPort}'` : "ui_page 'html/index.html'";
56
+ updated = updated.match(/ui_page\s+['"][^'"]*['"]/)
57
+ ? updated.replace(/ui_page\s+['"][^'"]*['"]/, desiredUiPage)
58
+ : `${updated.trimEnd()}\n\n${desiredUiPage}\n`;
59
+ }
60
+
61
+ if (updated !== contents) {
62
+ fs.writeFileSync(fxmanifestPath, updated, 'utf8');
63
+ }
64
+ };
65
+
66
+ updateFxManifest();
67
+
68
+ let lastRebuild = Date.now();
69
+
70
+ // Create restart plugin
71
+ const createRestartPlugin = () => ({
72
+ name: 'restart-resource',
73
+ setup(build) {
74
+ build.onEnd(async (result) => {
75
+ if (result.errors.length) return;
76
+
77
+ const now = Date.now();
78
+ if (now - lastRebuild < config.debounceDelay) return;
79
+
80
+ lastRebuild = now;
81
+ console.log(`\nšŸ“¦ Build completed, restarting ${resource_name}...`);
82
+
83
+ const controller = new AbortController();
84
+ const timeoutId = setTimeout(() => controller.abort(), config.restartTimeout);
85
+
86
+ fetch(`${config.restartEndpoint}?resource=${resource_name}`, {
87
+ signal: controller.signal
88
+ })
89
+ .then((response) => {
90
+ clearTimeout(timeoutId);
91
+ if (response.ok) {
92
+ console.log(`āœ… Restarted ${resource_name}`);
93
+ } else {
94
+ console.error(`āš ļø Restart returned: ${response.status}`);
95
+ }
96
+ })
97
+ .catch((error) => {
98
+ clearTimeout(timeoutId);
99
+ if (error.name === 'AbortError') {
100
+ console.error(`āš ļø Restart timed out (server might not be running)`);
101
+ } else {
102
+ console.error(`āš ļø Restart failed: ${error.message}`);
103
+ }
104
+ });
105
+ });
106
+ },
107
+ });
108
+
109
+ // Build configuration
110
+ const getBuildConfig = (name, buildConfig) => ({
111
+ bundle: true,
112
+ entryPoints: [resolve(cwd, `./${name}/main.ts`)],
113
+ outfile: resolve(cwd, `dist/${name}.js`),
114
+ sourcemap: true,
115
+ logLevel: 'info',
116
+ define: {
117
+ GAME: IS_REDM ? '"REDM"' : '"FIVEM"',
118
+ ...(config.define || {})
119
+ },
120
+ ...buildConfig,
121
+ });
122
+
123
+ if (IS_WATCH) {
124
+ // Watch mode
125
+ await Promise.all(
126
+ Object.entries(config.builds).map(async ([name, buildConfig]) => {
127
+ const ctx = await esbuild.context({
128
+ ...getBuildConfig(name, buildConfig),
129
+ plugins: [createRestartPlugin()],
130
+ });
131
+
132
+ await ctx.watch();
133
+ console.log(`šŸ‘€ Watching ${name}...`);
134
+ })
135
+ );
136
+
137
+ if (HAS_WEB_DIR) {
138
+ console.log('🌐 Starting web dev server...');
139
+ const webProcess = spawn('pnpm', ['dev'], {
140
+ cwd: resolve(cwd, './web'),
141
+ stdio: 'inherit',
142
+ shell: true
143
+ });
144
+
145
+ webProcess.on('error', (error) => {
146
+ console.error('Failed to start web dev server:', error);
147
+ });
148
+ } else {
149
+ console.log('āœ… Watch mode active. Press Ctrl+C to stop.');
150
+ await new Promise(() => { });
151
+ }
152
+ } else {
153
+ // Build mode
154
+ await Promise.all(
155
+ Object.entries(config.builds).map(async ([name, buildConfig]) => {
156
+ await esbuild.build(getBuildConfig(name, buildConfig));
157
+ console.log(`āœ… Built ${name}`);
158
+ })
159
+ );
160
+
161
+ if (HAS_WEB_DIR) {
162
+ console.log('🌐 Building web...');
163
+ execSync('pnpm build', { cwd: resolve(cwd, './web'), stdio: 'inherit' });
164
+ console.log('āœ… Web build completed');
165
+ }
166
+
167
+ process.exit(0);
168
+ }
169
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@chrisivey01/builder",
3
+ "version": "1.0.0",
4
+ "description": "Build tool for FiveM/RedM resources with watch mode and auto-restart",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "pma-build": "./cli.js"
9
+ },
10
+ "keywords": [
11
+ "fivem",
12
+ "redm",
13
+ "build",
14
+ "esbuild",
15
+ "cfx"
16
+ ],
17
+ "author": "Chris Ivey",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "esbuild": "^0.24.2"
21
+ },
22
+ "peerDependencies": {
23
+ "esbuild": "^0.20.0"
24
+ }
25
+ }