@metaverse-systems/the-seed 1.0.4 → 1.2.1

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 (56) hide show
  1. package/.eslintrc.json +1 -0
  2. package/.vscode/settings.json +14 -0
  3. package/README.md +530 -0
  4. package/dist/Build.d.ts +10 -0
  5. package/dist/Build.js +59 -16
  6. package/dist/Build.js.map +1 -1
  7. package/dist/Config.d.ts +1 -1
  8. package/dist/Config.js +2 -4
  9. package/dist/Config.js.map +1 -1
  10. package/dist/Dependencies.d.ts +14 -0
  11. package/dist/Dependencies.js +104 -0
  12. package/dist/Dependencies.js.map +1 -0
  13. package/dist/Package.d.ts +45 -0
  14. package/dist/Package.js +249 -0
  15. package/dist/Package.js.map +1 -0
  16. package/dist/ResourcePak.d.ts +2 -2
  17. package/dist/ResourcePak.js +19 -18
  18. package/dist/ResourcePak.js.map +1 -1
  19. package/dist/Scopes.d.ts +9 -6
  20. package/dist/Scopes.js.map +1 -1
  21. package/dist/Template.d.ts +2 -1
  22. package/dist/Template.js +18 -16
  23. package/dist/Template.js.map +1 -1
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.js +5 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/scripts/DependenciesCLI.d.ts +3 -0
  28. package/dist/scripts/DependenciesCLI.js +58 -0
  29. package/dist/scripts/DependenciesCLI.js.map +1 -0
  30. package/dist/scripts/PackageCLI.d.ts +3 -0
  31. package/dist/scripts/PackageCLI.js +38 -0
  32. package/dist/scripts/PackageCLI.js.map +1 -0
  33. package/dist/scripts/the-seed.js +13 -0
  34. package/dist/scripts/the-seed.js.map +1 -1
  35. package/dist/types.d.ts +27 -3
  36. package/native/binding.gyp +20 -0
  37. package/native/src/addon.cpp +60 -0
  38. package/package.json +4 -1
  39. package/src/Build.ts +71 -17
  40. package/src/Config.ts +3 -5
  41. package/src/Dependencies.ts +107 -0
  42. package/src/Package.ts +287 -0
  43. package/src/ResourcePak.ts +12 -11
  44. package/src/Scopes.ts +4 -4
  45. package/src/Template.ts +10 -9
  46. package/src/index.ts +13 -2
  47. package/src/scripts/DependenciesCLI.ts +57 -0
  48. package/src/scripts/PackageCLI.ts +42 -0
  49. package/src/scripts/the-seed.ts +13 -0
  50. package/src/types.ts +29 -3
  51. package/test/Build.test.ts +223 -0
  52. package/test/Config.test.ts +1 -1
  53. package/test/Dependencies.test.ts +153 -0
  54. package/test/Package.test.ts +631 -0
  55. package/test/ResourcePak.test.ts +106 -24
  56. package/test/Template.test.ts +187 -0
package/src/Template.ts CHANGED
@@ -1,9 +1,9 @@
1
- const fs = require("fs-extra");
2
- const { execSync } = require("child_process");
1
+ import fs from "fs-extra";
2
+ import { execSync } from "child_process";
3
3
  import path from "path";
4
- import os from "os";
5
4
  import Config from "./Config";
6
5
  import Scopes from "./Scopes";
6
+ import { PackageType } from "./types";
7
7
 
8
8
  const build_command = "the-seed build native";
9
9
  const build_win64_command = "the-seed build windows";
