@forgehive/forge-cli 0.2.14 → 0.3.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 (67) hide show
  1. package/dist/runner.js +3 -1
  2. package/dist/tasks/auth/add.js +23 -19
  3. package/dist/tasks/auth/list.js +20 -16
  4. package/dist/tasks/auth/load.js +19 -15
  5. package/dist/tasks/auth/loadCurrent.js +13 -9
  6. package/dist/tasks/auth/remove.js +30 -26
  7. package/dist/tasks/auth/switch.js +19 -15
  8. package/dist/tasks/bundle/create.js +16 -12
  9. package/dist/tasks/bundle/fingerprint.d.ts +36 -0
  10. package/dist/tasks/bundle/fingerprint.js +164 -0
  11. package/dist/tasks/bundle/load.js +9 -5
  12. package/dist/tasks/bundle/zip.js +49 -45
  13. package/dist/tasks/conf/info.js +23 -19
  14. package/dist/tasks/conf/load.js +8 -4
  15. package/dist/tasks/fixture/download.js +40 -36
  16. package/dist/tasks/init.js +35 -31
  17. package/dist/tasks/runner/bundle.js +34 -30
  18. package/dist/tasks/runner/create.js +28 -24
  19. package/dist/tasks/runner/remove.js +22 -18
  20. package/dist/tasks/task/createTask.js +35 -28
  21. package/dist/tasks/task/describe.js +85 -81
  22. package/dist/tasks/task/download.js +63 -59
  23. package/dist/tasks/task/fingerprint.d.ts +26 -0
  24. package/dist/tasks/task/fingerprint.js +87 -0
  25. package/dist/tasks/task/list.js +27 -23
  26. package/dist/tasks/task/publish.js +72 -68
  27. package/dist/tasks/task/remove.js +24 -20
  28. package/dist/tasks/task/replay.js +94 -90
  29. package/dist/tasks/task/run.js +78 -79
  30. package/dist/test/tasks/create.test.js +6 -5
  31. package/dist/utils/taskAnalysis.d.ts +21 -0
  32. package/dist/utils/taskAnalysis.js +380 -0
  33. package/forge.json +12 -0
  34. package/logs/task:fingerprint.log +10 -0
  35. package/package.json +7 -7
  36. package/specs/fingerprint.md +380 -0
  37. package/src/runner.ts +3 -1
  38. package/src/tasks/README.md +13 -13
  39. package/src/tasks/auth/add.ts +3 -3
  40. package/src/tasks/auth/list.ts +3 -3
  41. package/src/tasks/auth/load.ts +3 -3
  42. package/src/tasks/auth/loadCurrent.ts +3 -3
  43. package/src/tasks/auth/remove.ts +3 -3
  44. package/src/tasks/auth/switch.ts +3 -3
  45. package/src/tasks/bundle/README.md +7 -7
  46. package/src/tasks/bundle/create.ts +4 -4
  47. package/src/tasks/bundle/fingerprint.ts +218 -0
  48. package/src/tasks/bundle/load.ts +4 -4
  49. package/src/tasks/bundle/zip.ts +3 -3
  50. package/src/tasks/conf/info.ts +3 -3
  51. package/src/tasks/conf/load.ts +3 -3
  52. package/src/tasks/fixture/download.ts +3 -3
  53. package/src/tasks/init.ts +3 -3
  54. package/src/tasks/runner/bundle.ts +3 -3
  55. package/src/tasks/runner/create.ts +3 -3
  56. package/src/tasks/runner/remove.ts +3 -3
  57. package/src/tasks/task/createTask.ts +10 -7
  58. package/src/tasks/task/describe.ts +3 -3
  59. package/src/tasks/task/download.ts +3 -3
  60. package/src/tasks/task/fingerprint.ts +107 -0
  61. package/src/tasks/task/list.ts +3 -3
  62. package/src/tasks/task/publish.ts +3 -3
  63. package/src/tasks/task/remove.ts +3 -3
  64. package/src/tasks/task/replay.ts +3 -3
  65. package/src/tasks/task/run.ts +12 -18
  66. package/src/test/tasks/create.test.ts +9 -9
  67. package/src/utils/taskAnalysis.ts +419 -0
