@metaverse-systems/the-seed 1.0.4 → 1.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/.eslintrc.json +1 -0
- package/.vscode/settings.json +14 -0
- package/README.md +530 -0
- package/dist/Build.d.ts +10 -0
- package/dist/Build.js +59 -16
- package/dist/Build.js.map +1 -1
- package/dist/Config.d.ts +1 -1
- package/dist/Config.js +2 -4
- package/dist/Config.js.map +1 -1
- package/dist/Dependencies.d.ts +7 -0
- package/dist/Dependencies.js +76 -0
- package/dist/Dependencies.js.map +1 -0
- package/dist/Package.d.ts +45 -0
- package/dist/Package.js +249 -0
- package/dist/Package.js.map +1 -0
- package/dist/ResourcePak.js +14 -14
- package/dist/ResourcePak.js.map +1 -1
- package/dist/Scopes.d.ts +9 -6
- package/dist/Scopes.js.map +1 -1
- package/dist/Template.d.ts +2 -1
- package/dist/Template.js +18 -16
- package/dist/Template.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/scripts/DependenciesCLI.d.ts +3 -0
- package/dist/scripts/DependenciesCLI.js +58 -0
- package/dist/scripts/DependenciesCLI.js.map +1 -0
- package/dist/scripts/PackageCLI.d.ts +3 -0
- package/dist/scripts/PackageCLI.js +38 -0
- package/dist/scripts/PackageCLI.js.map +1 -0
- package/dist/scripts/the-seed.js +13 -0
- package/dist/scripts/the-seed.js.map +1 -1
- package/dist/types.d.ts +27 -3
- package/native/binding.gyp +20 -0
- package/native/src/addon.cpp +60 -0
- package/package.json +4 -1
- package/src/Build.ts +71 -17
- package/src/Config.ts +3 -5
- package/src/Dependencies.ts +73 -0
- package/src/Package.ts +287 -0
- package/src/ResourcePak.ts +5 -5
- package/src/Scopes.ts +4 -4
- package/src/Template.ts +10 -9
- package/src/index.ts +13 -2
- package/src/scripts/DependenciesCLI.ts +57 -0
- package/src/scripts/PackageCLI.ts +42 -0
- package/src/scripts/the-seed.ts +13 -0
- package/src/types.ts +29 -3
- package/test/Build.test.ts +223 -0
- package/test/Config.test.ts +1 -1
- package/test/Dependencies.test.ts +153 -0
- package/test/Package.test.ts +631 -0
- package/test/Template.test.ts +187 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import Config from "../src/Config";
|
|
5
|
+
import Package from "../src/Package";
|
|
6
|
+
import { targets } from "../src/Build";
|
|
7
|
+
import { DependencyResultType, ScriptArgsType } from "../src/types";
|
|
8
|
+
import PackageCLI from "../src/scripts/PackageCLI";
|
|
9
|
+
|
|
10
|
+
// Mock the native addon
|
|
11
|
+
jest.mock("../native/build/Release/dependency_lister.node", () => ({
|
|
12
|
+
listDependencies: jest.fn()
|
|
13
|
+
}), { virtual: true });
|
|
14
|
+
|
|
15
|
+
// Mock child_process for MinGW detection in getSearchPaths
|
|
16
|
+
jest.mock("child_process", () => ({
|
|
17
|
+
execSync: jest.fn((cmd: string) => {
|
|
18
|
+
if (cmd.includes("libstdc++-6.dll")) {
|
|
19
|
+
return Buffer.from("/usr/lib/gcc/x86_64-w64-mingw32/15-posix/libstdc++-6.dll\n");
|
|
20
|
+
}
|
|
21
|
+
if (cmd.includes("libwinpthread-1.dll")) {
|
|
22
|
+
return Buffer.from("/usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll\n");
|
|
23
|
+
}
|
|
24
|
+
return Buffer.from("");
|
|
25
|
+
})
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const mockAddon = require("../native/build/Release/dependency_lister.node");
|
|
29
|
+
const mockedListDependencies = mockAddon.listDependencies as jest.MockedFunction<
|
|
30
|
+
(binaryPaths: string[], searchPaths: string[]) => DependencyResultType
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
function createTempDir(): string {
|
|
34
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "package-test-"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createProjectDir(baseDir: string, name: string, makefileAmContent: string): string {
|
|
38
|
+
const projectDir = path.join(baseDir, name);
|
|
39
|
+
fs.mkdirSync(path.join(projectDir, "src"), { recursive: true });
|
|
40
|
+
fs.writeFileSync(path.join(projectDir, "src", "Makefile.am"), makefileAmContent);
|
|
41
|
+
return projectDir;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function installBuildOutput(projectDir: string, filename: string, content = ""): string {
|
|
45
|
+
const libsDir = path.join(projectDir, "src", ".libs");
|
|
46
|
+
fs.mkdirSync(libsDir, { recursive: true });
|
|
47
|
+
const filePath = path.join(libsDir, filename);
|
|
48
|
+
fs.writeFileSync(filePath, content);
|
|
49
|
+
return filePath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createNodeModuleDep(projectDir: string, depName: string, makefileAmContent: string): string {
|
|
53
|
+
const depDir = path.join(projectDir, "node_modules", depName);
|
|
54
|
+
fs.mkdirSync(path.join(depDir, "src"), { recursive: true });
|
|
55
|
+
fs.writeFileSync(path.join(depDir, "src", "Makefile.am"), makefileAmContent);
|
|
56
|
+
return depDir;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("test Package", () => {
|
|
60
|
+
let configDir: string;
|
|
61
|
+
let config: Config;
|
|
62
|
+
let pkg: Package;
|
|
63
|
+
let tempDir: string;
|
|
64
|
+
|
|
65
|
+
beforeAll(() => {
|
|
66
|
+
configDir = createTempDir();
|
|
67
|
+
config = new Config(configDir);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
tempDir = createTempDir();
|
|
72
|
+
config.config.prefix = path.join(tempDir, "prefix");
|
|
73
|
+
pkg = new Package(config);
|
|
74
|
+
mockedListDependencies.mockClear();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterAll(() => {
|
|
82
|
+
fs.rmSync(configDir, { recursive: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// T012: Search paths constructed from Config prefix and all targets
|
|
86
|
+
describe("getSearchPaths", () => {
|
|
87
|
+
it("constructs search paths from Config prefix and all targets, including lib and bin", () => {
|
|
88
|
+
const searchPaths = pkg.getSearchPaths();
|
|
89
|
+
const targetKeys = Object.keys(targets);
|
|
90
|
+
for (const target of targetKeys) {
|
|
91
|
+
const targetDir = targets[target];
|
|
92
|
+
expect(searchPaths).toContain(
|
|
93
|
+
config.config.prefix + "/" + targetDir + "/lib"
|
|
94
|
+
);
|
|
95
|
+
expect(searchPaths).toContain(
|
|
96
|
+
config.config.prefix + "/" + targetDir + "/bin"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
// MinGW runtime paths detected from mocked execSync
|
|
100
|
+
expect(searchPaths).toContain("/usr/lib/gcc/x86_64-w64-mingw32/15-posix");
|
|
101
|
+
expect(searchPaths).toContain("/usr/x86_64-w64-mingw32/lib");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// parseMakefileAm tests
|
|
106
|
+
describe("parseMakefileAm", () => {
|
|
107
|
+
it("parses bin_PROGRAMS as a program type", () => {
|
|
108
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
109
|
+
"ACLOCAL_AMFLAGS=-I m4\nbin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
110
|
+
const result = pkg.parseMakefileAm(projectDir);
|
|
111
|
+
expect(result).toEqual({ type: "program", name: "myapp" });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("parses lib_LTLIBRARIES as a library type", () => {
|
|
115
|
+
const projectDir = createProjectDir(tempDir, "libfoo",
|
|
116
|
+
"ACLOCAL_AMFLAGS=-I m4\nlib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
117
|
+
const result = pkg.parseMakefileAm(projectDir);
|
|
118
|
+
expect(result).toEqual({ type: "library", name: "libfoo" });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns null when src/Makefile.am does not exist", () => {
|
|
122
|
+
const projectDir = path.join(tempDir, "noproject");
|
|
123
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
124
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
125
|
+
const result = pkg.parseMakefileAm(projectDir);
|
|
126
|
+
consoleSpy.mockRestore();
|
|
127
|
+
expect(result).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("returns null when Makefile.am has unrecognized format", () => {
|
|
131
|
+
const projectDir = createProjectDir(tempDir, "weird",
|
|
132
|
+
"ACLOCAL_AMFLAGS=-I m4\n# nothing useful here\n");
|
|
133
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
134
|
+
const result = pkg.parseMakefileAm(projectDir);
|
|
135
|
+
consoleSpy.mockRestore();
|
|
136
|
+
expect(result).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// resolveBinaryPaths tests
|
|
141
|
+
describe("resolveBinaryPaths", () => {
|
|
142
|
+
it("finds native program binary in src/.libs/", () => {
|
|
143
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
144
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
145
|
+
installBuildOutput(projectDir, "myapp", "binary");
|
|
146
|
+
const result = pkg.resolveBinaryPaths(projectDir);
|
|
147
|
+
expect(result).toContain(
|
|
148
|
+
path.join(projectDir, "src", ".libs", "myapp"));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("finds Windows program binary (.exe) in src/.libs/", () => {
|
|
152
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
153
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
154
|
+
installBuildOutput(projectDir, "myapp.exe", "binary");
|
|
155
|
+
const result = pkg.resolveBinaryPaths(projectDir);
|
|
156
|
+
expect(result).toContain(
|
|
157
|
+
path.join(projectDir, "src", ".libs", "myapp.exe"));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("finds native library (.so) in src/.libs/", () => {
|
|
161
|
+
const projectDir = createProjectDir(tempDir, "libfoo",
|
|
162
|
+
"lib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
163
|
+
installBuildOutput(projectDir, "libfoo.so", "library");
|
|
164
|
+
const result = pkg.resolveBinaryPaths(projectDir);
|
|
165
|
+
expect(result).toContain(
|
|
166
|
+
path.join(projectDir, "src", ".libs", "libfoo.so"));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("finds Windows library DLL in src/.libs/", () => {
|
|
170
|
+
const projectDir = createProjectDir(tempDir, "libfoo",
|
|
171
|
+
"lib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
172
|
+
installBuildOutput(projectDir, "libfoo-0.dll", "library");
|
|
173
|
+
const result = pkg.resolveBinaryPaths(projectDir);
|
|
174
|
+
expect(result).toContain(
|
|
175
|
+
path.join(projectDir, "src", ".libs", "libfoo-0.dll"));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("finds both native and Windows binaries in src/.libs/", () => {
|
|
179
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
180
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
181
|
+
installBuildOutput(projectDir, "myapp", "binary");
|
|
182
|
+
installBuildOutput(projectDir, "myapp.exe", "binary");
|
|
183
|
+
const result = pkg.resolveBinaryPaths(projectDir);
|
|
184
|
+
expect(result).toHaveLength(2);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("returns empty array when src/.libs/ does not exist", () => {
|
|
188
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
189
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
190
|
+
const result = pkg.resolveBinaryPaths(projectDir);
|
|
191
|
+
expect(result).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// getPackageDeps tests
|
|
196
|
+
describe("getPackageDeps", () => {
|
|
197
|
+
it("returns dependency dirs from node_modules that have src/Makefile.am", () => {
|
|
198
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
199
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
200
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify({
|
|
201
|
+
dependencies: { "@org/libfoo": "^1.0.0", "@org/libbar": "^2.0.0" }
|
|
202
|
+
}));
|
|
203
|
+
createNodeModuleDep(projectDir, "@org/libfoo",
|
|
204
|
+
"lib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
205
|
+
createNodeModuleDep(projectDir, "@org/libbar",
|
|
206
|
+
"lib_LTLIBRARIES = libbar.la\nlibbar_la_SOURCES = bar.cpp\n");
|
|
207
|
+
const result = pkg.getPackageDeps(projectDir);
|
|
208
|
+
expect(result).toHaveLength(2);
|
|
209
|
+
expect(result).toContain(path.join(projectDir, "node_modules", "@org/libfoo"));
|
|
210
|
+
expect(result).toContain(path.join(projectDir, "node_modules", "@org/libbar"));
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("skips dependencies without src/Makefile.am", () => {
|
|
214
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
215
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
216
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify({
|
|
217
|
+
dependencies: { "@org/libfoo": "^1.0.0", "some-js-lib": "^3.0.0" }
|
|
218
|
+
}));
|
|
219
|
+
createNodeModuleDep(projectDir, "@org/libfoo",
|
|
220
|
+
"lib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
221
|
+
// some-js-lib exists in node_modules but has no Makefile.am
|
|
222
|
+
const jsLibDir = path.join(projectDir, "node_modules", "some-js-lib");
|
|
223
|
+
fs.mkdirSync(jsLibDir, { recursive: true });
|
|
224
|
+
const result = pkg.getPackageDeps(projectDir);
|
|
225
|
+
expect(result).toHaveLength(1);
|
|
226
|
+
expect(result).toContain(path.join(projectDir, "node_modules", "@org/libfoo"));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("returns empty array when no package.json exists", () => {
|
|
230
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
231
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
232
|
+
const result = pkg.getPackageDeps(projectDir);
|
|
233
|
+
expect(result).toEqual([]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("returns empty array when package.json has no dependencies", () => {
|
|
237
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
238
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
239
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify({
|
|
240
|
+
name: "myapp", version: "1.0.0"
|
|
241
|
+
}));
|
|
242
|
+
const result = pkg.getPackageDeps(projectDir);
|
|
243
|
+
expect(result).toEqual([]);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("recursively collects transitive native dependencies", () => {
|
|
247
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
248
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
249
|
+
// myapp depends on @org/libfoo
|
|
250
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify({
|
|
251
|
+
dependencies: { "@org/libfoo": "^1.0.0" }
|
|
252
|
+
}));
|
|
253
|
+
const libfooDir = createNodeModuleDep(projectDir, "@org/libfoo",
|
|
254
|
+
"lib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
255
|
+
// @org/libfoo depends on @org/libbar
|
|
256
|
+
fs.writeFileSync(path.join(libfooDir, "package.json"), JSON.stringify({
|
|
257
|
+
dependencies: { "@org/libbar": "^1.0.0" }
|
|
258
|
+
}));
|
|
259
|
+
createNodeModuleDep(libfooDir, "@org/libbar",
|
|
260
|
+
"lib_LTLIBRARIES = libbar.la\nlibbar_la_SOURCES = bar.cpp\n");
|
|
261
|
+
|
|
262
|
+
const result = pkg.getPackageDeps(projectDir);
|
|
263
|
+
expect(result).toHaveLength(2);
|
|
264
|
+
expect(result).toContain(path.join(projectDir, "node_modules", "@org/libfoo"));
|
|
265
|
+
expect(result).toContain(path.join(libfooDir, "node_modules", "@org/libbar"));
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// T008: Package binary with dependencies creates dir and copies all files
|
|
270
|
+
describe("run - happy path with dependencies", () => {
|
|
271
|
+
it("creates directory and copies binary plus all resolved dependencies", () => {
|
|
272
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
273
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
274
|
+
const binary = installBuildOutput(projectDir, "myapp", "binary-content");
|
|
275
|
+
const libFoo = installBuildOutput(projectDir, "libfoo.so", "libfoo-content");
|
|
276
|
+
const libBar = installBuildOutput(projectDir, "libbar.so", "libbar-content");
|
|
277
|
+
|
|
278
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
279
|
+
|
|
280
|
+
mockedListDependencies.mockReturnValue({
|
|
281
|
+
dependencies: {
|
|
282
|
+
[libFoo]: [binary],
|
|
283
|
+
[libBar]: [binary],
|
|
284
|
+
"libc.so.6": [binary] // system lib — should be skipped
|
|
285
|
+
},
|
|
286
|
+
errors: {}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
290
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
291
|
+
consoleSpy.mockRestore();
|
|
292
|
+
|
|
293
|
+
expect(result).toBe(true);
|
|
294
|
+
expect(fs.existsSync(outputDir)).toBe(true);
|
|
295
|
+
expect(fs.existsSync(path.join(outputDir, "myapp"))).toBe(true);
|
|
296
|
+
expect(fs.existsSync(path.join(outputDir, "libfoo.so"))).toBe(true);
|
|
297
|
+
expect(fs.existsSync(path.join(outputDir, "libbar.so"))).toBe(true);
|
|
298
|
+
// system lib should NOT be copied
|
|
299
|
+
expect(fs.existsSync(path.join(outputDir, "libc.so.6"))).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// run includes binaries from package.json dependencies in node_modules
|
|
304
|
+
describe("run - includes package.json dependency binaries", () => {
|
|
305
|
+
it("copies binaries from node_modules dependencies alongside the main binary", () => {
|
|
306
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
307
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
308
|
+
const binary = installBuildOutput(projectDir, "myapp", "binary-content");
|
|
309
|
+
|
|
310
|
+
// Add a native dependency via package.json + node_modules
|
|
311
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify({
|
|
312
|
+
dependencies: { "@org/libfoo": "^1.0.0" }
|
|
313
|
+
}));
|
|
314
|
+
const depDir = createNodeModuleDep(projectDir, "@org/libfoo",
|
|
315
|
+
"lib_LTLIBRARIES = libfoo.la\nlibfoo_la_SOURCES = foo.cpp\n");
|
|
316
|
+
const depLib = installBuildOutput(depDir, "libfoo-0.dll", "dll-content");
|
|
317
|
+
|
|
318
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
319
|
+
|
|
320
|
+
mockedListDependencies.mockReturnValue({
|
|
321
|
+
dependencies: {},
|
|
322
|
+
errors: {}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
326
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
327
|
+
consoleSpy.mockRestore();
|
|
328
|
+
|
|
329
|
+
expect(result).toBe(true);
|
|
330
|
+
expect(fs.existsSync(path.join(outputDir, "myapp"))).toBe(true);
|
|
331
|
+
expect(fs.existsSync(path.join(outputDir, "libfoo-0.dll"))).toBe(true);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// T009: Package binary with no non-system dependencies copies only explicit files
|
|
336
|
+
describe("run - no non-system dependencies", () => {
|
|
337
|
+
it("copies only the resolved binary when no project dependencies found", () => {
|
|
338
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
339
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
340
|
+
const binary = installBuildOutput(projectDir, "myapp", "binary-content");
|
|
341
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
342
|
+
|
|
343
|
+
mockedListDependencies.mockReturnValue({
|
|
344
|
+
dependencies: {
|
|
345
|
+
"libc.so.6": [binary],
|
|
346
|
+
"libm.so.6": [binary]
|
|
347
|
+
},
|
|
348
|
+
errors: {}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
352
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
353
|
+
consoleSpy.mockRestore();
|
|
354
|
+
|
|
355
|
+
expect(result).toBe(true);
|
|
356
|
+
expect(fs.existsSync(outputDir)).toBe(true);
|
|
357
|
+
expect(fs.existsSync(path.join(outputDir, "myapp"))).toBe(true);
|
|
358
|
+
// Only the resolved binary, no system libs
|
|
359
|
+
expect(fs.readdirSync(outputDir).length).toBe(1);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// T010: Two project dirs sharing a dependency copies shared library only once
|
|
364
|
+
describe("run - deduplication", () => {
|
|
365
|
+
it("copies a shared dependency only once when two binaries share it", () => {
|
|
366
|
+
const projectDir1 = createProjectDir(tempDir, "app1",
|
|
367
|
+
"bin_PROGRAMS = app1\napp1_SOURCES = app1.cpp\n");
|
|
368
|
+
const projectDir2 = createProjectDir(tempDir, "app2",
|
|
369
|
+
"bin_PROGRAMS = app2\napp2_SOURCES = app2.cpp\n");
|
|
370
|
+
const binary1 = installBuildOutput(projectDir1, "app1", "app1-content");
|
|
371
|
+
const binary2 = installBuildOutput(projectDir2, "app2", "app2-content");
|
|
372
|
+
const sharedLib = installBuildOutput(projectDir1, "libshared.so", "shared-content");
|
|
373
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
374
|
+
|
|
375
|
+
mockedListDependencies.mockReturnValue({
|
|
376
|
+
dependencies: {
|
|
377
|
+
[sharedLib]: [binary1, binary2]
|
|
378
|
+
},
|
|
379
|
+
errors: {}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
383
|
+
const result = pkg.run(outputDir, [projectDir1, projectDir2]);
|
|
384
|
+
consoleSpy.mockRestore();
|
|
385
|
+
|
|
386
|
+
expect(result).toBe(true);
|
|
387
|
+
const outputFiles = fs.readdirSync(outputDir);
|
|
388
|
+
expect(outputFiles).toContain("app1");
|
|
389
|
+
expect(outputFiles).toContain("app2");
|
|
390
|
+
expect(outputFiles).toContain("libshared.so");
|
|
391
|
+
// 2 binaries + 1 shared lib = 3 files total
|
|
392
|
+
expect(outputFiles.length).toBe(3);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Transitive dependency resolution: resolved libs are themselves analyzed
|
|
397
|
+
describe("run - transitive dependencies", () => {
|
|
398
|
+
it("resolves dependencies of dependencies recursively", () => {
|
|
399
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
400
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
401
|
+
const binary = installBuildOutput(projectDir, "myapp", "binary-content");
|
|
402
|
+
const libFoo = installBuildOutput(projectDir, "libfoo.so", "libfoo-content");
|
|
403
|
+
const libBar = installBuildOutput(projectDir, "libbar.so", "libbar-content");
|
|
404
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
405
|
+
|
|
406
|
+
// First call: binary depends on libfoo
|
|
407
|
+
// Second call: libfoo depends on libbar
|
|
408
|
+
// Third call: libbar has no new resolved deps
|
|
409
|
+
mockedListDependencies
|
|
410
|
+
.mockReturnValueOnce({
|
|
411
|
+
dependencies: { [libFoo]: [binary], "libc.so.6": [binary] },
|
|
412
|
+
errors: {}
|
|
413
|
+
})
|
|
414
|
+
.mockReturnValueOnce({
|
|
415
|
+
dependencies: { [libBar]: [libFoo], "libc.so.6": [libFoo] },
|
|
416
|
+
errors: {}
|
|
417
|
+
})
|
|
418
|
+
.mockReturnValueOnce({
|
|
419
|
+
dependencies: { "libc.so.6": [libBar] },
|
|
420
|
+
errors: {}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
424
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
425
|
+
consoleSpy.mockRestore();
|
|
426
|
+
|
|
427
|
+
expect(result).toBe(true);
|
|
428
|
+
expect(fs.existsSync(path.join(outputDir, "myapp"))).toBe(true);
|
|
429
|
+
expect(fs.existsSync(path.join(outputDir, "libfoo.so"))).toBe(true);
|
|
430
|
+
expect(fs.existsSync(path.join(outputDir, "libbar.so"))).toBe(true);
|
|
431
|
+
expect(fs.readdirSync(outputDir).length).toBe(3);
|
|
432
|
+
// listDependencies called 3 times (binary, libfoo, libbar)
|
|
433
|
+
expect(mockedListDependencies).toHaveBeenCalledTimes(3);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// T011: Each file printed during copy and summary count displayed
|
|
438
|
+
describe("run - verbose output", () => {
|
|
439
|
+
it("prints each file during copy and summary count at end", () => {
|
|
440
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
441
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
442
|
+
const binary = installBuildOutput(projectDir, "myapp", "binary-content");
|
|
443
|
+
const libFoo = installBuildOutput(projectDir, "libfoo.so", "libfoo-content");
|
|
444
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
445
|
+
|
|
446
|
+
mockedListDependencies.mockReturnValue({
|
|
447
|
+
dependencies: {
|
|
448
|
+
[libFoo]: [binary]
|
|
449
|
+
},
|
|
450
|
+
errors: {}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
454
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
455
|
+
|
|
456
|
+
const logCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
457
|
+
consoleSpy.mockRestore();
|
|
458
|
+
|
|
459
|
+
expect(result).toBe(true);
|
|
460
|
+
// Check individual file copy messages
|
|
461
|
+
expect(logCalls.some(msg => msg.includes("Copying myapp..."))).toBe(true);
|
|
462
|
+
expect(logCalls.some(msg => msg.includes("Copying libfoo.so..."))).toBe(true);
|
|
463
|
+
// Check summary
|
|
464
|
+
expect(logCalls.some(msg => msg.includes("Packaged 2 files into"))).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// T016: help subcommand prints usage information (US2)
|
|
469
|
+
// T017: no arguments prints usage message (US2)
|
|
470
|
+
// (These test the CLI handler, included in Phases 4)
|
|
471
|
+
|
|
472
|
+
// T019: project directory doesn't exist shows error, no output directory created (US3)
|
|
473
|
+
describe("run - project directory not found", () => {
|
|
474
|
+
it("shows error and does not create directory when project directory does not exist", () => {
|
|
475
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
476
|
+
const nonExistent = path.join(tempDir, "nonexistent");
|
|
477
|
+
|
|
478
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
479
|
+
const result = pkg.run(outputDir, [nonExistent]);
|
|
480
|
+
const errorCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
481
|
+
consoleSpy.mockRestore();
|
|
482
|
+
|
|
483
|
+
expect(result).toBe(false);
|
|
484
|
+
expect(fs.existsSync(outputDir)).toBe(false);
|
|
485
|
+
expect(errorCalls.some(msg => msg.includes("Directory not found"))).toBe(true);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// T020: output directory already exists shows error (US3)
|
|
490
|
+
describe("run - output directory already exists", () => {
|
|
491
|
+
it("shows error when output directory already exists", () => {
|
|
492
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
493
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
494
|
+
installBuildOutput(projectDir, "myapp", "binary-content");
|
|
495
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
496
|
+
fs.mkdirSync(outputDir);
|
|
497
|
+
|
|
498
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
499
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
500
|
+
const errorCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
501
|
+
consoleSpy.mockRestore();
|
|
502
|
+
|
|
503
|
+
expect(result).toBe(false);
|
|
504
|
+
expect(errorCalls.some(msg => msg.includes("Output directory already exists"))).toBe(true);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// T022: DependencyLister returns errors triggers fatal abort (US3)
|
|
509
|
+
describe("run - dependency resolution errors", () => {
|
|
510
|
+
it("aborts with error messages when DependencyLister returns errors", () => {
|
|
511
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
512
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
513
|
+
const binary = installBuildOutput(projectDir, "myapp", "binary-content");
|
|
514
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
515
|
+
|
|
516
|
+
mockedListDependencies.mockReturnValue({
|
|
517
|
+
dependencies: {},
|
|
518
|
+
errors: {
|
|
519
|
+
[binary]: "Failed to parse ELF header"
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
524
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
525
|
+
const errorCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
526
|
+
consoleSpy.mockRestore();
|
|
527
|
+
|
|
528
|
+
expect(result).toBe(false);
|
|
529
|
+
expect(fs.existsSync(outputDir)).toBe(false);
|
|
530
|
+
expect(errorCalls.some(msg => msg.includes("Failed to analyze binary"))).toBe(true);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// T022a: input path is not a directory shows error (US3)
|
|
535
|
+
describe("run - input is not a directory", () => {
|
|
536
|
+
it("shows error when input path is a file instead of a directory", () => {
|
|
537
|
+
const filePath = path.join(tempDir, "notadir");
|
|
538
|
+
fs.writeFileSync(filePath, "content");
|
|
539
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
540
|
+
|
|
541
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
542
|
+
const result = pkg.run(outputDir, [filePath]);
|
|
543
|
+
const errorCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
544
|
+
consoleSpy.mockRestore();
|
|
545
|
+
|
|
546
|
+
expect(result).toBe(false);
|
|
547
|
+
expect(fs.existsSync(outputDir)).toBe(false);
|
|
548
|
+
expect(errorCalls.some(msg => msg.includes("Not a directory"))).toBe(true);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// No installed binaries found for a valid project directory
|
|
553
|
+
describe("run - no installed binaries", () => {
|
|
554
|
+
it("shows error when project has Makefile.am but no installed binaries", () => {
|
|
555
|
+
const projectDir = createProjectDir(tempDir, "myapp",
|
|
556
|
+
"bin_PROGRAMS = myapp\nmyapp_SOURCES = myapp.cpp\n");
|
|
557
|
+
const outputDir = path.join(tempDir, "my-release");
|
|
558
|
+
|
|
559
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
560
|
+
const result = pkg.run(outputDir, [projectDir]);
|
|
561
|
+
const errorCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
562
|
+
consoleSpy.mockRestore();
|
|
563
|
+
|
|
564
|
+
expect(result).toBe(false);
|
|
565
|
+
expect(fs.existsSync(outputDir)).toBe(false);
|
|
566
|
+
expect(errorCalls.some(msg => msg.includes("No installed binaries found"))).toBe(true);
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// T016: help subcommand prints usage information (US2)
|
|
571
|
+
describe("PackageCLI - help subcommand", () => {
|
|
572
|
+
it("prints usage information when help subcommand is given", () => {
|
|
573
|
+
const scriptConfig: ScriptArgsType = {
|
|
574
|
+
binName: "the-seed",
|
|
575
|
+
args: ["node", "the-seed", "package", "help"],
|
|
576
|
+
configDir: configDir
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
580
|
+
PackageCLI(scriptConfig);
|
|
581
|
+
const logCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
582
|
+
consoleSpy.mockRestore();
|
|
583
|
+
|
|
584
|
+
expect(logCalls.some(msg => msg.includes("Usage: the-seed package"))).toBe(true);
|
|
585
|
+
expect(logCalls.some(msg => msg.includes("output-directory"))).toBe(true);
|
|
586
|
+
expect(logCalls.some(msg => msg.includes("project-dir"))).toBe(true);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// T017: no arguments prints usage message (US2)
|
|
591
|
+
describe("PackageCLI - no arguments", () => {
|
|
592
|
+
it("prints usage message when no arguments are given", () => {
|
|
593
|
+
const scriptConfig: ScriptArgsType = {
|
|
594
|
+
binName: "the-seed",
|
|
595
|
+
args: ["node", "the-seed", "package"],
|
|
596
|
+
configDir: configDir
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
600
|
+
PackageCLI(scriptConfig);
|
|
601
|
+
const logCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
602
|
+
consoleSpy.mockRestore();
|
|
603
|
+
|
|
604
|
+
expect(logCalls.some(msg => msg.includes("Usage: the-seed package"))).toBe(true);
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// T021: no project dirs specified shows usage error (US3)
|
|
609
|
+
describe("PackageCLI - no project dirs specified", () => {
|
|
610
|
+
it("shows usage error when output dir given but no project dirs", () => {
|
|
611
|
+
const scriptConfig: ScriptArgsType = {
|
|
612
|
+
binName: "the-seed",
|
|
613
|
+
args: ["node", "the-seed", "package", "my-release"],
|
|
614
|
+
configDir: configDir
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
618
|
+
const mockExit = jest.spyOn(process, "exit").mockImplementation((code?: number) => {
|
|
619
|
+
throw new Error("process.exit: " + code);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
expect(() => PackageCLI(scriptConfig)).toThrow("process.exit: 1");
|
|
623
|
+
|
|
624
|
+
const errorCalls = consoleSpy.mock.calls.map(c => c[0]);
|
|
625
|
+
consoleSpy.mockRestore();
|
|
626
|
+
mockExit.mockRestore();
|
|
627
|
+
|
|
628
|
+
expect(errorCalls.some(msg => msg.includes("Usage: the-seed package"))).toBe(true);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
});
|