@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.
Files changed (2) hide show
  1. package/build.mjs +122 -11
  2. 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
- * esbuild plugin: scans HTML files for local asset references
76
- * (script src, link href, img src) and copies them into dist/.
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
- log("Preparing dist...");
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) from source files`);
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() - start}ms → ${outfile}`);
270
+ logSuccess(`Rebuilt in ${Date.now() - rebuildStart}ms → ${outfile}`);
160
271
  }
161
272
  });
162
273
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotbunny/hackhub-content-sdk",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Type definitions and build tools for HackHub mod development",
5
5
  "types": "index.d.ts",
6
6
  "exports": {