create-hackhub-mod 0.1.1 → 0.2.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/dist/index.js CHANGED
@@ -5,9 +5,22 @@ import * as p from "@clack/prompts";
5
5
  import pc from "picocolors";
6
6
  import fs from "fs";
7
7
  import path from "path";
8
+ import { execSync } from "child_process";
8
9
  import { fileURLToPath } from "url";
9
10
  var __filename = fileURLToPath(import.meta.url);
10
11
  var __dirname = path.dirname(__filename);
12
+ var BANNER = `
13
+ ${pc.cyan(" \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}
14
+ ${pc.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
15
+ ${pc.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
16
+ ${pc.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
17
+ ${pc.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
18
+ ${pc.cyan(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D ")}
19
+ ${pc.dim(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}
20
+ ${pc.dim(" \u2502")} ${pc.green(" >")} ${pc.white("Create HackHub Mod")}${pc.dim(" \u2502")}
21
+ ${pc.dim(" \u2502")} ${pc.dim(" Mod scaffolding tool for HackHub")}${pc.dim(" \u2502")}
22
+ ${pc.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}
23
+ `;
11
24
  function toKebabCase(str) {
12
25
  return str.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
13
26
  }
@@ -28,16 +41,36 @@ function copyDir(src, dest, vars) {
28
41
  copyDir(srcPath, destPath, vars);
29
42
  } else {
30
43
  let content = fs.readFileSync(srcPath, "utf-8");
31
- if (entry.name.endsWith(".hbs") || entry.name.endsWith(".ts") || entry.name.endsWith(".json")) {
44
+ if (entry.name.endsWith(".hbs") || entry.name.endsWith(".ts") || entry.name.endsWith(".html") || entry.name.endsWith(".json")) {
32
45
  content = inject(content, vars);
33
46
  }
34
47
  fs.writeFileSync(destPath, content);
35
48
  }
36
49
  }
37
50
  }
