@hotbunny/hackhub-content-sdk 0.5.2 → 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 +122 -11
- package/package.json +1 -1
package/build.mjs
CHANGED
|
@@ -4,6 +4,12 @@ import path from "node:path";
|
|
|
4
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
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
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
|
+
|
|
7
13
|
function copyDirSync(src, dest) {
|
|
8
14
|
fs.mkdirSync(dest, { recursive: true });
|
|
9
15
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -40,11 +46,6 @@ function collectFilesRecursive(dir, ext, results = []) {
|
|
|
40
46
|
return results;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
/**
|
|
44
|
-
* Scans all .ts/.tsx files in src/ for relative path strings that
|
|
45
|
-
* reference asset files (images, scripts, styles, etc.) and copies
|
|
46
|
-
* them into dist/ so they're available via mod-asset:// at runtime.
|
|
47
|
-
*/
|
|
48
49
|
function scanSourceAssets() {
|
|
49
50
|
const sourceFiles = collectFilesRecursive("src", [".ts", ".tsx"]);
|
|
50
51
|
let copied = 0;
|
|
@@ -71,10 +72,112 @@ function scanSourceAssets() {
|
|
|
71
72
|
return copied;
|
|
72
73
|
}
|
|
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
|
+
|
|
74
97
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
98
|
+
* Validates project structure and auto-fixes missing/outdated files.
|
|
99
|
+
* Returns a list of actions taken.
|
|
77
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
|
+
|
|
78
181
|
export function htmlAssetsPlugin() {
|
|
79
182
|
const assetPattern = /(?:src|href)\s*=\s*["'](\.[^"']+)["']/g;
|
|
80
183
|
|
|
@@ -107,10 +210,10 @@ export function htmlAssetsPlugin() {
|
|
|
107
210
|
|
|
108
211
|
function log(msg) { process.stdout.write(`\x1b[36m[HackHub]\x1b[0m ${msg}\n`); }
|
|
109
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`); }
|
|
110
214
|
|
|
111
215
|
/**
|
|
112
216
|
* Builds a HackHub mod project.
|
|
113
|
-
* Call this from your esbuild.config.ts — all build logic is handled here.
|
|
114
217
|
*
|
|
115
218
|
* @param {object} [options]
|
|
116
219
|
* @param {string} [options.entryPoint] - Entry file (default: "src/index.ts")
|
|
@@ -122,12 +225,18 @@ export async function buildMod(options = {}) {
|
|
|
122
225
|
const isWatch = process.argv.includes("--watch");
|
|
123
226
|
const start = Date.now();
|
|
124
227
|
|
|
125
|
-
|
|
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
|
+
}
|
|
234
|
+
|
|
126
235
|
prepareDist();
|
|
127
236
|
|
|
128
237
|
const assetCount = scanSourceAssets();
|
|
129
238
|
if (assetCount > 0) {
|
|
130
|
-
log(`Copied ${assetCount} asset(s)
|
|
239
|
+
log(`Copied ${assetCount} asset(s)`);
|
|
131
240
|
}
|
|
132
241
|
|
|
133
242
|
const outfile = options.outfile || "dist/mod.js";
|
|
@@ -152,11 +261,13 @@ export async function buildMod(options = {}) {
|
|
|
152
261
|
{
|
|
153
262
|
name: "rebuild-notify",
|
|
154
263
|
setup(build) {
|
|
264
|
+
let rebuildStart = Date.now();
|
|
265
|
+
build.onStart(() => { rebuildStart = Date.now(); });
|
|
155
266
|
build.onEnd((result) => {
|
|
156
267
|
if (result.errors.length === 0) {
|
|
157
268
|
prepareDist();
|
|
158
269
|
scanSourceAssets();
|
|
159
|
-
logSuccess(`Rebuilt in ${Date.now() -
|
|
270
|
+
logSuccess(`Rebuilt in ${Date.now() - rebuildStart}ms → ${outfile}`);
|
|
160
271
|
}
|
|
161
272
|
});
|
|
162
273
|
},
|