@hotbunny/hackhub-content-sdk 0.5.1 → 0.6.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/build.mjs +196 -7
- package/package.json +1 -1
package/build.mjs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
const ASSET_EXTS = /\.(png|jpg|jpeg|gif|svg|ico|webp|css|js|cur|woff|woff2|ttf|eot|mp3|wav|ogg|mp4|webm|json|pdf)$/i;
|
|
5
|
+
const SRC_ASSET_PATTERN = /["'](\.[^"']+?\.(png|jpg|jpeg|gif|svg|ico|webp|css|js|cur|woff|woff2|ttf|eot|mp3|wav|ogg|mp4|webm|json|pdf))["']/gi;
|
|
6
|
+
|
|
7
|
+
// ─── SDK project schema version ─────────────────────────────────────
|
|
8
|
+
// Bump this when the required project structure changes (new files,
|
|
9
|
+
// new manifest fields, tsconfig options, etc.). buildMod() compares
|
|
10
|
+
// it against the project's saved version and runs migrations.
|
|
11
|
+
const PROJECT_SCHEMA_VERSION = 1;
|
|
12
|
+
|
|
4
13
|
function copyDirSync(src, dest) {
|
|
5
14
|
fs.mkdirSync(dest, { recursive: true });
|
|
6
15
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -24,10 +33,151 @@ function prepareDist() {
|
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
|
|
36
|
+
function collectFilesRecursive(dir, ext, results = []) {
|
|
37
|
+
if (!fs.existsSync(dir)) return results;
|
|
38
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
+
const full = path.join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
collectFilesRecursive(full, ext, results);
|
|
42
|
+
} else if (ext.some(e => entry.name.endsWith(e))) {
|
|
43
|
+
results.push(full);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function scanSourceAssets() {
|
|
50
|
+
const sourceFiles = collectFilesRecursive("src", [".ts", ".tsx"]);
|
|
51
|
+
let copied = 0;
|
|
52
|
+
|
|
53
|
+
for (const file of sourceFiles) {
|
|
54
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
55
|
+
const fileDir = path.dirname(file);
|
|
56
|
+
let match;
|
|
57
|
+
SRC_ASSET_PATTERN.lastIndex = 0;
|
|
58
|
+
|
|
59
|
+
while ((match = SRC_ASSET_PATTERN.exec(content)) !== null) {
|
|
60
|
+
const ref = match[1];
|
|
61
|
+
const absPath = path.resolve(fileDir, ref);
|
|
62
|
+
if (fs.existsSync(absPath)) {
|
|
63
|
+
const rel = ref.startsWith("./") ? ref.slice(2) : ref;
|
|
64
|
+
const dest = path.join("dist", rel);
|
|
65
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
66
|
+
fs.copyFileSync(absPath, dest);
|
|
67
|
+
copied++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return copied;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Required file templates ────────────────────────────────────────
|
|
76
|
+
// Each entry defines a file that must exist in the mod project.
|
|
77
|
+
// If missing, it's auto-created from the template.
|
|
78
|
+
|
|
79
|
+
const REQUIRED_FILES = {
|
|
80
|
+
"src/types.d.ts": {
|
|
81
|
+
content: `declare module "*.html" {\n const value: string;\n export default value;\n}\n`,
|
|
82
|
+
description: "HTML import type declarations",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Fields that must exist in manifest.json (with defaults)
|
|
87
|
+
const REQUIRED_MANIFEST_FIELDS = {
|
|
88
|
+
apiVersion: "1.0",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Required tsconfig.json compilerOptions
|
|
92
|
+
const REQUIRED_TSCONFIG_OPTIONS = {
|
|
93
|
+
experimentalDecorators: true,
|
|
94
|
+
strict: true,
|
|
95
|
+
};
|
|
96
|
+
|
|
27
97
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
98
|
+
* Validates project structure and auto-fixes missing/outdated files.
|
|
99
|
+
* Returns a list of actions taken.
|
|
30
100
|
*/
|
|
101
|
+
function validateProject() {
|
|
102
|
+
const actions = [];
|
|
103
|
+
|
|
104
|
+
// 1. Check required files
|
|
105
|
+
for (const [filePath, spec] of Object.entries(REQUIRED_FILES)) {
|
|
106
|
+
if (!fs.existsSync(filePath)) {
|
|
107
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
108
|
+
fs.writeFileSync(filePath, spec.content, "utf-8");
|
|
109
|
+
actions.push(`Created missing file: ${filePath} (${spec.description})`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 2. Check manifest.json required fields
|
|
114
|
+
if (fs.existsSync("manifest.json")) {
|
|
115
|
+
const raw = fs.readFileSync("manifest.json", "utf-8");
|
|
116
|
+
const manifest = JSON.parse(raw);
|
|
117
|
+
let changed = false;
|
|
118
|
+
|
|
119
|
+
for (const [key, defaultValue] of Object.entries(REQUIRED_MANIFEST_FIELDS)) {
|
|
120
|
+
if (!(key in manifest)) {
|
|
121
|
+
manifest[key] = defaultValue;
|
|
122
|
+
changed = true;
|
|
123
|
+
actions.push(`Added missing manifest field: "${key}" = "${defaultValue}"`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (changed) {
|
|
128
|
+
fs.writeFileSync("manifest.json", JSON.stringify(manifest, null, 4) + "\n", "utf-8");
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
actions.push("WARNING: manifest.json not found!");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3. Check tsconfig.json required options
|
|
135
|
+
if (fs.existsSync("tsconfig.json")) {
|
|
136
|
+
const raw = fs.readFileSync("tsconfig.json", "utf-8");
|
|
137
|
+
let tsconfig;
|
|
138
|
+
try {
|
|
139
|
+
tsconfig = JSON.parse(raw);
|
|
140
|
+
} catch {
|
|
141
|
+
actions.push("WARNING: tsconfig.json has invalid JSON");
|
|
142
|
+
return actions;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!tsconfig.compilerOptions) tsconfig.compilerOptions = {};
|
|
146
|
+
let changed = false;
|
|
147
|
+
|
|
148
|
+
for (const [key, value] of Object.entries(REQUIRED_TSCONFIG_OPTIONS)) {
|
|
149
|
+
if (tsconfig.compilerOptions[key] !== value) {
|
|
150
|
+
tsconfig.compilerOptions[key] = value;
|
|
151
|
+
changed = true;
|
|
152
|
+
actions.push(`Fixed tsconfig option: "${key}" → ${value}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (changed) {
|
|
157
|
+
fs.writeFileSync("tsconfig.json", JSON.stringify(tsconfig, null, 4) + "\n", "utf-8");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 4. Check public/ directory exists
|
|
162
|
+
if (!fs.existsSync("public")) {
|
|
163
|
+
fs.mkdirSync("public", { recursive: true });
|
|
164
|
+
actions.push("Created missing public/ directory");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 5. Update schema version stamp
|
|
168
|
+
const stampFile = "node_modules/.hackhub-schema-version";
|
|
169
|
+
const currentStamp = fs.existsSync(stampFile) ? parseInt(fs.readFileSync(stampFile, "utf-8").trim(), 10) : 0;
|
|
170
|
+
if (currentStamp < PROJECT_SCHEMA_VERSION) {
|
|
171
|
+
fs.mkdirSync(path.dirname(stampFile), { recursive: true });
|
|
172
|
+
fs.writeFileSync(stampFile, String(PROJECT_SCHEMA_VERSION), "utf-8");
|
|
173
|
+
if (currentStamp > 0) {
|
|
174
|
+
actions.push(`Project schema updated: v${currentStamp} → v${PROJECT_SCHEMA_VERSION}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return actions;
|
|
179
|
+
}
|
|
180
|
+
|
|
31
181
|
export function htmlAssetsPlugin() {
|
|
32
182
|
const assetPattern = /(?:src|href)\s*=\s*["'](\.[^"']+)["']/g;
|
|
33
183
|
|
|
@@ -58,9 +208,12 @@ export function htmlAssetsPlugin() {
|
|
|
58
208
|
};
|
|
59
209
|
}
|
|
60
210
|
|
|
211
|
+
function log(msg) { process.stdout.write(`\x1b[36m[HackHub]\x1b[0m ${msg}\n`); }
|
|
212
|
+
function logSuccess(msg) { process.stdout.write(`\x1b[32m[HackHub]\x1b[0m ${msg}\n`); }
|
|
213
|
+
function logWarn(msg) { process.stdout.write(`\x1b[33m[HackHub]\x1b[0m ${msg}\n`); }
|
|
214
|
+
|
|
61
215
|
/**
|
|
62
216
|
* Builds a HackHub mod project.
|
|
63
|
-
* Call this from your esbuild.config.ts — all build logic is handled here.
|
|
64
217
|
*
|
|
65
218
|
* @param {object} [options]
|
|
66
219
|
* @param {string} [options.entryPoint] - Entry file (default: "src/index.ts")
|
|
@@ -70,13 +223,28 @@ export function htmlAssetsPlugin() {
|
|
|
70
223
|
export async function buildMod(options = {}) {
|
|
71
224
|
const esbuild = await import("esbuild");
|
|
72
225
|
const isWatch = process.argv.includes("--watch");
|
|
226
|
+
const start = Date.now();
|
|
227
|
+
|
|
228
|
+
// Validate & auto-fix project structure
|
|
229
|
+
const fixes = validateProject();
|
|
230
|
+
for (const fix of fixes) {
|
|
231
|
+
if (fix.startsWith("WARNING")) logWarn(fix);
|
|
232
|
+
else logWarn(`Auto-fix: ${fix}`);
|
|
233
|
+
}
|
|
73
234
|
|
|
74
235
|
prepareDist();
|
|
75
236
|
|
|
237
|
+
const assetCount = scanSourceAssets();
|
|
238
|
+
if (assetCount > 0) {
|
|
239
|
+
log(`Copied ${assetCount} asset(s)`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const outfile = options.outfile || "dist/mod.js";
|
|
243
|
+
|
|
76
244
|
const config = {
|
|
77
245
|
entryPoints: [options.entryPoint || "src/index.ts"],
|
|
78
246
|
bundle: true,
|
|
79
|
-
outfile
|
|
247
|
+
outfile,
|
|
80
248
|
format: "cjs",
|
|
81
249
|
platform: "neutral",
|
|
82
250
|
target: "es2020",
|
|
@@ -86,11 +254,32 @@ export async function buildMod(options = {}) {
|
|
|
86
254
|
};
|
|
87
255
|
|
|
88
256
|
if (isWatch) {
|
|
89
|
-
const ctx = await esbuild.context(
|
|
257
|
+
const ctx = await esbuild.context({
|
|
258
|
+
...config,
|
|
259
|
+
plugins: [
|
|
260
|
+
...config.plugins,
|
|
261
|
+
{
|
|
262
|
+
name: "rebuild-notify",
|
|
263
|
+
setup(build) {
|
|
264
|
+
let rebuildStart = Date.now();
|
|
265
|
+
build.onStart(() => { rebuildStart = Date.now(); });
|
|
266
|
+
build.onEnd((result) => {
|
|
267
|
+
if (result.errors.length === 0) {
|
|
268
|
+
prepareDist();
|
|
269
|
+
scanSourceAssets();
|
|
270
|
+
logSuccess(`Rebuilt in ${Date.now() - rebuildStart}ms → ${outfile}`);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
});
|
|
90
277
|
await ctx.watch();
|
|
91
|
-
|
|
278
|
+
log("Watching for changes...");
|
|
92
279
|
} else {
|
|
93
280
|
await esbuild.build(config);
|
|
94
|
-
|
|
281
|
+
const elapsed = Date.now() - start;
|
|
282
|
+
const size = (fs.statSync(outfile).size / 1024).toFixed(1);
|
|
283
|
+
logSuccess(`Build complete in ${elapsed}ms → ${outfile} (${size} KB)`);
|
|
95
284
|
}
|
|
96
285
|
}
|