@@ -25,29 +25,33 @@ const boundaries = {
25
25
  await promises_1.default.unlink(filePath);
26
26
  }
27
27
  };
28
- exports.remove = (0, task_1.createTask)(schema, boundaries, async function ({ descriptorName }, { loadConf, persistConf, deleteFile }) {
29
- // Load shadow configuration
30
- const forge = await loadConf({});
31
- // Check if the task exists in shadow.json
32
- if (!forge.tasks[descriptorName]) {
33
- throw new Error(`Task '${descriptorName}' not found in forge.json`);
34
- }
35
- // Get the task file path
36
- const taskFilePath = path_1.default.join(process.cwd(), forge.tasks[descriptorName].path);
37
- console.log(`
28
+ exports.remove = (0, task_1.createTask)({
29
+ schema,
30
+ boundaries,
31
+ fn: async function ({ descriptorName }, { loadConf, persistConf, deleteFile }) {
32
+ // Load shadow configuration
33
+ const forge = await loadConf({});
34
+ // Check if the task exists in shadow.json
35
+ if (!forge.tasks[descriptorName]) {
36
+ throw new Error(`Task '${descriptorName}' not found in forge.json`);
37
+ }
38
+ // Get the task file path
39
+ const taskFilePath = path_1.default.join(process.cwd(), forge.tasks[descriptorName].path);
40
+ console.log(`
38
41
  ==================================================
39
42
  Removing task: ${descriptorName}
40
43
  File path: ${taskFilePath}
41
44
  ==================================================
42
45
  `);
43
- // Delete the task file
44
- await deleteFile(taskFilePath);
45
- // Remove the task from shadow.json
46
- delete forge.tasks[descriptorName];
47
- // Save the updated shadow.json
48
- await persistConf(forge);
49
- return {
50
- status: 'Ok',
51
- message: `Task '${descriptorName}' has been successfully removed`
52
- };
46
+ // Delete the task file
47
+ await deleteFile(taskFilePath);
48
+ // Remove the task from shadow.json
49
+ delete forge.tasks[descriptorName];
50
+ // Save the updated shadow.json
51
+ await persistConf(forge);
52
+ return {
53
+ status: 'Ok',
54
+ message: `Task '${descriptorName}' has been successfully removed`
55
+ };
56
+ }
53
57
  });
@@ -80,100 +80,104 @@ const boundaries = {
80
80
  }
81
81
  }
82
82
  };
83
- exports.replay = (0, task_1.createTask)(schema, boundaries, async function ({ descriptorName, path: fixturePath, cache }, { readFixture, loadConf, loadCurrentProfile, bundleCreate, bundleLoad, ensureBuildsFolder, verifyLogFolder, sendLogToAPI }) {
84
- console.log('Input descriptorName:', descriptorName);
85
- console.log('Input path:', fixturePath);
86
- console.log('Input cache:', cache);
87
- // Load forge configuration
88
- const forge = await loadConf({});
89
- const taskDescriptor = forge.tasks[descriptorName];
90
- const projectName = forge.project.name;
91
- if (taskDescriptor === undefined) {
92
- throw new Error(`Task ${descriptorName} is not defined in forge.json`);
93
- }
94
- // Resolve the fixture path (check if absolute, if not make it relative to logs folder)
95
- const resolvedFixturePath = path_1.default.isAbsolute(fixturePath)
96
- ? fixturePath
97
- : path_1.default.join(process.cwd(), forge.paths.fixtures, fixturePath);
98
- // Read the file from the provided path
99
- const fixture = await readFixture(resolvedFixturePath);
100
- // Try to load profile, but continue if not found
101
- let profile = null;
102
- try {
103
- profile = await loadCurrentProfile({});
104
- }
105
- catch (error) {
106
- // Profile not found or not configured, continue without it
107
- console.log('No profile found, logs will not be sent to remote API');
108
- }
109
- // Verify if log folder exists
110
- const logFolderPath = path_1.default.join(process.cwd(), forge.paths.logs);
111
- const logFolderExists = await verifyLogFolder(logFolderPath);
112
- if (!logFolderExists) {
113
- throw new Error(`Log folder "${logFolderPath}" does not exist`);
114
- }
115
- // Prepare paths
116
- const entryPoint = path_1.default.join(process.cwd(), taskDescriptor.path);
117
- const buildsPath = await ensureBuildsFolder();
118
- const outputFile = path_1.default.join(buildsPath, `${descriptorName}.js`);
119
- // Bundle the task
120
- await bundleCreate({
121
- entryPoint,
122
- outputFile
123
- });
124
- // Load the bundled task
125
- const bundle = await bundleLoad({
126
- bundlePath: outputFile
127
- });
128
- // Get the task handler
129
- const task = bundle[taskDescriptor.handler];
130
- if (!task) {
131
- throw new Error(`Handler "${taskDescriptor.handler}" not found in bundle`);
132
- }
133
- // Configure boundaries based on cache parameter if provided
134
- const boundaryConfig = {};
135
- if (cache) {
136
- // Parse the comma-separated list and trim each item
137
- const cacheBoundaries = cache.split(',').map((b) => b.trim());
138
- // Log which boundaries will use cache mode
139
- if (cacheBoundaries.length > 0) {
140
- // Set each specified boundary to 'replay' mode
141
- cacheBoundaries.forEach((boundary) => {
142
- boundaryConfig[boundary] = 'replay';
143
- });
83
+ exports.replay = (0, task_1.createTask)({
84
+ schema,
85
+ boundaries,
86
+ fn: async function ({ descriptorName, path: fixturePath, cache }, { readFixture, loadConf, loadCurrentProfile, bundleCreate, bundleLoad, ensureBuildsFolder, verifyLogFolder, sendLogToAPI }) {
87
+ console.log('Input descriptorName:', descriptorName);
88
+ console.log('Input path:', fixturePath);
89
+ console.log('Input cache:', cache);
90
+ // Load forge configuration
91
+ const forge = await loadConf({});
92
+ const taskDescriptor = forge.tasks[descriptorName];
93
+ const projectName = forge.project.name;
94
+ if (taskDescriptor === undefined) {
95
+ throw new Error(`Task ${descriptorName} is not defined in forge.json`);
144
96
  }
145
- }
146
- console.log('==================================================');
147
- console.log('UUID:', fixture.fixtureUUID);
148
- console.log('Task name:', fixture.taskName);
149
- console.log('Project name:', fixture.projectName);
150
- console.log('Context:', fixture.context);
151
- console.log('==================================================');
152
- console.log('Replay:', fixture.input);
153
- console.log('Boundaries:', JSON.stringify(fixture.boundaries, null, 2));
154
- console.log('==================================================');
155
- console.log('Boundary config:', boundaryConfig);
156
- console.log('==================================================');
157
- // Perform the replay
158
- const [result, error, record] = await task.safeReplay({
159
- input: fixture.input,
160
- output: fixture.output,
161
- boundaries: fixture.boundaries,
162
- }, {
163
- boundaries: boundaryConfig // Use configured boundary modes
164
- });
165
- // Send the log to API if profile is available
166
- if (profile) {
97
+ // Resolve the fixture path (check if absolute, if not make it relative to logs folder)
98
+ const resolvedFixturePath = path_1.default.isAbsolute(fixturePath)
99
+ ? fixturePath
100
+ : path_1.default.join(process.cwd(), forge.paths.fixtures, fixturePath);
101
+ // Read the file from the provided path
102
+ const fixture = await readFixture(resolvedFixturePath);
103
+ // Try to load profile, but continue if not found
104
+ let profile = null;
167
105
  try {
168
- await sendLogToAPI(profile, projectName, descriptorName, record, fixture.fixtureUUID);
106
+ profile = await loadCurrentProfile({});
169
107
  }
170
- catch (e) {
171
- console.error('Failed to send log to API:', e);
108
+ catch (error) {
109
+ // Profile not found or not configured, continue without it
110
+ console.log('No profile found, logs will not be sent to remote API');
172
111
  }
112
+ // Verify if log folder exists
113
+ const logFolderPath = path_1.default.join(process.cwd(), forge.paths.logs);
114
+ const logFolderExists = await verifyLogFolder(logFolderPath);
115
+ if (!logFolderExists) {
116
+ throw new Error(`Log folder "${logFolderPath}" does not exist`);
117
+ }
118
+ // Prepare paths
119
+ const entryPoint = path_1.default.join(process.cwd(), taskDescriptor.path);
120
+ const buildsPath = await ensureBuildsFolder();
121
+ const outputFile = path_1.default.join(buildsPath, `${descriptorName}.js`);
122
+ // Bundle the task
123
+ await bundleCreate({
124
+ entryPoint,
125
+ outputFile
126
+ });
127
+ // Load the bundled task
128
+ const bundle = await bundleLoad({
129
+ bundlePath: outputFile
130
+ });
131
+ // Get the task handler
132
+ const task = bundle[taskDescriptor.handler];
133
+ if (!task) {
134
+ throw new Error(`Handler "${taskDescriptor.handler}" not found in bundle`);
135
+ }
136
+ // Configure boundaries based on cache parameter if provided
137
+ const boundaryConfig = {};
138
+ if (cache) {
139
+ // Parse the comma-separated list and trim each item
140
+ const cacheBoundaries = cache.split(',').map((b) => b.trim());
141
+ // Log which boundaries will use cache mode
142
+ if (cacheBoundaries.length > 0) {
143
+ // Set each specified boundary to 'replay' mode
144
+ cacheBoundaries.forEach((boundary) => {
145
+ boundaryConfig[boundary] = 'replay';
146
+ });
147
+ }
148
+ }
149
+ console.log('==================================================');
150
+ console.log('UUID:', fixture.fixtureUUID);
151
+ console.log('Task name:', fixture.taskName);
152
+ console.log('Project name:', fixture.projectName);
153
+ console.log('Context:', fixture.context);
154
+ console.log('==================================================');
155
+ console.log('Replay:', fixture.input);
156
+ console.log('Boundaries:', JSON.stringify(fixture.boundaries, null, 2));
157
+ console.log('==================================================');
158
+ console.log('Boundary config:', boundaryConfig);
159
+ console.log('==================================================');
160
+ // Perform the replay
161
+ const [result, error, record] = await task.safeReplay({
162
+ input: fixture.input,
163
+ output: fixture.output,
164
+ boundaries: fixture.boundaries,
165
+ }, {
166
+ boundaries: boundaryConfig // Use configured boundary modes
167
+ });
168
+ // Send the log to API if profile is available
169
+ if (profile) {
170
+ try {
171
+ await sendLogToAPI(profile, projectName, descriptorName, record, fixture.fixtureUUID);
172
+ }
173
+ catch (e) {
174
+ console.error('Failed to send log to API:', e);
175
+ }
176
+ }
177
+ if (error) {
178
+ throw new Error(error.message);
179
+ }
180
+ return result;
173
181
  }
174
- if (error) {
175
- throw new Error(error.message);
176
- }
177
- return result;
178
182
  });
179
183
  exports.replay.setDescription(description);
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  // TASK: run
3
3
  // Run this task with:
4
- // shadow-cli task:run
4
+ // most recursive call on the project
5
+ // forge task:run task:run
5
6
  var __importDefault = (this && this.__importDefault) || function (mod) {
6
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
7
8
  };
@@ -75,87 +76,85 @@ const boundaries = {
75
76
  }
76
77
  }
77
78
  };
78
- exports.run = (0, task_1.createTask)(schema, boundaries, async function ({ descriptorName, args }, { loadConf, bundleCreate, bundleLoad, verifyLogFolder, ensureBuildsFolder, loadCurrentProfile, sendLogToAPI }) {
79
- // Load forge configuration
80
- const forge = await loadConf({});
81
- const taskDescriptor = forge.tasks[descriptorName];
82
- const projectName = forge.project.name;
83
- if (taskDescriptor === undefined) {
84
- throw new Error('Task is not defined on forge.json');
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
- }
95
- // Verify if log folder exists
96
- const logFolderPath = path_1.default.join(process.cwd(), forge.paths.logs);
97
- const logFolderExists = await verifyLogFolder(logFolderPath);
98
- if (!logFolderExists) {
99
- throw new Error(`Log folder "${logFolderPath}" does not exist`);
100
- }
101
- // Prepare paths
102
- const logsPath = path_1.default.join(logFolderPath, descriptorName);
103
- const entryPoint = path_1.default.join(process.cwd(), taskDescriptor.path);
104
- const buildsPath = await ensureBuildsFolder();
105
- const outputFile = path_1.default.join(buildsPath, `${descriptorName}.js`);
106
- // Bundle the task
107
- await bundleCreate({
108
- entryPoint,
109
- outputFile
110
- });
111
- // Load the bundled task
112
- const bundle = await bundleLoad({
113
- bundlePath: outputFile
114
- });
115
- // Get the task handler
116
- const task = bundle[taskDescriptor.handler];
117
- if (!task) {
118
- throw new Error(`Handler "${taskDescriptor.handler}" not found in bundle`);
119
- }
120
- // Setup record tape
121
- let tape = new record_tape_1.RecordTape({
122
- path: logsPath
123
- });
124
- // load record tape
125
- try {
126
- await tape.load();
127
- // Need to figure out how to handle the log length
128
- // and other options for the RecordTape
129
- // For now, we'll just keep the implementation simple
130
- const maxLogLength = 9;
131
- const log = tape.getLog();
132
- if (log.length > maxLogLength) {
133
- const newTape = new record_tape_1.RecordTape({
134
- path: logsPath,
135
- log: log.slice(-maxLogLength)
136
- });
137
- tape = newTape;
79
+ exports.run = (0, task_1.createTask)({
80
+ schema,
81
+ boundaries,
82
+ fn: async function ({ descriptorName, args }, { loadConf, bundleCreate, bundleLoad, verifyLogFolder, ensureBuildsFolder, loadCurrentProfile, sendLogToAPI }) {
83
+ // Load forge configuration
84
+ const forge = await loadConf({});
85
+ const taskDescriptor = forge.tasks[descriptorName];
86
+ const projectName = forge.project.name;
87
+ if (taskDescriptor === undefined) {
88
+ throw new Error('Task is not defined on forge.json');
138
89
  }
139
- }
140
- catch (_error) {
141
- // if the tape is not found, create a new one on saving
142
- }
143
- // Run the task with provided arguments
144
- const [result, error, record] = await task.safeRun(args);
145
- const logItem = tape.push(descriptorName, record, {
146
- environment: 'cli'
147
- });
148
- await tape.save();
149
- if (profile) {
90
+ // Try to load profile, but continue if not found
91
+ let profile = null;
150
92
  try {
151
- await sendLogToAPI(profile, projectName, descriptorName, logItem);
93
+ profile = await loadCurrentProfile({});
152
94
  }
153
- catch (e) {
154
- console.error('Failed to send log to API:', e);
95
+ catch (error) {
96
+ // Profile not found or not configured, continue without it
97
+ console.log('No profile found, logs will not be sent to remote API');
155
98
  }
99
+ // Verify if log folder exists
100
+ const logFolderPath = path_1.default.join(process.cwd(), forge.paths.logs);
101
+ const logFolderExists = await verifyLogFolder(logFolderPath);
102
+ if (!logFolderExists) {
103
+ throw new Error(`Log folder "${logFolderPath}" does not exist`);
104
+ }
105
+ // Prepare paths
106
+ const logsPath = path_1.default.join(logFolderPath, descriptorName);
107
+ const entryPoint = path_1.default.join(process.cwd(), taskDescriptor.path);
108
+ const buildsPath = await ensureBuildsFolder();
109
+ const outputFile = path_1.default.join(buildsPath, `${descriptorName}.js`);
110
+ // Bundle the task
111
+ await bundleCreate({
112
+ entryPoint,
113
+ outputFile
114
+ });
115
+ // Load the bundled task
116
+ const bundle = await bundleLoad({
117
+ bundlePath: outputFile
118
+ });
119
+ // Get the task handler
120
+ const task = bundle[taskDescriptor.handler];
121
+ if (!task) {
122
+ throw new Error(`Handler "${taskDescriptor.handler}" not found in bundle`);
123
+ }
124
+ // Setup record tape
125
+ const tape = new record_tape_1.RecordTape({
126
+ path: logsPath
127
+ });
128
+ // load record tape
129
+ try {
130
+ await tape.load();
131
+ // Maintain a maximum log length by removing old records
132
+ const maxLogLength = 10;
133
+ // Remove records from the beginning until we're within the limit
134
+ while (tape.getLength() >= maxLogLength) {
135
+ tape.shift();
136
+ }
137
+ }
138
+ catch (_error) {
139
+ // if the tape is not found, create a new one on saving
140
+ }
141
+ // Run the task with provided arguments
142
+ const [result, error, record] = await task.safeRun(args);
143
+ const logItem = tape.push(record, {
144
+ environment: 'cli'
145
+ });
146
+ await tape.save();
147
+ if (profile) {
148
+ try {
149
+ await sendLogToAPI(profile, projectName, descriptorName, logItem);
150
+ }
151
+ catch (e) {
152
+ console.error('Failed to send log to API:', e);
153
+ }
154
+ }
155
+ if (error) {
156
+ throw error;
157
+ }
158
+ return result;
156
159
  }
157
- if (error) {
158
- throw error;
159
- }
160
- return result;
161
160
  });
@@ -15,6 +15,7 @@ const expectedContent = `// TASK: newTask
15
15
  import { createTask } from '@forgehive/task'
16
16
  import { Schema } from '@forgehive/schema'
17
17
 
18
+ const name = 'sample:newTask'
18
19
  const description = 'Add task description here'
19
20
 
20
21
  const schema = new Schema({
@@ -27,10 +28,12 @@ const boundaries = {
27
28
  // example: readFile: async (path: string) => fs.readFile(path, 'utf-8')
28
29
  }
29
30
 
30
- export const newTask = createTask(
31
+ export const newTask = createTask({
32
+ name,
33
+ description,
31
34
  schema,
32
35
  boundaries,
33
- async function (argv, boundaries) {
36
+ fn: async function (argv, boundaries) {
34
37
  console.log('input:', argv)
35
38
  console.log('boundaries:', boundaries)
36
39
  // Your task implementation goes here
@@ -38,13 +41,11 @@ export const newTask = createTask(
38
41
 
39
42
  return status
40
43
  }
41
- )
44
+ })
42
45
 
43
- newTask.setDescription(description)
44
46
  `;
45
47
  describe('Create task', () => {
46
48
  let volume;
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
49
  let fs;
49
50
  let rootDir;
50
51
  beforeEach(() => {
@@ -0,0 +1,21 @@
1
+ interface SchemaProperty {
2
+ type: string;
3
+ optional?: boolean;
4
+ default?: string;
5
+ }
6
+ interface InputSchema {
7
+ type: string;
8
+ properties: Record<string, SchemaProperty>;
9
+ }
10
+ interface OutputType {
11
+ type: string;
12
+ properties?: Record<string, SchemaProperty>;
13
+ }
14
+ export interface TaskFingerprintOutput {
15
+ description?: string;
16
+ inputSchema: InputSchema;
17
+ outputType: OutputType;
18
+ boundaries: string[];
19
+ }
20
+ export declare function analyzeTaskFile(sourceCode: string, filePath: string, _expectedTaskName?: string): TaskFingerprintOutput | null;
21
+ export {};