@forgehive/forge-cli 0.2.4 → 0.2.6
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/tasks/init.js +1 -1
- package/dist/tasks/task/publish.js +2 -0
- package/dist/tasks/task/run.d.ts +5 -1
- package/dist/tasks/task/run.js +54 -5
- package/dist/test/tasks/create.test.js +17 -18
- package/dist/test/tasks/init.test.js +23 -25
- package/dist/test/testUtils.d.ts +8 -0
- package/dist/test/testUtils.js +25 -0
- package/package.json +6 -6
- package/src/tasks/init.ts +1 -1
- package/src/tasks/task/publish.ts +2 -0
- package/src/tasks/task/run.ts +66 -6
- package/src/test/tasks/create.test.ts +18 -19
- package/src/test/tasks/init.test.ts +24 -26
- package/src/test/testUtils.ts +28 -0
- package/src/test/utils.ts +0 -17
package/dist/tasks/init.js
CHANGED
|
@@ -101,6 +101,7 @@ exports.publish = (0, task_1.createTask)(schema, boundaries, async function ({ d
|
|
|
101
101
|
const task = bundle[taskDescriptor.handler];
|
|
102
102
|
const description = task.getDescription() ?? '';
|
|
103
103
|
const schema = task.getSchema() || new schema_1.Schema({});
|
|
104
|
+
const boundaries = Object.keys(task.getBoundaries()) || [];
|
|
104
105
|
const schemaDescriptor = schema.describe();
|
|
105
106
|
// Read the task file content
|
|
106
107
|
const sourceCode = await readFileUtf8(entryPoint);
|
|
@@ -114,6 +115,7 @@ exports.publish = (0, task_1.createTask)(schema, boundaries, async function ({ d
|
|
|
114
115
|
projectName,
|
|
115
116
|
description,
|
|
116
117
|
schemaDescriptor: JSON.stringify(schemaDescriptor),
|
|
118
|
+
boundaries,
|
|
117
119
|
sourceCode,
|
|
118
120
|
bundleSize
|
|
119
121
|
};
|
package/dist/tasks/task/run.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type ForgeConf } from '../types';
|
|
1
|
+
import { type ForgeConf, type Profile } from '../types';
|
|
2
2
|
export declare const run: import("@forgehive/task").TaskInstanceType<(argv: {
|
|
3
3
|
descriptorName: string;
|
|
4
4
|
args: Record<string, string | number | boolean>;
|
|
5
5
|
}, boundaries: import("@forgehive/task").WrappedBoundaries<{
|
|
6
6
|
loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
|
|
7
|
+
loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
|
|
7
8
|
bundleCreate: (args: {
|
|
8
9
|
entryPoint: string;
|
|
9
10
|
outputFile: string;
|
|
@@ -15,8 +16,10 @@ export declare const run: import("@forgehive/task").TaskInstanceType<(argv: {
|
|
|
15
16
|
}) => Promise<Promise<any>>;
|
|
16
17
|
verifyLogFolder: (logsPath: string) => Promise<boolean>;
|
|
17
18
|
ensureBuildsFolder: () => Promise<string>;
|
|
19
|
+
sendLogToAPI: (profile: Profile, projectName: string, taskName: string, logItem: unknown) => Promise<boolean>;
|
|
18
20
|
}>) => Promise<any>, {
|
|
19
21
|
loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
|
|
22
|
+
loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
|
|
20
23
|
bundleCreate: (args: {
|
|
21
24
|
entryPoint: string;
|
|
22
25
|
outputFile: string;
|
|
@@ -28,4 +31,5 @@ export declare const run: import("@forgehive/task").TaskInstanceType<(argv: {
|
|
|
28
31
|
}) => Promise<Promise<any>>;
|
|
29
32
|
verifyLogFolder: (logsPath: string) => Promise<boolean>;
|
|
30
33
|
ensureBuildsFolder: () => Promise<string>;
|
|
34
|
+
sendLogToAPI: (profile: Profile, projectName: string, taskName: string, logItem: unknown) => Promise<boolean>;
|
|
31
35
|
}>;
|
package/dist/tasks/task/run.js
CHANGED
|
@@ -10,12 +10,14 @@ exports.run = void 0;
|
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
12
12
|
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const axios_1 = __importDefault(require("axios"));
|
|
13
14
|
const task_1 = require("@forgehive/task");
|
|
14
15
|
const schema_1 = require("@forgehive/schema");
|
|
15
16
|
const record_tape_1 = require("@forgehive/record-tape");
|
|
16
17
|
const create_1 = require("../bundle/create");
|
|
17
18
|
const load_1 = require("../bundle/load");
|
|
18
19
|
const load_2 = require("../conf/load");
|
|
20
|
+
const loadCurrent_1 = require("../auth/loadCurrent");
|
|
19
21
|
// For now, we'll use a simple schema without the record type
|
|
20
22
|
// TODO: Use Schema.record once it's properly built and available
|
|
21
23
|
const schema = new schema_1.Schema({
|
|
@@ -25,6 +27,7 @@ const schema = new schema_1.Schema({
|
|
|
25
27
|
});
|
|
26
28
|
const boundaries = {
|
|
27
29
|
loadConf: load_2.load.asBoundary(),
|
|
30
|
+
loadCurrentProfile: loadCurrent_1.loadCurrent.asBoundary(),
|
|
28
31
|
bundleCreate: create_1.create.asBoundary(),
|
|
29
32
|
bundleLoad: load_1.load.asBoundary(),
|
|
30
33
|
verifyLogFolder: async (logsPath) => {
|
|
@@ -46,15 +49,49 @@ const boundaries = {
|
|
|
46
49
|
await promises_1.default.mkdir(buildsPath, { recursive: true });
|
|
47
50
|
}
|
|
48
51
|
return buildsPath;
|
|
52
|
+
},
|
|
53
|
+
sendLogToAPI: async (profile, projectName, taskName, logItem) => {
|
|
54
|
+
try {
|
|
55
|
+
const logsUrl = `${profile.url}/api/tasks/log-ingest`;
|
|
56
|
+
const authToken = `${profile.apiKey}:${profile.apiSecret}`;
|
|
57
|
+
await axios_1.default.post(logsUrl, {
|
|
58
|
+
projectName,
|
|
59
|
+
taskName,
|
|
60
|
+
logItem: JSON.stringify(logItem)
|
|
61
|
+
}, {
|
|
62
|
+
headers: {
|
|
63
|
+
Authorization: `Bearer ${authToken}`,
|
|
64
|
+
'Content-Type': 'application/json'
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
console.log('===============================================');
|
|
68
|
+
console.log('Log sent to API... ', profile.name, profile.url);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
const error = e;
|
|
73
|
+
console.error('Failed to send log to API:', error.message);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
49
76
|
}
|
|
50
77
|
};
|
|
51
|
-
exports.run = (0, task_1.createTask)(schema, boundaries, async function ({ descriptorName, args }, { loadConf, bundleCreate, bundleLoad, verifyLogFolder, ensureBuildsFolder }) {
|
|
78
|
+
exports.run = (0, task_1.createTask)(schema, boundaries, async function ({ descriptorName, args }, { loadConf, bundleCreate, bundleLoad, verifyLogFolder, ensureBuildsFolder, loadCurrentProfile, sendLogToAPI }) {
|
|
52
79
|
// Load forge configuration
|
|
53
80
|
const forge = await loadConf({});
|
|
54
81
|
const taskDescriptor = forge.tasks[descriptorName];
|
|
82
|
+
const projectName = forge.project.name;
|
|
55
83
|
if (taskDescriptor === undefined) {
|
|
56
84
|
throw new Error('Task is not defined on forge.json');
|
|
57
85
|
}
|
|
86
|
+
// Try to load profile, but continue if not found
|
|
87
|
+
let profile = null;
|
|
88
|
+
try {
|
|
89
|
+
profile = await loadCurrentProfile({});
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// Profile not found or not configured, continue without it
|
|
93
|
+
console.log('No profile found, logs will not be sent to remote API');
|
|
94
|
+
}
|
|
58
95
|
// Verify if log folder exists
|
|
59
96
|
const logFolderPath = path_1.default.join(process.cwd(), forge.paths.logs);
|
|
60
97
|
const logFolderExists = await verifyLogFolder(logFolderPath);
|
|
@@ -105,14 +142,26 @@ exports.run = (0, task_1.createTask)(schema, boundaries, async function ({ descr
|
|
|
105
142
|
}
|
|
106
143
|
tape.recordFrom(descriptorName, task);
|
|
107
144
|
// Run the task with provided arguments
|
|
108
|
-
let result;
|
|
145
|
+
let result, error;
|
|
109
146
|
try {
|
|
110
147
|
result = await task.run(args);
|
|
111
148
|
}
|
|
112
|
-
catch (
|
|
113
|
-
|
|
114
|
-
throw error;
|
|
149
|
+
catch (e) {
|
|
150
|
+
error = e;
|
|
115
151
|
}
|
|
116
152
|
await tape.save();
|
|
153
|
+
const logItems = tape.getLog();
|
|
154
|
+
const lastLogItem = logItems[logItems.length - 1];
|
|
155
|
+
if (profile) {
|
|
156
|
+
try {
|
|
157
|
+
await sendLogToAPI(profile, projectName, descriptorName, lastLogItem);
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
console.error('Failed to send log to API:', e);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (error) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
117
166
|
return result;
|
|
118
167
|
});
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const createTask_1 = require("../../tasks/task/createTask");
|
|
7
7
|
const memfs_1 = require("memfs");
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const
|
|
9
|
+
const testUtils_1 = require("../testUtils");
|
|
10
10
|
// Verify the task file content
|
|
11
11
|
const expectedContent = `// TASK: newTask
|
|
12
12
|
// Run this task with:
|
|
@@ -58,32 +58,31 @@ describe('Create task', () => {
|
|
|
58
58
|
});
|
|
59
59
|
afterEach(() => {
|
|
60
60
|
jest.clearAllMocks();
|
|
61
|
+
// Reset any boundary mocks
|
|
62
|
+
createTask_1.createTaskCommand.resetMocks();
|
|
61
63
|
});
|
|
62
64
|
it('should create a new task file with correct content and update forge.json', async () => {
|
|
63
|
-
// Create
|
|
64
|
-
const
|
|
65
|
-
const persistConfMock = (0, utils_1.createBoundaryMock)();
|
|
66
|
-
const getCwdMock = (0, utils_1.createBoundaryMock)();
|
|
67
|
-
const persistTaskFn = persistTaskMock;
|
|
68
|
-
const persistConfFn = persistConfMock;
|
|
69
|
-
const getCwdFn = getCwdMock;
|
|
70
|
-
// Override the persistTask implementation to use our in-memory fs
|
|
71
|
-
persistTaskFn.mockImplementation(async (dir, fileName, content, cwd) => {
|
|
65
|
+
// Create mock functions with Jest
|
|
66
|
+
const persistTaskFn = jest.fn().mockImplementation(async (dir, fileName, content, cwd) => {
|
|
72
67
|
const fullPath = path_1.default.join(cwd, dir, fileName);
|
|
73
68
|
await fs.promises.writeFile(fullPath, content);
|
|
74
69
|
return { path: fullPath };
|
|
75
70
|
});
|
|
76
|
-
//
|
|
77
|
-
persistConfFn.mockImplementation(async (conf, cwd) => {
|
|
71
|
+
// Mock persistConf to use our in-memory fs
|
|
72
|
+
const persistConfFn = jest.fn().mockImplementation(async (conf, cwd) => {
|
|
78
73
|
const forgePath = path_1.default.join(cwd, 'forge.json');
|
|
79
74
|
await fs.promises.writeFile(forgePath, JSON.stringify(conf, null, 2));
|
|
80
75
|
});
|
|
81
|
-
//
|
|
82
|
-
getCwdFn.mockResolvedValue(rootDir);
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
// Mock getCwd to return our root directory
|
|
77
|
+
const getCwdFn = jest.fn().mockResolvedValue(rootDir);
|
|
78
|
+
// Create boundary mocks with proper type casting
|
|
79
|
+
const persistTaskMock = (0, testUtils_1.createMockBoundary)(persistTaskFn);
|
|
80
|
+
const persistConfMock = (0, testUtils_1.createMockBoundary)(persistConfFn);
|
|
81
|
+
const getCwdMock = (0, testUtils_1.createMockBoundary)(getCwdFn);
|
|
82
|
+
// Use the mockBoundary method to mock the boundaries
|
|
83
|
+
createTask_1.createTaskCommand.mockBoundary('persistTask', persistTaskMock);
|
|
84
|
+
createTask_1.createTaskCommand.mockBoundary('persistConf', persistConfMock);
|
|
85
|
+
createTask_1.createTaskCommand.mockBoundary('getCwd', getCwdMock);
|
|
87
86
|
// Run the task
|
|
88
87
|
const taskName = 'sample:new-task';
|
|
89
88
|
await createTask_1.createTaskCommand.run({ descriptorName: taskName });
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const init_1 = require("../../tasks/init");
|
|
7
7
|
const memfs_1 = require("memfs");
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const
|
|
9
|
+
const testUtils_1 = require("../testUtils");
|
|
10
10
|
describe('Init task', () => {
|
|
11
11
|
let volume;
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -20,30 +20,29 @@ describe('Init task', () => {
|
|
|
20
20
|
});
|
|
21
21
|
afterEach(() => {
|
|
22
22
|
jest.clearAllMocks();
|
|
23
|
+
// Reset any boundary mocks after each test
|
|
24
|
+
init_1.init.resetMocks();
|
|
23
25
|
});
|
|
24
26
|
it('should create forge.json with correct content in the filesystem', async () => {
|
|
25
|
-
// Create
|
|
26
|
-
const
|
|
27
|
-
const getCwdMock = (0, utils_1.createBoundaryMock)();
|
|
28
|
-
const saveFileFn = saveFileMock;
|
|
29
|
-
const getCwdFn = getCwdMock;
|
|
30
|
-
// Override the saveFile implementation to use our in-memory fs
|
|
31
|
-
saveFileFn.mockImplementation(async (filePath, content) => {
|
|
27
|
+
// Create mocks directly using Jest
|
|
28
|
+
const saveFileFn = jest.fn().mockImplementation(async (filePath, content) => {
|
|
32
29
|
const fullPath = path_1.default.join(rootDir, filePath);
|
|
33
30
|
await fs.promises.writeFile(fullPath, content);
|
|
34
31
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const getCwdFn = jest.fn().mockResolvedValue(rootDir);
|
|
33
|
+
// Create wrapped boundary mocks
|
|
34
|
+
const saveFileMock = (0, testUtils_1.createMockBoundary)(saveFileFn);
|
|
35
|
+
const getCwdMock = (0, testUtils_1.createMockBoundary)(getCwdFn);
|
|
36
|
+
// Mock the boundaries
|
|
37
|
+
init_1.init.mockBoundary('saveFile', saveFileMock);
|
|
38
|
+
init_1.init.mockBoundary('getCwd', getCwdMock);
|
|
40
39
|
// Run the task
|
|
41
40
|
await init_1.init.run({});
|
|
42
41
|
// Read the created file
|
|
43
42
|
const fileContent = await fs.promises.readFile(path_1.default.join(rootDir, 'forge.json'), 'utf-8');
|
|
44
43
|
const config = JSON.parse(fileContent);
|
|
45
44
|
// Verify the file content
|
|
46
|
-
expect(config).toHaveProperty('project.name', '
|
|
45
|
+
expect(config).toHaveProperty('project.name', 'BaseProject');
|
|
47
46
|
expect(config).toHaveProperty('paths.logs', 'logs/');
|
|
48
47
|
expect(config).toHaveProperty('paths.tasks', 'src/tasks/');
|
|
49
48
|
expect(config).toHaveProperty('infra.region', 'us-west-2');
|
|
@@ -51,22 +50,21 @@ describe('Init task', () => {
|
|
|
51
50
|
expect(config).toHaveProperty('runners');
|
|
52
51
|
});
|
|
53
52
|
it('should not create forge.json when dryRun is true', async () => {
|
|
54
|
-
// Create
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
init_1.init.
|
|
63
|
-
init_1.init.getBoundaries().getCwd = getCwdMock;
|
|
53
|
+
// Create mocks directly using Jest
|
|
54
|
+
const saveFileFn = jest.fn();
|
|
55
|
+
const getCwdFn = jest.fn().mockResolvedValue(rootDir);
|
|
56
|
+
// Create wrapped boundary mocks
|
|
57
|
+
const saveFileMock = (0, testUtils_1.createMockBoundary)(saveFileFn);
|
|
58
|
+
const getCwdMock = (0, testUtils_1.createMockBoundary)(getCwdFn);
|
|
59
|
+
// Mock the boundaries
|
|
60
|
+
init_1.init.mockBoundary('saveFile', saveFileMock);
|
|
61
|
+
init_1.init.mockBoundary('getCwd', getCwdMock);
|
|
64
62
|
// Run the task with dryRun flag
|
|
65
63
|
const result = await init_1.init.run({ dryRun: true });
|
|
66
64
|
// Verify saveFile was not called
|
|
67
65
|
expect(saveFileFn).not.toHaveBeenCalled();
|
|
68
66
|
// Verify the returned config has the correct structure
|
|
69
|
-
expect(result).toHaveProperty('project.name', '
|
|
67
|
+
expect(result).toHaveProperty('project.name', 'BaseProject');
|
|
70
68
|
expect(result).toHaveProperty('paths.logs', 'logs/');
|
|
71
69
|
expect(result).toHaveProperty('paths.tasks', 'src/tasks/');
|
|
72
70
|
expect(result).toHaveProperty('infra.region', 'us-west-2');
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type WrappedBoundaryFunction } from '@forgehive/task';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock boundary function that implements the WrappedBoundaryFunction interface
|
|
4
|
+
*
|
|
5
|
+
* @param mockFn Optional Jest mock function to use as the base function
|
|
6
|
+
* @returns A wrapped boundary function compatible with task.mockBoundary()
|
|
7
|
+
*/
|
|
8
|
+
export declare const createMockBoundary: (mockFn?: jest.Mock) => WrappedBoundaryFunction;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMockBoundary = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a mock boundary function that implements the WrappedBoundaryFunction interface
|
|
6
|
+
*
|
|
7
|
+
* @param mockFn Optional Jest mock function to use as the base function
|
|
8
|
+
* @returns A wrapped boundary function compatible with task.mockBoundary()
|
|
9
|
+
*/
|
|
10
|
+
const createMockBoundary = (mockFn) => {
|
|
11
|
+
// Use provided mock or create a new one
|
|
12
|
+
const baseMockFn = mockFn || jest.fn().mockResolvedValue(undefined);
|
|
13
|
+
// Create a proper boundary function object that extends the mock function
|
|
14
|
+
const boundaryMock = Object.assign(baseMockFn, {
|
|
15
|
+
getTape: jest.fn().mockReturnValue([]),
|
|
16
|
+
setTape: jest.fn(),
|
|
17
|
+
getMode: jest.fn().mockReturnValue('proxy'),
|
|
18
|
+
setMode: jest.fn(),
|
|
19
|
+
startRun: jest.fn(),
|
|
20
|
+
stopRun: jest.fn(),
|
|
21
|
+
getRunData: jest.fn().mockReturnValue([])
|
|
22
|
+
});
|
|
23
|
+
return boundaryMock;
|
|
24
|
+
};
|
|
25
|
+
exports.createMockBoundary = createMockBoundary;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/forge-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "TypeScript CLI application",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@forgehive/record-tape": "^0.0.1",
|
|
14
14
|
"@forgehive/runner": "^0.1.4",
|
|
15
15
|
"@forgehive/schema": "^0.1.4",
|
|
16
|
-
"@forgehive/task": "^0.1.
|
|
16
|
+
"@forgehive/task": "^0.1.6",
|
|
17
17
|
"esbuild": "^0.25.0",
|
|
18
18
|
"handlebars": "^4.7.8",
|
|
19
19
|
"minimist": "^1.2.8"
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"esbuild": "^0.25.0",
|
|
26
26
|
"handlebars": "^4.7.8",
|
|
27
27
|
"minimist": "^1.2.8",
|
|
28
|
-
"@forgehive/
|
|
29
|
-
"@forgehive/
|
|
30
|
-
"@forgehive/
|
|
31
|
-
"@forgehive/
|
|
28
|
+
"@forgehive/record-tape": "0.0.3",
|
|
29
|
+
"@forgehive/runner": "0.1.6",
|
|
30
|
+
"@forgehive/schema": "0.1.4",
|
|
31
|
+
"@forgehive/task": "0.1.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/jest": "^29.5.3",
|
package/src/tasks/init.ts
CHANGED
|
@@ -126,6 +126,7 @@ export const publish = createTask(
|
|
|
126
126
|
const task = bundle[taskDescriptor.handler]
|
|
127
127
|
const description = task.getDescription() ?? ''
|
|
128
128
|
const schema = task.getSchema() || new Schema({})
|
|
129
|
+
const boundaries = Object.keys(task.getBoundaries()) || []
|
|
129
130
|
const schemaDescriptor = schema.describe()
|
|
130
131
|
|
|
131
132
|
// Read the task file content
|
|
@@ -142,6 +143,7 @@ export const publish = createTask(
|
|
|
142
143
|
projectName,
|
|
143
144
|
description,
|
|
144
145
|
schemaDescriptor: JSON.stringify(schemaDescriptor),
|
|
146
|
+
boundaries,
|
|
145
147
|
sourceCode,
|
|
146
148
|
bundleSize
|
|
147
149
|
}
|
package/src/tasks/task/run.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import fs from 'fs/promises'
|
|
7
7
|
import os from 'os'
|
|
8
|
+
import axios from 'axios'
|
|
8
9
|
|
|
9
10
|
import { createTask } from '@forgehive/task'
|
|
10
11
|
import { Schema } from '@forgehive/schema'
|
|
@@ -13,7 +14,8 @@ import { RecordTape } from '@forgehive/record-tape'
|
|
|
13
14
|
import { create as bundleCreate } from '../bundle/create'
|
|
14
15
|
import { load as bundleLoad } from '../bundle/load'
|
|
15
16
|
import { load as loadConf } from '../conf/load'
|
|
16
|
-
import {
|
|
17
|
+
import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
|
|
18
|
+
import { type ForgeConf, type Profile } from '../types'
|
|
17
19
|
|
|
18
20
|
// For now, we'll use a simple schema without the record type
|
|
19
21
|
// TODO: Use Schema.record once it's properly built and available
|
|
@@ -25,6 +27,7 @@ const schema = new Schema({
|
|
|
25
27
|
|
|
26
28
|
const boundaries = {
|
|
27
29
|
loadConf: loadConf.asBoundary(),
|
|
30
|
+
loadCurrentProfile: loadCurrentProfile.asBoundary(),
|
|
28
31
|
bundleCreate: bundleCreate.asBoundary(),
|
|
29
32
|
bundleLoad: bundleLoad.asBoundary(),
|
|
30
33
|
verifyLogFolder: async (logsPath: string): Promise<boolean> => {
|
|
@@ -46,21 +49,65 @@ const boundaries = {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
return buildsPath
|
|
52
|
+
},
|
|
53
|
+
sendLogToAPI: async (profile: Profile, projectName: string, taskName: string, logItem: unknown): Promise<boolean> => {
|
|
54
|
+
try {
|
|
55
|
+
const logsUrl = `${profile.url}/api/tasks/log-ingest`
|
|
56
|
+
const authToken = `${profile.apiKey}:${profile.apiSecret}`
|
|
57
|
+
|
|
58
|
+
await axios.post(logsUrl, {
|
|
59
|
+
projectName,
|
|
60
|
+
taskName,
|
|
61
|
+
logItem: JSON.stringify(logItem)
|
|
62
|
+
}, {
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: `Bearer ${authToken}`,
|
|
65
|
+
'Content-Type': 'application/json'
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
console.log('===============================================')
|
|
70
|
+
console.log('Log sent to API... ', profile.name, profile.url)
|
|
71
|
+
|
|
72
|
+
return true
|
|
73
|
+
} catch (e) {
|
|
74
|
+
const error = e as Error
|
|
75
|
+
console.error('Failed to send log to API:', error.message)
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
49
78
|
}
|
|
50
79
|
}
|
|
51
80
|
|
|
52
81
|
export const run = createTask(
|
|
53
82
|
schema,
|
|
54
83
|
boundaries,
|
|
55
|
-
async function ({ descriptorName, args }, {
|
|
84
|
+
async function ({ descriptorName, args }, {
|
|
85
|
+
loadConf,
|
|
86
|
+
bundleCreate,
|
|
87
|
+
bundleLoad,
|
|
88
|
+
verifyLogFolder,
|
|
89
|
+
ensureBuildsFolder,
|
|
90
|
+
loadCurrentProfile,
|
|
91
|
+
sendLogToAPI
|
|
92
|
+
}) {
|
|
56
93
|
// Load forge configuration
|
|
57
94
|
const forge: ForgeConf = await loadConf({})
|
|
58
95
|
const taskDescriptor = forge.tasks[descriptorName as keyof typeof forge.tasks]
|
|
96
|
+
const projectName = forge.project.name
|
|
59
97
|
|
|
60
98
|
if (taskDescriptor === undefined) {
|
|
61
99
|
throw new Error('Task is not defined on forge.json')
|
|
62
100
|
}
|
|
63
101
|
|
|
102
|
+
// Try to load profile, but continue if not found
|
|
103
|
+
let profile = null
|
|
104
|
+
try {
|
|
105
|
+
profile = await loadCurrentProfile({})
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Profile not found or not configured, continue without it
|
|
108
|
+
console.log('No profile found, logs will not be sent to remote API')
|
|
109
|
+
}
|
|
110
|
+
|
|
64
111
|
// Verify if log folder exists
|
|
65
112
|
const logFolderPath = path.join(process.cwd(), forge.paths.logs)
|
|
66
113
|
const logFolderExists = await verifyLogFolder(logFolderPath)
|
|
@@ -122,15 +169,28 @@ export const run = createTask(
|
|
|
122
169
|
tape.recordFrom(descriptorName, task)
|
|
123
170
|
|
|
124
171
|
// Run the task with provided arguments
|
|
125
|
-
let result
|
|
172
|
+
let result, error
|
|
126
173
|
try {
|
|
127
174
|
result = await task.run(args)
|
|
128
|
-
} catch (
|
|
129
|
-
|
|
130
|
-
throw error
|
|
175
|
+
} catch (e) {
|
|
176
|
+
error = e as Error
|
|
131
177
|
}
|
|
132
178
|
|
|
133
179
|
await tape.save()
|
|
180
|
+
const logItems = tape.getLog()
|
|
181
|
+
const lastLogItem = logItems[logItems.length - 1]
|
|
182
|
+
|
|
183
|
+
if (profile) {
|
|
184
|
+
try {
|
|
185
|
+
await sendLogToAPI(profile, projectName, descriptorName, lastLogItem)
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error('Failed to send log to API:', e)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (error) {
|
|
192
|
+
throw error
|
|
193
|
+
}
|
|
134
194
|
|
|
135
195
|
return result
|
|
136
196
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createTaskCommand } from '../../tasks/task/createTask'
|
|
2
2
|
import { createFsFromVolume, Volume } from 'memfs'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import {
|
|
4
|
+
import { createMockBoundary } from '../testUtils'
|
|
5
5
|
import { ForgeConf } from '../../tasks/types'
|
|
6
6
|
|
|
7
7
|
// Verify the task file content
|
|
@@ -59,37 +59,36 @@ describe('Create task', () => {
|
|
|
59
59
|
|
|
60
60
|
afterEach(() => {
|
|
61
61
|
jest.clearAllMocks()
|
|
62
|
+
// Reset any boundary mocks
|
|
63
|
+
createTaskCommand.resetMocks()
|
|
62
64
|
})
|
|
63
65
|
|
|
64
66
|
it('should create a new task file with correct content and update forge.json', async () => {
|
|
65
|
-
// Create
|
|
66
|
-
const
|
|
67
|
-
const persistConfMock = createBoundaryMock()
|
|
68
|
-
const getCwdMock = createBoundaryMock()
|
|
69
|
-
const persistTaskFn = persistTaskMock as unknown as jest.Mock
|
|
70
|
-
const persistConfFn = persistConfMock as unknown as jest.Mock
|
|
71
|
-
const getCwdFn = getCwdMock as unknown as jest.Mock
|
|
72
|
-
|
|
73
|
-
// Override the persistTask implementation to use our in-memory fs
|
|
74
|
-
persistTaskFn.mockImplementation(async (dir: string, fileName: string, content: string, cwd: string) => {
|
|
67
|
+
// Create mock functions with Jest
|
|
68
|
+
const persistTaskFn = jest.fn().mockImplementation(async (dir: string, fileName: string, content: string, cwd: string) => {
|
|
75
69
|
const fullPath = path.join(cwd, dir, fileName)
|
|
76
70
|
await (fs as { promises: { writeFile: (path: string, content: string) => Promise<void> } }).promises.writeFile(fullPath, content)
|
|
77
71
|
return { path: fullPath }
|
|
78
72
|
})
|
|
79
73
|
|
|
80
|
-
//
|
|
81
|
-
persistConfFn.mockImplementation(async (conf: ForgeConf, cwd: string) => {
|
|
74
|
+
// Mock persistConf to use our in-memory fs
|
|
75
|
+
const persistConfFn = jest.fn().mockImplementation(async (conf: ForgeConf, cwd: string) => {
|
|
82
76
|
const forgePath = path.join(cwd, 'forge.json')
|
|
83
77
|
await (fs as { promises: { writeFile: (path: string, content: string) => Promise<void> } }).promises.writeFile(forgePath, JSON.stringify(conf, null, 2))
|
|
84
78
|
})
|
|
85
79
|
|
|
86
|
-
//
|
|
87
|
-
getCwdFn.mockResolvedValue(rootDir)
|
|
80
|
+
// Mock getCwd to return our root directory
|
|
81
|
+
const getCwdFn = jest.fn().mockResolvedValue(rootDir)
|
|
88
82
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
// Create boundary mocks with proper type casting
|
|
84
|
+
const persistTaskMock = createMockBoundary(persistTaskFn)
|
|
85
|
+
const persistConfMock = createMockBoundary(persistConfFn)
|
|
86
|
+
const getCwdMock = createMockBoundary(getCwdFn)
|
|
87
|
+
|
|
88
|
+
// Use the mockBoundary method to mock the boundaries
|
|
89
|
+
createTaskCommand.mockBoundary('persistTask', persistTaskMock)
|
|
90
|
+
createTaskCommand.mockBoundary('persistConf', persistConfMock)
|
|
91
|
+
createTaskCommand.mockBoundary('getCwd', getCwdMock)
|
|
93
92
|
|
|
94
93
|
// Run the task
|
|
95
94
|
const taskName = 'sample:new-task'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { init } from '../../tasks/init'
|
|
2
2
|
import { createFsFromVolume, Volume } from 'memfs'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import {
|
|
4
|
+
import { createMockBoundary } from '../testUtils'
|
|
5
5
|
|
|
6
6
|
describe('Init task', () => {
|
|
7
7
|
let volume: InstanceType<typeof Volume>
|
|
@@ -18,27 +18,26 @@ describe('Init task', () => {
|
|
|
18
18
|
|
|
19
19
|
afterEach(() => {
|
|
20
20
|
jest.clearAllMocks()
|
|
21
|
+
// Reset any boundary mocks after each test
|
|
22
|
+
init.resetMocks()
|
|
21
23
|
})
|
|
22
24
|
|
|
23
25
|
it('should create forge.json with correct content in the filesystem', async () => {
|
|
24
|
-
// Create
|
|
25
|
-
const
|
|
26
|
-
const getCwdMock = createBoundaryMock()
|
|
27
|
-
const saveFileFn = saveFileMock as unknown as jest.Mock
|
|
28
|
-
const getCwdFn = getCwdMock as unknown as jest.Mock
|
|
29
|
-
|
|
30
|
-
// Override the saveFile implementation to use our in-memory fs
|
|
31
|
-
saveFileFn.mockImplementation(async (filePath: string, content: string) => {
|
|
26
|
+
// Create mocks directly using Jest
|
|
27
|
+
const saveFileFn = jest.fn().mockImplementation(async (filePath: string, content: string) => {
|
|
32
28
|
const fullPath = path.join(rootDir, filePath)
|
|
33
29
|
await (fs as { promises: { writeFile: (path: string, content: string) => Promise<void> } }).promises.writeFile(fullPath, content)
|
|
34
30
|
})
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
getCwdFn.mockResolvedValue(rootDir)
|
|
32
|
+
const getCwdFn = jest.fn().mockResolvedValue(rootDir)
|
|
38
33
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
// Create wrapped boundary mocks
|
|
35
|
+
const saveFileMock = createMockBoundary(saveFileFn)
|
|
36
|
+
const getCwdMock = createMockBoundary(getCwdFn)
|
|
37
|
+
|
|
38
|
+
// Mock the boundaries
|
|
39
|
+
init.mockBoundary('saveFile', saveFileMock)
|
|
40
|
+
init.mockBoundary('getCwd', getCwdMock)
|
|
42
41
|
|
|
43
42
|
// Run the task
|
|
44
43
|
await init.run({})
|
|
@@ -48,7 +47,7 @@ describe('Init task', () => {
|
|
|
48
47
|
const config = JSON.parse(fileContent)
|
|
49
48
|
|
|
50
49
|
// Verify the file content
|
|
51
|
-
expect(config).toHaveProperty('project.name', '
|
|
50
|
+
expect(config).toHaveProperty('project.name', 'BaseProject')
|
|
52
51
|
expect(config).toHaveProperty('paths.logs', 'logs/')
|
|
53
52
|
expect(config).toHaveProperty('paths.tasks', 'src/tasks/')
|
|
54
53
|
expect(config).toHaveProperty('infra.region', 'us-west-2')
|
|
@@ -57,18 +56,17 @@ describe('Init task', () => {
|
|
|
57
56
|
})
|
|
58
57
|
|
|
59
58
|
it('should not create forge.json when dryRun is true', async () => {
|
|
60
|
-
// Create
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const saveFileFn = saveFileMock as unknown as jest.Mock
|
|
64
|
-
const getCwdFn = getCwdMock as unknown as jest.Mock
|
|
59
|
+
// Create mocks directly using Jest
|
|
60
|
+
const saveFileFn = jest.fn()
|
|
61
|
+
const getCwdFn = jest.fn().mockResolvedValue(rootDir)
|
|
65
62
|
|
|
66
|
-
//
|
|
67
|
-
|
|
63
|
+
// Create wrapped boundary mocks
|
|
64
|
+
const saveFileMock = createMockBoundary(saveFileFn)
|
|
65
|
+
const getCwdMock = createMockBoundary(getCwdFn)
|
|
68
66
|
|
|
69
|
-
//
|
|
70
|
-
init.
|
|
71
|
-
init.
|
|
67
|
+
// Mock the boundaries
|
|
68
|
+
init.mockBoundary('saveFile', saveFileMock)
|
|
69
|
+
init.mockBoundary('getCwd', getCwdMock)
|
|
72
70
|
|
|
73
71
|
// Run the task with dryRun flag
|
|
74
72
|
const result = await init.run({ dryRun: true })
|
|
@@ -77,7 +75,7 @@ describe('Init task', () => {
|
|
|
77
75
|
expect(saveFileFn).not.toHaveBeenCalled()
|
|
78
76
|
|
|
79
77
|
// Verify the returned config has the correct structure
|
|
80
|
-
expect(result).toHaveProperty('project.name', '
|
|
78
|
+
expect(result).toHaveProperty('project.name', 'BaseProject')
|
|
81
79
|
expect(result).toHaveProperty('paths.logs', 'logs/')
|
|
82
80
|
expect(result).toHaveProperty('paths.tasks', 'src/tasks/')
|
|
83
81
|
expect(result).toHaveProperty('infra.region', 'us-west-2')
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type WrappedBoundaryFunction } from '@forgehive/task'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a mock boundary function that implements the WrappedBoundaryFunction interface
|
|
5
|
+
*
|
|
6
|
+
* @param mockFn Optional Jest mock function to use as the base function
|
|
7
|
+
* @returns A wrapped boundary function compatible with task.mockBoundary()
|
|
8
|
+
*/
|
|
9
|
+
export const createMockBoundary = (mockFn?: jest.Mock): WrappedBoundaryFunction => {
|
|
10
|
+
// Use provided mock or create a new one
|
|
11
|
+
const baseMockFn = mockFn || jest.fn().mockResolvedValue(undefined)
|
|
12
|
+
|
|
13
|
+
// Create a proper boundary function object that extends the mock function
|
|
14
|
+
const boundaryMock = Object.assign(
|
|
15
|
+
baseMockFn,
|
|
16
|
+
{
|
|
17
|
+
getTape: jest.fn().mockReturnValue([]),
|
|
18
|
+
setTape: jest.fn(),
|
|
19
|
+
getMode: jest.fn().mockReturnValue('proxy'),
|
|
20
|
+
setMode: jest.fn(),
|
|
21
|
+
startRun: jest.fn(),
|
|
22
|
+
stopRun: jest.fn(),
|
|
23
|
+
getRunData: jest.fn().mockReturnValue([])
|
|
24
|
+
}
|
|
25
|
+
) as WrappedBoundaryFunction
|
|
26
|
+
|
|
27
|
+
return boundaryMock
|
|
28
|
+
}
|
package/src/test/utils.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type WrappedBoundaryFunction } from '@forgehive/task'
|
|
2
|
-
|
|
3
|
-
export const createBoundaryMock = (): WrappedBoundaryFunction => {
|
|
4
|
-
const mockFn = jest.fn().mockResolvedValue(undefined)
|
|
5
|
-
const boundaryMock = mockFn as unknown as WrappedBoundaryFunction
|
|
6
|
-
|
|
7
|
-
// Add required methods to satisfy the interface
|
|
8
|
-
boundaryMock.getTape = jest.fn().mockReturnValue([])
|
|
9
|
-
boundaryMock.setTape = jest.fn()
|
|
10
|
-
boundaryMock.getMode = jest.fn().mockReturnValue('proxy')
|
|
11
|
-
boundaryMock.setMode = jest.fn()
|
|
12
|
-
boundaryMock.startRun = jest.fn()
|
|
13
|
-
boundaryMock.stopRun = jest.fn()
|
|
14
|
-
boundaryMock.getRunData = jest.fn().mockReturnValue([])
|
|
15
|
-
|
|
16
|
-
return boundaryMock
|
|
17
|
-
}
|