51
+ function detectPackageManager() {
52
+ const ua = process.env.npm_config_user_agent || "";
53
+ if (ua.startsWith("pnpm")) return "pnpm";
54
+ if (ua.startsWith("yarn")) return "yarn";
55
+ if (ua.startsWith("bun")) return "bun";
56
+ try {
57
+ execSync("bun --version", { stdio: "ignore" });
58
+ return "bun";
59
+ } catch {
60
+ }
61
+ try {
62
+ execSync("pnpm --version", { stdio: "ignore" });
63
+ return "pnpm";
64
+ } catch {
65
+ }
66
+ return "npm";
67
+ }
68
+ function installCommand(pm) {
69
+ return pm === "yarn" ? "yarn" : `${pm} install`;
70
+ }
38
71
  async function main() {
39
- console.log();
40
- p.intro(pc.bgCyan(pc.black(" Create HackHub Mod ")));
72
+ console.log(BANNER);
73
+ p.intro(pc.bgCyan(pc.black(" create-hackhub-mod ")));
41
74
  const project = await p.group(
42
75
  {
43
76
  name: () => p.text({
@@ -51,10 +84,10 @@ async function main() {
51
84
  template: () => p.select({
52
85
  message: "Select a template",
53
86
  options: [
54
- { value: "basic", label: "Basic", hint: "Minimal mod setup" },
55
- { value: "quest", label: "Quest", hint: "Includes an example quest" },
56
- { value: "website", label: "Website", hint: "Includes an in-game website" },
57
- { value: "full", label: "Full", hint: "Quest + Website + Command + App" }
87
+ { value: "basic", label: `${pc.bold("Basic")}`, hint: "Minimal mod setup" },
88
+ { value: "quest", label: `${pc.bold("Quest")}`, hint: "Includes an example quest" },
89
+ { value: "website", label: `${pc.bold("Website")}`, hint: "Includes an in-game website" },
90
+ { value: "full", label: `${pc.bold("Full")}`, hint: "Quest + Website + Command + App" }
58
91
  ]
59
92
  }),
60
93
  author: () => p.text({
@@ -94,13 +127,30 @@ async function main() {
94
127
  s.start("Scaffolding mod project...");
95
128
  copyDir(baseDir, targetDir, vars);
96
129
  copyDir(templateDir, targetDir, vars);
97
- s.stop("Project scaffolded.");
98
- const nextSteps = [
99
- `cd ${modId}`,
100
- "npm install",
101
- "npm run build"
102
- ];
103
- p.note(nextSteps.join("\n"), "Next steps");
104
- p.outro(pc.green(`Done! Your mod "${modName}" is ready.`));
130
+ s.stop(pc.green("Project scaffolded."));
131
+ const pm = detectPackageManager();
132
+ s.start(`Installing dependencies with ${pc.cyan(pm)}...`);
133
+ try {
134
+ execSync(installCommand(pm), { cwd: targetDir, stdio: "ignore" });
135
+ s.stop(pc.green("Dependencies installed."));
136
+ } catch {
137
+ s.stop(pc.yellow("Could not install dependencies. Run it manually."));
138
+ }
139
+ console.log();
140
+ console.log(pc.dim(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
141
+ console.log(pc.dim(" \u2502 \u2502"));
142
+ console.log(pc.dim(" \u2502") + pc.green(" \u2714 Your mod is ready!") + pc.dim(" \u2502"));
143
+ console.log(pc.dim(" \u2502 \u2502"));
144
+ console.log(pc.dim(" \u2502") + pc.white(` Project: ${pc.bold(modName)}`) + " ".repeat(Math.max(0, 40 - modName.length)) + pc.dim("\u2502"));
145
+ console.log(pc.dim(" \u2502") + pc.white(` Template: ${pc.bold(template)}`) + " ".repeat(Math.max(0, 40 - template.length)) + pc.dim("\u2502"));
146
+ console.log(pc.dim(" \u2502") + pc.white(` Author: ${pc.bold(author)}`) + " ".repeat(Math.max(0, 40 - author.length)) + pc.dim("\u2502"));
147
+ console.log(pc.dim(" \u2502 \u2502"));
148
+ console.log(pc.dim(" \u2502") + pc.dim(" Next steps:") + pc.dim(" \u2502"));
149
+ console.log(pc.dim(" \u2502") + pc.cyan(` cd ${modId}`) + " ".repeat(Math.max(0, 46 - modId.length)) + pc.dim("\u2502"));
150
+ console.log(pc.dim(" \u2502") + pc.cyan(` ${pm} run build`) + " ".repeat(Math.max(0, 38 - pm.length)) + pc.dim("\u2502"));
151
+ console.log(pc.dim(" \u2502 \u2502"));
152
+ console.log(pc.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
153
+ console.log();
154
+ p.outro(pc.green("Happy hacking! \u{1F513}"));
105
155
  }
106
156
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hackhub-mod",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold a new HackHub mod project with interactive prompts",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,9 @@ const config = {
10
10
  platform: "neutral" as const,
11
11
  target: "es2020",
12
12
  external: ["@hotbunny/hackhub-content-sdk"],
13
+ loader: {
14
+ ".html": "text" as const,
15
+ },
13
16
  };
14
17
 
15
18
  if (isWatch) {
@@ -1,40 +1,18 @@
1
1
  import { App, RegisterApp } from "@hotbunny/hackhub-content-sdk";
2
+ import appHTML from "../html/app.html";
2
3
 
3
4
  @RegisterApp()
4
5
  export class ExampleApp extends App {
5
- definition = {
6
- id: "example-app",
7
- name: "Example App",
8
- icon: "terminal",
9
- html: `
10
- <!DOCTYPE html>
11
- <html>
12
- <head>
13
- <style>
14
- body {
15
- font-family: sans-serif;
16
- background: #1a1a2e;
17
- color: #eee;
18
- padding: 24px;
19
- margin: 0;
20
- }
21
- h2 { color: #e94560; }
22
- button {
23
- background: #e94560;
24
- color: white;
25
- border: none;
26
- padding: 8px 16px;
27
- border-radius: 4px;
28
- cursor: pointer;
29
- }
30
- button:hover { opacity: 0.85; }
31
- </style>
32
- </head>
33
- <body>
34
- <h2>{{modName}} App</h2>
35
- <p>This is a custom in-game application.</p>
36
- <button onclick="alert('Hello from {{modName}}!')">Click me</button>
37
- </body>
38
- </html>`,
6
+
7
+ AppName: string = "{{className}}App";
8
+ Title: string = "{{modName}}";
9
+ Icon: string = "";
10
+ HTML: string = appHTML;
11
+ DefaultSize = { width: 420, height: 320 };
12
+
13
+ Store = {
14
+ title: "{{modName}}",
15
+ ratings: 5.0,
16
+ description: "{{description}}",
39
17
  };
40
18
  }
@@ -1,13 +1,20 @@
1
- import { Command, RegisterCommand } from "@hotbunny/hackhub-content-sdk";
1
+ import { Command, RegisterCommand, CommandTools } from "@hotbunny/hackhub-content-sdk";
2
2
 
3
3
  @RegisterCommand()
4
4
  export class HelloCommand extends Command {
5
- definition = {
6
- name: "hello",
7
- description: "Says hello from {{modName}}",
8
- };
9
5
 
10
- execute(args: string[]) {
11
- return `Hello from {{modName}}! Args: ${args.join(", ")}`;
6
+ CommandName: string = "hello";
7
+ Description: string = "Says hello from {{modName}}";
8
+ Autocomplete = [
9
+ { label: "hello", type: "STRING" as const },
10
+ ];
11
+
12
+ async Run(tools: CommandTools) {
13
+ const args = tools.getArgs();
14
+ if (args.length > 0) {
15
+ tools.println(`Hello, ${args.join(" ")}! From {{modName}}.`);
16
+ } else {
17
+ tools.println("Hello from {{modName}}!");
18
+ }
12
19
  }
13
20
  }
@@ -0,0 +1,37 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style>
5
+ * { margin: 0; padding: 0; box-sizing: border-box; }
6
+ body {
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
8
+ background: #1a1a2e;
9
+ color: #eee;
10
+ padding: 24px;
11
+ }
12
+ h2 {
13
+ color: #e94560;
14
+ margin-bottom: 12px;
15
+ }
16
+ p {
17
+ color: #aaa;
18
+ margin-bottom: 16px;
19
+ }
20
+ button {
21
+ background: #e94560;
22
+ color: white;
23
+ border: none;
24
+ padding: 8px 16px;
25
+ border-radius: 4px;
26
+ cursor: pointer;
27
+ font-size: 14px;
28
+ }
29
+ button:hover { opacity: 0.85; }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <h2>{{modName}}</h2>
34
+ <p>This is a custom in-game application.</p>
35
+ <button onclick="alert('Hello from {{modName}}!')">Click me</button>
36
+ </body>
37
+ </html>
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style>
5
+ * { margin: 0; padding: 0; box-sizing: border-box; }
6
+ body {
7
+ font-family: 'Courier New', monospace;
8
+ background: #0a0a0a;
9
+ color: #00ff41;
10
+ min-height: 100vh;
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: center;
14
+ justify-content: center;
15
+ padding: 24px;
16
+ }
17
+ h1 {
18
+ font-size: 2rem;
19
+ text-shadow: 0 0 10px #00ff41, 0 0 20px #00ff4180;
20
+ margin-bottom: 16px;
21
+ }
22
+ p {
23
+ color: #00ff41aa;
24
+ font-size: 0.9rem;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <h1>{{modName}}</h1>
30
+ <p>Welcome to the website. You shouldn't be here.</p>
31
+ </body>
32
+ </html>
@@ -1,14 +1,29 @@
1
- import { Quest, Network, RegisterQuest } from "@hotbunny/hackhub-content-sdk";
1
+ import { Quest, RegisterQuest, QuestObjectiveDefinition } from "@hotbunny/hackhub-content-sdk";
2
2
 
3
3
  @RegisterQuest()
4
4
  export class ExampleQuest extends Quest {
5
- definition = {
6
- name: "ExampleQuest",
7
- title: "Example Quest",
8
- description: "An example quest from {{modName}}.",
9
- rewards: { money: 100 },
10
- objectives: [
11
- { name: "scan", description: "Scan the target server" },
12
- ],
13
- };
5
+
6
+ Name: string = "ExampleQuest";
7
+ Title: string = "Example Quest";
8
+ Description: string = "An example quest from {{modName}}.";
9
+ Rewards = { money: 100 };
10
+ Objectives: QuestObjectiveDefinition[] = [
11
+ {
12
+ name: "scan",
13
+ description: "Scan the target server",
14
+ },
15
+ {
16
+ name: "exploit",
17
+ description: "Exploit the vulnerability",
18
+ unlocksAfter: ["scan"],
19
+ },
20
+ ];
21
+
22
+ OnStart() {
23
+ console.log("ExampleQuest started!");
24
+ }
25
+
26
+ OnComplete() {
27
+ console.log("ExampleQuest completed!");
28
+ }
14
29
  }
@@ -0,0 +1,4 @@
1
+ declare module "*.html" {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -1,31 +1,13 @@
1
- import { Website, RegisterWebsite } from "@hotbunny/hackhub-content-sdk";
1
+ import { Website, RegisterWebsite, WebsitePageDefinition } from "@hotbunny/hackhub-content-sdk";
2
+ import homePage from "../pages/home.html";
2
3
 
3
4
  @RegisterWebsite()
4
5
  export class ExampleWebsite extends Website {
5
- definition = {
6
- url: "example.mod",
7
- title: "Example Website",
8
- html: `
9
- <!DOCTYPE html>
10
- <html>
11
- <head>
12
- <style>
13
- body {
14
- font-family: monospace;
15
- background: #0a0a0a;
16
- color: #00ff41;
17
- display: flex;
18
- align-items: center;
19
- justify-content: center;
20
- height: 100vh;
21
- margin: 0;
22
- }
23
- h1 { text-shadow: 0 0 10px #00ff41; }
24
- </style>
25
- </head>
26
- <body>
27
- <h1>Welcome to {{modName}}</h1>
28
- </body>
29
- </html>`,
30
- };
6
+
7
+ SiteName: string = "{{modName}}";
8
+ Host: string = "{{modId}}.mod";
9
+ Icon: string = "";
10
+ Pages: WebsitePageDefinition[] = [
11
+ { path: "/", title: "Home", html: homePage },
12
+ ];
31
13
  }
@@ -1,14 +1,29 @@
1
- import { Quest, Network, RegisterQuest } from "@hotbunny/hackhub-content-sdk";
1
+ import { Quest, RegisterQuest, QuestObjectiveDefinition } from "@hotbunny/hackhub-content-sdk";
2
2
 
3
3
  @RegisterQuest()
4
4
  export class ExampleQuest extends Quest {
5
- definition = {
6
- name: "ExampleQuest",
7
- title: "Example Quest",
8
- description: "An example quest from {{modName}}.",
9
- rewards: { money: 100 },
10
- objectives: [
11
- { name: "scan", description: "Scan the target server" },
12
- ],
13
- };
5
+
6
+ Name: string = "ExampleQuest";
7
+ Title: string = "Example Quest";
8
+ Description: string = "An example quest from {{modName}}.";
9
+ Rewards = { money: 100 };
10
+ Objectives: QuestObjectiveDefinition[] = [
11
+ {
12
+ name: "scan",
13
+ description: "Scan the target server",
14
+ },
15
+ {
16
+ name: "exploit",
17
+ description: "Exploit the vulnerability",
18
+ unlocksAfter: ["scan"],
19
+ },
20
+ ];
21
+
22
+ OnStart() {
23
+ console.log("ExampleQuest started!");
24
+ }
25
+
26
+ OnComplete() {
27
+ console.log("ExampleQuest completed!");
28
+ }
14
29
  }
@@ -0,0 +1,37 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style>
5
+ * { margin: 0; padding: 0; box-sizing: border-box; }
6
+ body {
7
+ font-family: 'Courier New', monospace;
8
+ background: #0a0a0a;
9
+ color: #00ff41;
10
+ min-height: 100vh;
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: center;
14
+ justify-content: center;
15
+ padding: 24px;
16
+ }
17
+ h1 {
18
+ font-size: 2rem;
19
+ text-shadow: 0 0 10px #00ff41, 0 0 20px #00ff4180;
20
+ margin-bottom: 16px;
21
+ }
22
+ p {
23
+ color: #00ff41aa;
24
+ font-size: 0.9rem;
25
+ }
26
+ a {
27
+ color: #00bfff;
28
+ text-decoration: none;
29
+ }
30
+ a:hover { text-decoration: underline; }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <h1>{{modName}}</h1>
35
+ <p>Welcome to the website. You shouldn't be here.</p>
36
+ </body>
37
+ </html>
@@ -0,0 +1,4 @@
1
+ declare module "*.html" {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -1,31 +1,13 @@
1
- import { Website, RegisterWebsite } from "@hotbunny/hackhub-content-sdk";
1
+ import { Website, RegisterWebsite, WebsitePageDefinition } from "@hotbunny/hackhub-content-sdk";
2
+ import homePage from "../pages/home.html";
2
3
 
3
4
  @RegisterWebsite()
4
5
  export class ExampleWebsite extends Website {
5
- definition = {
6
- url: "example.mod",
7
- title: "Example Website",
8
- html: `
9
- <!DOCTYPE html>
10
- <html>
11
- <head>
12
- <style>
13
- body {
14
- font-family: monospace;
15
- background: #0a0a0a;
16
- color: #00ff41;
17
- display: flex;
18
- align-items: center;
19
- justify-content: center;
20
- height: 100vh;
21
- margin: 0;
22
- }
23
- h1 { text-shadow: 0 0 10px #00ff41; }
24
- </style>
25
- </head>
26
- <body>
27
- <h1>Welcome to {{modName}}</h1>
28
- </body>
29
- </html>`,
30
- };
6
+
7
+ SiteName: string = "{{modName}}";
8
+ Host: string = "{{modId}}.mod";
9
+ Icon: string = "";
10
+ Pages: WebsitePageDefinition[] = [
11
+ { path: "/", title: "Home", html: homePage },
12
+ ];
31
13
  }