@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.
- package/README.md +110 -0
- package/cli.js +7 -0
- package/index.js +169 -0
- 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
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
|
+
}
|