@@ -13,7 +13,7 @@ class Template {
13
13
  packageDir = "";
14
14
  config: Config;
15
15
  scopes: Scopes;
16
- package: any;
16
+ package?: PackageType;
17
17
 
18
18
  constructor(config: Config) {
19
19
  this.config = config;
@@ -36,17 +36,18 @@ class Template {
36
36
  };
37
37
 
38
38
  copyTemplate = (scope: string, name: string) => {
39
- const templateDir = path.join(path.dirname(require.main!.filename), "../../templates/" + this.type);
39
+ const templateDir = path.join(__dirname, "..", "templates", this.type);
40
40
  const scopeDir = this.config.config.prefix + "/projects/" + scope;
41
41
 
42
42
  const underscoreRegex = new RegExp("-", "g");
43
43
  const underscoreName = name.replace(underscoreRegex, "_");
44
44
 
45
45
  if(!fs.existsSync(scopeDir)) {
46
- fs.mkdirSync(scopeDir);
46
+ fs.mkdirSync(scopeDir, { recursive: true });
47
47
  }
48
48
 
49
49
  // Create package from template
50
+ console.log("Copying template from " + templateDir + " to " + this.packageDir);
50
51
  fs.copySync(templateDir, this.packageDir);
51
52
 
52
53
  // Replace template variables with real values
@@ -90,14 +91,13 @@ class Template {
90
91
  };
91
92
 
92
93
  createPackage = (scope: string, name: string) => {
93
- const scopeDir = this.config.config.prefix + "/projects/" + scope;
94
- this.packageDir = scopeDir + "/" + name;
94
+ this.packageDir = name;
95
95
  this.copyTemplate(scope, name);
96
96
 
97
97
  // Create default package.json
98
98
  execSync("npm init --yes", { cwd: this.packageDir });
99
99
 
100
- this.package = JSON.parse(fs.readFileSync(this.packageDir + "/package.json"));
100
+ this.package = JSON.parse(fs.readFileSync(this.packageDir + "/package.json").toString()) as PackageType;
101
101
  this.package.author = this.scopes.getScope(scope).author;
102
102
  this.package.license = "UNLICENSED";
103
103
  this.package.name = scope + "/" + name;
@@ -112,6 +112,7 @@ class Template {
112
112
  };
113
113
 
114
114
  save = () => {
115
+ if (!this.package) return;
115
116
  fs.writeFileSync(this.packageDir + "/package.json", JSON.stringify(this.package, null, 2));
116
117
  };
117
118
  }
package/src/index.ts CHANGED
@@ -1,12 +1,23 @@
1
1
  import Config from "./Config";
2
2
  import Scopes from "./Scopes";
3
- import { AuthorType, ConfigType, ScopeType, ScopesType } from "./types";
3
+ import Package from "./Package";
4
+ import Build from "./Build";
5
+ import { AuthorType, ConfigType, ScopeType, ScopesType, ScopeAnswersType, ScopeDefaultsType, ResourceType, PackageType, ScriptArgsType, DependencyResultType, BuildStep } from "./types";
4
6
 
5
7
  export {
6
8
  Config,
7
9
  Scopes,
10
+ Package,
11
+ Build,
8
12
  AuthorType,
9
13
  ConfigType,
10
14
  ScopeType,
11
- ScopesType
15
+ ScopesType,
16
+ ScopeAnswersType,
17
+ ScopeDefaultsType,
18
+ ResourceType,
19
+ PackageType,
20
+ ScriptArgsType,
21
+ DependencyResultType,
22
+ BuildStep
12
23
  };
@@ -0,0 +1,57 @@
1
+ import { checkLibEcs, checkLibTheSeed, installLibEcs, installLibTheSeed } from "../Dependencies";
2
+ import Config from "../Config";
3
+ import { ScriptArgsType } from "../types";
4
+
5
+ const DependenciesCLI = (scriptConfig: ScriptArgsType) => {
6
+ const config = new Config(scriptConfig.configDir);
7
+
8
+ const command = scriptConfig.args[3];
9
+ const target = scriptConfig.args[4] ? scriptConfig.args[4] : "native";
10
+
11
+ switch (command) {
12
+ case "help":
13
+ console.log("\nUsage: the-seed dependencies <command>");
14
+ console.log("\nAvailable commands:");
15
+ console.log(" help - Show this help message");
16
+ console.log(" check - Check if dependencies are installed");
17
+ break;
18
+ case "check":
19
+ if (checkLibEcs(config, target)) {
20
+ console.log("libecs-cpp is installed.");
21
+ } else {
22
+ console.log("libecs-cpp is not installed.");
23
+ }
24
+ if (checkLibTheSeed(config, target)) {
25
+ console.log("libthe-seed is installed.");
26
+ } else {
27
+ console.log("libthe-seed is not installed.");
28
+ }
29
+ break;
30
+ case "install":
31
+ if (!checkLibEcs(config, target)) {
32
+ console.log("Installing libecs-cpp...");
33
+ if (installLibEcs(config, target)) {
34
+ console.log("libecs-cpp installed successfully.");
35
+ } else {
36
+ console.error("Failed to install libecs-cpp.");
37
+ }
38
+ }
39
+ if (!checkLibTheSeed(config, target)) {
40
+ console.log("Installing libthe-seed...");
41
+ if (installLibTheSeed(config, target)) {
42
+ console.log("libthe-seed installed successfully.");
43
+ } else {
44
+ console.error("Failed to install libthe-seed.");
45
+ }
46
+ }
47
+ break;
48
+ default:
49
+ console.log(
50
+ "Invalid command. Use 'the-seed dependencies help' for usage information."
51
+ );
52
+ break;
53
+ }
54
+ return true;
55
+ };
56
+
57
+ export default DependenciesCLI;
@@ -0,0 +1,42 @@
1
+ import Config from "../Config";
2
+ import Package from "../Package";
3
+ import { ScriptArgsType } from "../types";
4
+
5
+ const PackageCLI = (scriptConfig: ScriptArgsType) => {
6
+ const command = scriptConfig.args[3];
7
+
8
+ // No arguments or help subcommand
9
+ if (!command || command === "help") {
10
+ console.log("Usage: the-seed package <output-directory> <project-dir> [project-dir2] ...");
11
+ console.log("");
12
+ console.log("Package binaries with their shared library dependencies into a directory.");
13
+ console.log("");
14
+ console.log("Arguments:");
15
+ console.log(" output-directory Name of the output directory to create");
16
+ console.log(" project-dir Project directories containing src/Makefile.am");
17
+ console.log("");
18
+ console.log("The command reads src/Makefile.am to determine the binary type and name,");
19
+ console.log("resolves installed binaries for all build targets, uses DependencyLister");
20
+ console.log("to find shared library dependencies, and copies everything into the output directory.");
21
+ return;
22
+ }
23
+
24
+ const outputDir = command;
25
+ const projectDirs = scriptConfig.args.slice(4);
26
+
27
+ // No project directories specified
28
+ if (projectDirs.length === 0) {
29
+ console.error("Usage: the-seed package <output-directory> <project-dir> [project-dir2] ...");
30
+ process.exit(1);
31
+ }
32
+
33
+ const config = new Config(scriptConfig.configDir);
34
+ const pkg = new Package(config);
35
+ const success = pkg.run(outputDir, projectDirs);
36
+
37
+ if (!success) {
38
+ process.exit(1);
39
+ }
40
+ };
41
+
42
+ export default PackageCLI;
@@ -6,6 +6,8 @@ import ScopesCLI from "./ScopesCLI";
6
6
  import TemplateCLI from "./TemplateCLI";
7
7
  import BuildCLI from "./BuildCLI";
8
8
  import ResourcePakCLI from "./ResourcePakCLI";
9
+ import DependenciesCLI from "./DependenciesCLI";
10
+ import PackageCLI from "./PackageCLI";
9
11
  import { ScriptArgsType } from "../types";
10
12
 
11
13
  const homedir = os.homedir;
@@ -21,10 +23,12 @@ const section = scriptConfig.args[2] || "help";
21
23
  switch(section)
22
24
  {
23
25
  case "help":
26
+ console.log(scriptConfig.binName + " dependencies");
24
27
  console.log(scriptConfig.binName + " build");
25
28
  console.log(scriptConfig.binName + " config");
26
29
  console.log(scriptConfig.binName + " scopes");
27
30
  console.log(scriptConfig.binName + " template");
31
+ console.log(scriptConfig.binName + " package");
28
32
  console.log(scriptConfig.binName + " resource-pak");
29
33
  break;
30
34
  case "config":
@@ -42,4 +46,13 @@ switch(section)
42
46
  case "resource-pak":
43
47
  ResourcePakCLI(scriptConfig);
44
48
  break;
49
+ case "dependencies":
50
+ DependenciesCLI(scriptConfig);
51
+ break;
52
+ case "package":
53
+ PackageCLI(scriptConfig);
54
+ break;
55
+ default:
56
+ console.log("Invalid command. Use 'the-seed help' for usage information.");
57
+ break;
45
58
  }
package/src/types.ts CHANGED
@@ -13,7 +13,6 @@ export interface ScopesType {
13
13
  }
14
14
 
15
15
  export interface ConfigType {
16
- [index: string]: any;
17
16
  prefix: string;
18
17
  scopes: ScopesType;
19
18
  }
@@ -28,7 +27,7 @@ export interface ResourceType {
28
27
  name: string;
29
28
  filename: string;
30
29
  size: number;
31
- attributes?: any;
30
+ attributes?: { [key: string]: string };
32
31
  }
33
32
 
34
33
  export interface PackageType {
@@ -40,5 +39,32 @@ export interface PackageType {
40
39
  [index: string]: string;
41
40
  };
42
41
  resources: ResourceType[];
43
- main?: any;
42
+ main?: string;
43
+ }
44
+
45
+ export interface ScopeAnswersType {
46
+ scopeName: string;
47
+ authorName: string;
48
+ authorEmail: string;
49
+ authorURL: string;
50
+ }
51
+
52
+ export interface ScopeDefaultsType {
53
+ name?: string;
54
+ email?: string;
55
+ url?: string;
56
+ }
57
+
58
+ export interface DependencyResultType {
59
+ dependencies: Record<string, string[]>;
60
+ errors: Record<string, string>;
61
+ }
62
+
63
+ export interface BuildStep {
64
+ /** Human-readable step name (e.g., 'autogen', 'configure', 'compile', 'install') */
65
+ label: string;
66
+ /** Shell command to execute */
67
+ command: string;
68
+ /** If true, non-zero exit codes do not abort the build (used for 'make distclean') */
69
+ ignoreExitCode?: boolean;
44
70
  }
@@ -0,0 +1,223 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import Config from "../src/Config";
5
+ import Build, { targets } from "../src/Build";
6
+ import { BuildStep } from "../src/types";
7
+
8
+ jest.mock("child_process", () => ({
9
+ execSync: jest.fn(() => Buffer.from(""))
10
+ }));
11
+
12
+ import { execSync } from "child_process";
13
+ const mockedExecSync = execSync as jest.MockedFunction<typeof execSync>;
14
+
15
+ function createTempDir(): string {
16
+ return fs.mkdtempSync(path.join(os.tmpdir(), "build-test-"));
17
+ }
18
+
19
+ describe("test Build", () => {
20
+ let configDir: string;
21
+ let config: Config;
22
+ let build: Build;
23
+
24
+ beforeAll(() => {
25
+ configDir = createTempDir();
26
+ config = new Config(configDir);
27
+ });
28
+
29
+ beforeEach(() => {
30
+ build = new Build(config);
31
+ mockedExecSync.mockClear();
32
+ mockedExecSync.mockReturnValue(Buffer.from(""));
33
+ });
34
+
35
+ afterAll(() => {
36
+ fs.rmSync(configDir, { recursive: true });
37
+ });
38
+
39
+ describe("targets map", () => {
40
+ it("has correct target mappings", () => {
41
+ expect(targets["native"]).toBe("x86_64-linux-gnu");
42
+ expect(targets["windows"]).toBe("x86_64-w64-mingw32");
43
+ });
44
+ });
45
+
46
+ describe("autogen", () => {
47
+ it("runs autogen.sh", () => {
48
+ build.autogen();
49
+ expect(mockedExecSync).toHaveBeenCalledWith(
50
+ "./autogen.sh",
51
+ expect.objectContaining({ stdio: "pipe" })
52
+ );
53
+ });
54
+
55
+ it("propagates errors", () => {
56
+ mockedExecSync.mockImplementation(() => {
57
+ throw new Error("autogen failed");
58
+ });
59
+ expect(() => build.autogen()).toThrow("autogen failed");
60
+ });
61
+ });
62
+
63
+ describe("configure", () => {
64
+ it("runs configure for native target", () => {
65
+ build.target = "native";
66
+ build.configure();
67
+
68
+ // First call is make distclean
69
+ expect(mockedExecSync).toHaveBeenCalledWith(
70
+ "make distclean",
71
+ expect.anything()
72
+ );
73
+
74
+ // Second call is configure
75
+ const configureCalls = mockedExecSync.mock.calls.filter(
76
+ (call) => String(call[0]).includes("./configure")
77
+ );
78
+ expect(configureCalls.length).toBe(1);
79
+ const configureCmd = String(configureCalls[0][0]);
80
+ expect(configureCmd).toContain("--prefix=" + config.config.prefix + "/x86_64-linux-gnu");
81
+ expect(configureCmd).not.toContain("--host");
82
+ });
83
+
84
+ it("runs configure for windows target", () => {
85
+ build.target = "windows";
86
+ build.configure();
87
+
88
+ const configureCalls = mockedExecSync.mock.calls.filter(
89
+ (call) => String(call[0]).includes("./configure")
90
+ );
91
+ expect(configureCalls.length).toBe(1);
92
+ const configureCmd = String(configureCalls[0][0]);
93
+ expect(configureCmd).toContain("--host=x86_64-w64-mingw32");
94
+ });
95
+
96
+ it("runs distclean before configure", () => {
97
+ build.target = "native";
98
+ build.configure();
99
+
100
+ const calls = mockedExecSync.mock.calls.map((c) => String(c[0]));
101
+ const distcleanIndex = calls.findIndex((c) => c.includes("make distclean"));
102
+ const configureIndex = calls.findIndex((c) => c.includes("./configure"));
103
+ expect(distcleanIndex).toBeLessThan(configureIndex);
104
+ });
105
+ });
106
+
107
+ describe("reconfigure", () => {
108
+ it("chains autogen and configure", () => {
109
+ build.reconfigure("native");
110
+
111
+ const calls = mockedExecSync.mock.calls.map((c) => String(c[0]));
112
+ const autogenIndex = calls.findIndex((c) => c.includes("./autogen.sh"));
113
+ const configureIndex = calls.findIndex((c) => c.includes("./configure"));
114
+ expect(autogenIndex).toBeGreaterThanOrEqual(0);
115
+ expect(configureIndex).toBeGreaterThan(autogenIndex);
116
+ });
117
+ });
118
+
119
+ describe("compile", () => {
120
+ it("runs make -j", () => {
121
+ build.compile();
122
+ expect(mockedExecSync).toHaveBeenCalledWith("make -j");
123
+ });
124
+ });
125
+
126
+ describe("install", () => {
127
+ it("runs make install", () => {
128
+ build.install();
129
+ expect(mockedExecSync).toHaveBeenCalledWith("make install");
130
+ });
131
+ });
132
+
133
+ describe("getSteps", () => {
134
+ it("returns 5 steps for full native build", () => {
135
+ const steps: BuildStep[] = build.getSteps("native", true);
136
+ expect(steps).toHaveLength(5);
137
+ expect(steps.map(s => s.label)).toEqual([
138
+ "autogen", "distclean", "configure", "compile", "install"
139
+ ]);
140
+ });
141
+
142
+ it("returns 5 steps for full windows build", () => {
143
+ const steps: BuildStep[] = build.getSteps("windows", true);
144
+ expect(steps).toHaveLength(5);
145
+ expect(steps.map(s => s.label)).toEqual([
146
+ "autogen", "distclean", "configure", "compile", "install"
147
+ ]);
148
+ });
149
+
150
+ it("returns 2 steps for incremental build (fullReconfigure=false)", () => {
151
+ const steps: BuildStep[] = build.getSteps("native", false);
152
+ expect(steps).toHaveLength(2);
153
+ expect(steps.map(s => s.label)).toEqual(["compile", "install"]);
154
+ });
155
+
156
+ it("native configure step does not contain --host flag", () => {
157
+ const steps = build.getSteps("native", true);
158
+ const configureStep = steps.find(s => s.label === "configure");
159
+ expect(configureStep).toBeDefined();
160
+ expect(configureStep!.command).not.toContain("--host");
161
+ });
162
+
163
+ it("windows configure step contains --host=x86_64-w64-mingw32", () => {
164
+ const steps = build.getSteps("windows", true);
165
+ const configureStep = steps.find(s => s.label === "configure");
166
+ expect(configureStep).toBeDefined();
167
+ expect(configureStep!.command).toContain("--host=x86_64-w64-mingw32");
168
+ });
169
+
170
+ it("distclean step has ignoreExitCode set to true", () => {
171
+ const steps = build.getSteps("native", true);
172
+ const distcleanStep = steps.find(s => s.label === "distclean");
173
+ expect(distcleanStep).toBeDefined();
174
+ expect(distcleanStep!.ignoreExitCode).toBe(true);
175
+ });
176
+
177
+ it("non-distclean steps do not have ignoreExitCode set to true", () => {
178
+ const steps = build.getSteps("native", true);
179
+ const nonDistclean = steps.filter(s => s.label !== "distclean");
180
+ for (const step of nonDistclean) {
181
+ expect(step.ignoreExitCode).toBeFalsy();
182
+ }
183
+ });
184
+
185
+ it("configure step includes correct prefix path for native", () => {
186
+ const steps = build.getSteps("native", true);
187
+ const configureStep = steps.find(s => s.label === "configure");
188
+ const expectedPrefix = config.config.prefix + "/x86_64-linux-gnu";
189
+ expect(configureStep!.command).toContain("--prefix=" + expectedPrefix);
190
+ expect(configureStep!.command).toContain("PKG_CONFIG_PATH=" + expectedPrefix + "/lib/pkgconfig/");
191
+ });
192
+
193
+ it("configure step includes correct prefix path for windows", () => {
194
+ const steps = build.getSteps("windows", true);
195
+ const configureStep = steps.find(s => s.label === "configure");
196
+ const expectedPrefix = config.config.prefix + "/x86_64-w64-mingw32";
197
+ expect(configureStep!.command).toContain("--prefix=" + expectedPrefix);
198
+ });
199
+
200
+ it("autogen step command is ./autogen.sh", () => {
201
+ const steps = build.getSteps("native", true);
202
+ expect(steps[0].command).toBe("./autogen.sh");
203
+ });
204
+
205
+ it("compile step command is make -j", () => {
206
+ const steps = build.getSteps("native", true);
207
+ const compileStep = steps.find(s => s.label === "compile");
208
+ expect(compileStep!.command).toBe("make -j");
209
+ });
210
+
211
+ it("install step command is make install", () => {
212
+ const steps = build.getSteps("native", true);
213
+ const installStep = steps.find(s => s.label === "install");
214
+ expect(installStep!.command).toBe("make install");
215
+ });
216
+
217
+ it("incremental build for windows returns 2 steps", () => {
218
+ const steps = build.getSteps("windows", false);
219
+ expect(steps).toHaveLength(2);
220
+ expect(steps.map(s => s.label)).toEqual(["compile", "install"]);
221
+ });
222
+ });
223
+ });
@@ -24,7 +24,7 @@ describe("test Config", () => {
24
24
  });
25
25
 
26
26
  it("write updated config file", () => {
27
- config.config["prefix"] = prefix;
27
+ config.config.prefix = prefix;
28
28
  config.saveConfig();
29
29
  });
30
30
 
@@ -0,0 +1,153 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import Config from "../src/Config";
5
+ import {
6
+ checkLib,
7
+ checkLibEcs,
8
+ checkLibTheSeed,
9
+ installLib,
10
+ installLibEcs,
11
+ installLibTheSeed
12
+ } from "../src/Dependencies";
13
+
14
+ jest.mock("child_process", () => ({
15
+ execSync: jest.fn(() => Buffer.from(""))
16
+ }));
17
+
18
+ import { execSync } from "child_process";
19
+ const mockedExecSync = execSync as jest.MockedFunction<typeof execSync>;
20
+
21
+ function createTempDir(): string {
22
+ return fs.mkdtempSync(path.join(os.tmpdir(), "deps-test-"));
23
+ }
24
+
25
+ describe("test Dependencies", () => {
26
+ let configDir: string;
27
+ let config: Config;
28
+
29
+ beforeAll(() => {
30
+ configDir = createTempDir();
31
+ config = new Config(configDir);
32
+ });
33
+
34
+ beforeEach(() => {
35
+ mockedExecSync.mockClear();
36
+ mockedExecSync.mockReturnValue(Buffer.from(""));
37
+ });
38
+
39
+ afterAll(() => {
40
+ fs.rmSync(configDir, { recursive: true });
41
+ });
42
+
43
+ describe("checkLib", () => {
44
+ it("returns true when library is found", () => {
45
+ mockedExecSync.mockReturnValue(Buffer.from("-l ecs-cpp -I/usr/include/ecs-cpp"));
46
+ const result = checkLib(config, "ecs-cpp", "native");
47
+ expect(result).toBe(true);
48
+ });
49
+
50
+ it("returns false when pkg-config throws", () => {
51
+ mockedExecSync.mockImplementation(() => {
52
+ throw new Error("pkg-config not found");
53
+ });
54
+ const result = checkLib(config, "ecs-cpp", "native");
55
+ expect(result).toBe(false);
56
+ });
57
+
58
+ it("returns false when output does not include library name", () => {
59
+ mockedExecSync.mockReturnValue(Buffer.from("-l other-lib"));
60
+ const result = checkLib(config, "ecs-cpp", "native");
61
+ expect(result).toBe(false);
62
+ });
63
+ });
64
+
65
+ describe("checkLibEcs", () => {
66
+ it("delegates to checkLib with ecs-cpp", () => {
67
+ mockedExecSync.mockReturnValue(Buffer.from("-l ecs-cpp"));
68
+ const result = checkLibEcs(config, "native");
69
+ expect(result).toBe(true);
70
+ expect(mockedExecSync).toHaveBeenCalledWith(
71
+ expect.stringContaining("ecs-cpp"),
72
+ expect.anything()
73
+ );
74
+ });
75
+ });
76
+
77
+ describe("checkLibTheSeed", () => {
78
+ it("delegates to checkLib with the-seed", () => {
79
+ mockedExecSync.mockReturnValue(Buffer.from("-l the-seed"));
80
+ const result = checkLibTheSeed(config, "native");
81
+ expect(result).toBe(true);
82
+ expect(mockedExecSync).toHaveBeenCalledWith(
83
+ expect.stringContaining("the-seed"),
84
+ expect.anything()
85
+ );
86
+ });
87
+ });
88
+
89
+ describe("installLib", () => {
90
+ it("returns true on success", () => {
91
+ mockedExecSync.mockReturnValue(Buffer.from("ok"));
92
+ const result = installLib(config, "https://github.com/test/repo.git", "test-dir", "native");
93
+ expect(result).toBe(true);
94
+ });
95
+
96
+ it("returns false on build failure", () => {
97
+ // First call (clone) succeeds, second call (build) throws
98
+ mockedExecSync
99
+ .mockReturnValueOnce(Buffer.from("cloned"))
100
+ .mockImplementationOnce(() => {
101
+ throw new Error("build failed");
102
+ });
103
+ const result = installLib(config, "https://github.com/test/repo.git", "test-dir", "native");
104
+ expect(result).toBe(false);
105
+ });
106
+
107
+ it("native target has no --host flag", () => {
108
+ mockedExecSync.mockReturnValue(Buffer.from("ok"));
109
+ installLib(config, "https://github.com/test/repo.git", "test-dir", "native");
110
+
111
+ const buildCall = mockedExecSync.mock.calls.find(
112
+ (call) => String(call[0]).includes("./configure")
113
+ );
114
+ expect(buildCall).toBeDefined();
115
+ expect(String(buildCall![0])).not.toContain("--host");
116
+ });
117
+
118
+ it("windows target includes --host flag", () => {
119
+ mockedExecSync.mockReturnValue(Buffer.from("ok"));
120
+ installLib(config, "https://github.com/test/repo.git", "test-dir", "windows");
121
+
122
+ const buildCall = mockedExecSync.mock.calls.find(
123
+ (call) => String(call[0]).includes("./configure")
124
+ );
125
+ expect(buildCall).toBeDefined();
126
+ expect(String(buildCall![0])).toContain("--host=x86_64-w64-mingw32");
127
+ });
128
+ });
129
+
130
+ describe("installLibEcs", () => {
131
+ it("delegates to installLib with libecs-cpp repo", () => {
132
+ mockedExecSync.mockReturnValue(Buffer.from("ok"));
133
+ const result = installLibEcs(config, "native");
134
+ expect(result).toBe(true);
135
+ expect(mockedExecSync).toHaveBeenCalledWith(
136
+ expect.stringContaining("libecs-cpp"),
137
+ expect.anything()
138
+ );
139
+ });
140
+ });
141
+
142
+ describe("installLibTheSeed", () => {
143
+ it("delegates to installLib with libthe-seed repo", () => {
144
+ mockedExecSync.mockReturnValue(Buffer.from("ok"));
145
+ const result = installLibTheSeed(config, "native");
146
+ expect(result).toBe(true);
147
+ expect(mockedExecSync).toHaveBeenCalledWith(
148
+ expect.stringContaining("libthe-seed"),
149
+ expect.anything()
150
+ );
151
+ });
152
+ });
153
+ });