@forgehive/forge-cli 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/runner.js +57 -2
  2. package/dist/tasks/auth/add.d.ts +16 -0
  3. package/dist/tasks/auth/add.js +50 -0
  4. package/dist/tasks/auth/list.d.ts +14 -0
  5. package/dist/tasks/auth/list.js +31 -0
  6. package/dist/tasks/auth/load.d.ts +6 -0
  7. package/dist/tasks/auth/load.js +44 -0
  8. package/dist/tasks/auth/loadCurrent.d.ts +6 -0
  9. package/dist/tasks/auth/loadCurrent.js +24 -0
  10. package/dist/tasks/auth/remove.d.ts +13 -0
  11. package/dist/tasks/auth/remove.js +56 -0
  12. package/dist/tasks/auth/switch.d.ts +12 -0
  13. package/dist/tasks/auth/switch.js +43 -0
  14. package/dist/tasks/conf/info.d.ts +7 -0
  15. package/dist/tasks/conf/info.js +13 -8
  16. package/dist/tasks/runner/bundle.d.ts +18 -0
  17. package/dist/tasks/runner/bundle.js +71 -0
  18. package/dist/tasks/runner/create.d.ts +21 -0
  19. package/dist/tasks/runner/create.js +97 -0
  20. package/dist/tasks/runner/remove.d.ts +18 -0
  21. package/dist/tasks/runner/remove.js +58 -0
  22. package/dist/tasks/task/createTask.js +4 -0
  23. package/dist/tasks/task/download.d.ts +58 -0
  24. package/dist/tasks/task/download.js +153 -0
  25. package/dist/tasks/task/publish.d.ts +41 -0
  26. package/dist/tasks/task/publish.js +106 -0
  27. package/dist/tasks/types.d.ts +10 -0
  28. package/dist/test/tasks/create.test.js +4 -0
  29. package/forge.json +44 -0
  30. package/logs/auth:list.log +4 -0
  31. package/logs/auth:load.log +2 -0
  32. package/logs/auth:loadCurrent.log +1 -0
  33. package/logs/conf:info.log +1 -2
  34. package/logs/runner:create.log +3 -0
  35. package/package.json +3 -2
  36. package/src/runner.ts +60 -2
  37. package/src/tasks/auth/add.ts +57 -0
  38. package/src/tasks/auth/list.ts +43 -0
  39. package/src/tasks/auth/load.ts +51 -0
  40. package/src/tasks/auth/loadCurrent.ts +35 -0
  41. package/src/tasks/auth/remove.ts +66 -0
  42. package/src/tasks/auth/switch.ts +53 -0
  43. package/src/tasks/conf/info.ts +16 -8
  44. package/src/tasks/runner/bundle.ts +79 -0
  45. package/src/tasks/runner/create.ts +121 -0
  46. package/src/tasks/runner/remove.ts +74 -0
  47. package/src/tasks/task/createTask.ts +4 -0
  48. package/src/tasks/task/download.ts +192 -0
  49. package/src/tasks/task/publish.ts +135 -0
  50. package/src/tasks/types.ts +12 -0
  51. package/src/test/tasks/create.test.ts +4 -0
@@ -0,0 +1,121 @@
1
+ // TASK: create
2
+ // Run this task with:
3
+ // forge task:run runner:create --runnerName <runner-name>
4
+ // forge task:run runner:create --runnerName inventory
5
+
6
+ import { createTask } from '@forgehive/task'
7
+ import { Schema } from '@forgehive/schema'
8
+
9
+ import Handlebars from 'handlebars'
10
+ import path from 'path'
11
+ import fs from 'fs/promises'
12
+ import { camelCase } from '../../utils/camelCase'
13
+
14
+ import { load } from '../conf/load'
15
+ import { type RunnerDescriptor, type ForgeConf } from '../types'
16
+
17
+ // Define the template content directly in the code
18
+ const RUNNER_TEMPLATE = `// RUNNER: {{ runnerName }}
19
+ import { Runner } from '@forgehive/runner'
20
+
21
+ // Import your tasks here
22
+ // import { EXAMPLE_TASK } from '../../tasks/MODULE/TASK'
23
+
24
+ const {{ runnerName }}Runner = new Runner()
25
+
26
+ // Load your tasks here
27
+ // runner.load('MODULE:TASK', EXAMPLE_TASK)
28
+
29
+ export { {{ runnerName }}Runner }
30
+ `
31
+
32
+ const schema = new Schema({
33
+ runnerName: Schema.string()
34
+ })
35
+
36
+ const boundaries = {
37
+ // Load boundaries
38
+ loadConf: load.asBoundary(),
39
+ getCwd: async (): Promise<string> => {
40
+ return process.cwd()
41
+ },
42
+
43
+ // Persist boundaries
44
+ persistRunner: async (runnerPath: string, runnerName: string, content: string): Promise<{ path: string }> => {
45
+ const folderPath = path.join(runnerPath, runnerName)
46
+ const filePath = path.join(folderPath, 'index.ts')
47
+
48
+ let err
49
+ try {
50
+ await fs.stat(folderPath)
51
+ } catch (e) {
52
+ err = e
53
+ }
54
+
55
+ if (err === undefined) {
56
+ throw new Error(`Runner folder '${folderPath}' already exists.`)
57
+ }
58
+
59
+ await fs.mkdir(folderPath, { recursive: true })
60
+ await fs.writeFile(filePath, content, 'utf-8')
61
+
62
+ return {
63
+ path: filePath.toString()
64
+ }
65
+ },
66
+ persistConf: async (forge: ForgeConf, cwd: string): Promise<void> => {
67
+ const forgePath = path.join(cwd, 'forge.json')
68
+ await fs.writeFile(forgePath, JSON.stringify(forge, null, 2))
69
+ }
70
+ }
71
+
72
+ export const create = createTask(
73
+ schema,
74
+ boundaries,
75
+ async function ({ runnerName }, {
76
+ persistRunner,
77
+ loadConf,
78
+ persistConf,
79
+ getCwd
80
+ }) {
81
+ const formattedName = camelCase(runnerName)
82
+ const cwd = await getCwd()
83
+
84
+ const forge = await loadConf({})
85
+ const runnerPath = forge.paths.runners
86
+
87
+ console.log(`
88
+ ==================================================
89
+ Starting runner creation!
90
+ Creating runner: ${formattedName}
91
+ Into: ${runnerPath}${formattedName}/index.ts
92
+ ==================================================
93
+ `)
94
+
95
+ const comp = Handlebars.compile(RUNNER_TEMPLATE)
96
+ const content = comp({
97
+ runnerName: formattedName
98
+ })
99
+
100
+ await persistRunner(runnerPath, formattedName, content)
101
+
102
+ if (forge.runners === undefined) {
103
+ forge.runners = {}
104
+ }
105
+
106
+ // Create runner descriptor
107
+ const runnerDescriptor: RunnerDescriptor = {
108
+ path: `${runnerPath}${formattedName}/index.ts`,
109
+ version: '0.0.1'
110
+ }
111
+
112
+ forge.runners[formattedName] = runnerDescriptor
113
+
114
+ await persistConf(forge, cwd)
115
+
116
+ return {
117
+ runnerPath: `${runnerPath}/${formattedName}`,
118
+ runnerName: formattedName
119
+ }
120
+ }
121
+ )
@@ -0,0 +1,74 @@
1
+ // TASK: remove
2
+ // Run this task with:
3
+ // forge task:run runner:remove --runnerName <runner-name>
4
+
5
+ import { createTask } from '@forgehive/task'
6
+ import { Schema } from '@forgehive/schema'
7
+ import path from 'path'
8
+ import fs from 'fs/promises'
9
+ import { camelCase } from '../../utils/camelCase'
10
+ import { load } from '../conf/load'
11
+ import { type ForgeConf } from '../types'
12
+
13
+ const schema = new Schema({
14
+ runnerName: Schema.string()
15
+ })
16
+
17
+ const boundaries = {
18
+ // Load boundaries
19
+ loadConf: load.asBoundary(),
20
+ getCwd: async (): Promise<string> => {
21
+ return process.cwd()
22
+ },
23
+
24
+ // File system operations
25
+ removeRunner: async (runnerPath: string): Promise<void> => {
26
+ await fs.rm(runnerPath, { recursive: true, force: true })
27
+ },
28
+ // Configuration operations
29
+ persistConf: async (forge: ForgeConf, cwd: string): Promise<void> => {
30
+ const forgePath = path.join(cwd, 'forge.json')
31
+ await fs.writeFile(forgePath, JSON.stringify(forge, null, 2))
32
+ }
33
+ }
34
+
35
+ export const remove = createTask(
36
+ schema,
37
+ boundaries,
38
+ async function ({ runnerName }, {
39
+ loadConf,
40
+ getCwd,
41
+ removeRunner,
42
+ persistConf
43
+ }) {
44
+ const formattedName = camelCase(runnerName)
45
+ const cwd = await getCwd()
46
+ const forge = await loadConf({})
47
+
48
+ if (!forge.runners || !forge.runners[formattedName]) {
49
+ throw new Error(`Runner '${formattedName}' not found in forge.json configuration`)
50
+ }
51
+
52
+ const runnerConfig = forge.runners[formattedName]
53
+ const runnerFolder = path.join(cwd, path.dirname(runnerConfig.path))
54
+
55
+ console.log(`
56
+ ==================================================
57
+ Removing runner: ${formattedName}
58
+ Path: ${runnerFolder}
59
+ ==================================================
60
+ `)
61
+
62
+ await removeRunner(runnerFolder)
63
+
64
+ delete forge.runners[formattedName]
65
+
66
+ await persistConf(forge, cwd)
67
+
68
+ return {
69
+ status: 'success',
70
+ message: `Runner '${formattedName}' successfully removed`,
71
+ runnerName: formattedName
72
+ }
73
+ }
74
+ )
@@ -18,6 +18,8 @@ const TASK_TEMPLATE = `// TASK: {{ taskName }}
18
18
  import { createTask } from '@forgehive/task'
19
19
  import { Schema } from '@forgehive/schema'
20
20
 
21
+ const description = 'Add task description here'
22
+
21
23
  const schema = new Schema({
22
24
  // Add your schema definitions here
23
25
  // example: myParam: Schema.string()
@@ -40,6 +42,8 @@ export const {{ taskName }} = createTask(
40
42
  return status
41
43
  }
42
44
  )
45
+
46
+ {{ taskName }}.setDescription(description)
43
47
  `
44
48
 
45
49
  const schema = new Schema({
@@ -0,0 +1,192 @@
1
+ // TASK: download
2
+ // Run this task with:
3
+ // forge task:run task:download --descriptorName [name] --uuid [task-uuid]
4
+
5
+ import { createTask } from '@forgehive/task'
6
+ import { Schema } from '@forgehive/schema'
7
+ import axios from 'axios'
8
+ import path from 'path'
9
+ import fs from 'fs/promises'
10
+ import { camelCase } from '../../utils/camelCase'
11
+ import { load as loadConf } from '../conf/load'
12
+ import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
13
+ import { Profile, type ForgeConf } from '../types'
14
+
15
+ const schema = new Schema({
16
+ descriptorName: Schema.string(),
17
+ uuid: Schema.string()
18
+ })
19
+
20
+ const boundaries = {
21
+ loadCurrentProfile: loadCurrentProfile.asBoundary(),
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ downloadTask: async (uuid: string, profile: Profile): Promise<any> => {
24
+ const downloadUrl = `${profile.url}/api/tasks/download`
25
+
26
+ console.log(`Downloading task from ${downloadUrl}...`)
27
+
28
+ const authToken = `${profile.apiKey}:${profile.apiSecret}`
29
+ const response = await axios.post(downloadUrl, { uuid }, {
30
+ headers: {
31
+ Authorization: `Bearer ${authToken}`,
32
+ 'Content-Type': 'application/json'
33
+ }
34
+ })
35
+
36
+ return response.data
37
+ },
38
+ loadConf: loadConf.asBoundary(),
39
+ getCwd: async (): Promise<string> => {
40
+ return process.cwd()
41
+ },
42
+ parseTaskName: async (taskDescriptor: string): Promise<{
43
+ descriptor: string
44
+ taskName: string
45
+ fileName: string
46
+ dir?: string
47
+ }> => {
48
+ const res: string[] = taskDescriptor.split(':')
49
+
50
+ if (res.length === 1) {
51
+ return {
52
+ descriptor: `${camelCase(res[0])}`,
53
+ taskName: `${camelCase(res[0])}`,
54
+ fileName: `${camelCase(res[0])}.ts`
55
+ }
56
+ }
57
+
58
+ return {
59
+ dir: res[0],
60
+ descriptor: `${res[0]}:${camelCase(res[1])}`,
61
+ taskName: `${camelCase(res[1])}`,
62
+ fileName: `${camelCase(res[1])}.ts`
63
+ }
64
+ },
65
+ persistTask: async (dir: string, fileName: string, content: string, cwd: string): Promise<{ path: string }> => {
66
+ const dirPath = path.resolve(cwd, dir)
67
+ const taskPath = path.resolve(dirPath, fileName)
68
+
69
+ await fs.mkdir(dirPath, { recursive: true })
70
+ await fs.writeFile(taskPath, content, 'utf-8')
71
+
72
+
73
+ return {
74
+ path: taskPath.toString()
75
+ }
76
+ },
77
+ persistConf: async (forge: ForgeConf, cwd: string): Promise<void> => {
78
+ const forgePath = path.join(cwd, 'forge.json')
79
+ await fs.writeFile(forgePath, JSON.stringify(forge, null, 2))
80
+ },
81
+ checkTaskExists: async (dir: string, fileName: string): Promise<boolean> => {
82
+ const taskPath = path.resolve(dir, fileName)
83
+
84
+ try {
85
+ await fs.access(taskPath)
86
+ return true
87
+ } catch {
88
+ return false
89
+ }
90
+ }
91
+ }
92
+
93
+ export const download = createTask(
94
+ schema,
95
+ boundaries,
96
+ async function ({ descriptorName, uuid }, {
97
+ downloadTask,
98
+ getCwd,
99
+ parseTaskName,
100
+ persistTask,
101
+ loadConf,
102
+ persistConf,
103
+ checkTaskExists,
104
+ loadCurrentProfile
105
+ }) {
106
+ console.log(`Attempting to download task with descriptor: ${descriptorName} and uuid: ${uuid}`)
107
+
108
+ // Parse descriptor name to get task details
109
+ const { taskName, fileName, descriptor, dir } = await parseTaskName(descriptorName)
110
+ const profile = await loadCurrentProfile({})
111
+ const cwd = await getCwd()
112
+ const forge = await loadConf({})
113
+
114
+ let taskPath: string = forge.paths.tasks
115
+
116
+ if (dir !== undefined) {
117
+ taskPath = path.join(taskPath, dir)
118
+ }
119
+
120
+ // Check if task already exists
121
+ const taskExists = await checkTaskExists(taskPath, fileName)
122
+ if (taskExists) {
123
+ console.log(`Task ${descriptor} already exists at ${taskPath}/${fileName}`)
124
+ return {
125
+ error: 'Task already exists',
126
+ taskPath: `${taskPath}/${fileName}`,
127
+ descriptor
128
+ }
129
+ }
130
+
131
+ // Download from hive api server
132
+ let response
133
+ try {
134
+ response = await downloadTask(uuid, profile)
135
+ } catch (e: unknown) {
136
+ const error = e as { status: number, message: string }
137
+ console.error('Error downloading task:', error.message, error.status)
138
+
139
+ if (error.status === 404) {
140
+ return {
141
+ error: 'Task not found',
142
+ taskPath: `${taskPath}/${fileName}`,
143
+ descriptor
144
+ }
145
+ }
146
+
147
+ return {
148
+ error: 'Failed to download task',
149
+ message: error.message,
150
+ taskPath: `${taskPath}/${fileName}`,
151
+ descriptor
152
+ }
153
+ }
154
+
155
+ console.log(`
156
+ ==================================================
157
+ Starting task download!
158
+ Creating: ${taskName}
159
+ Dir: ${dir ?? ''}
160
+ Into: ${taskPath}
161
+ ==================================================
162
+ `)
163
+
164
+ console.log('Writing task file:', taskPath, fileName)
165
+ console.log('Handler:', response.handler)
166
+ console.log('Source code:', response.sourceCode)
167
+
168
+ // Persist task with cwd
169
+ await persistTask(taskPath, fileName, response.sourceCode, cwd)
170
+
171
+ // Update forge.json with the new task
172
+ if (forge.tasks === undefined) {
173
+ forge.tasks = {}
174
+ }
175
+
176
+ forge.tasks[descriptor] = {
177
+ path: `${taskPath}/${fileName}`,
178
+ handler: response.handler
179
+ }
180
+
181
+ console.log('Forge:', forge)
182
+
183
+ await persistConf(forge, cwd)
184
+
185
+ return {
186
+ taskPath,
187
+ fileName,
188
+ descriptor,
189
+ handler: response.handler
190
+ }
191
+ }
192
+ )
@@ -0,0 +1,135 @@
1
+ // TASK: publish
2
+ // Run this task with:
3
+ // forge task:run task:publish --descriptorMame task-name
4
+
5
+ import { createTask } from '@forgehive/task'
6
+ import { Schema } from '@forgehive/schema'
7
+ import axios from 'axios'
8
+
9
+ import path from 'path'
10
+ import fs from 'fs/promises'
11
+ import os from 'os'
12
+
13
+ import { load as loadConf } from '../conf/load'
14
+ import { create as bundleCreate } from '../bundle/create'
15
+ import { load as bundleLoad } from '../bundle/load'
16
+ import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
17
+ import { Profile } from '../types'
18
+
19
+ const schema = new Schema({
20
+ descriptorName: Schema.string()
21
+ })
22
+
23
+ const boundaries = {
24
+ getCwd: async (): Promise<string> => {
25
+ return process.cwd()
26
+ },
27
+ loadConf: loadConf.asBoundary(),
28
+ loadCurrentProfile: loadCurrentProfile.asBoundary(),
29
+ bundleCreate: bundleCreate.asBoundary(),
30
+ bundleLoad: bundleLoad.asBoundary(),
31
+ readFileUtf8: async (filePath: string): Promise<string> => {
32
+ return fs.readFile(filePath, 'utf-8')
33
+ },
34
+ readFileBinary: async (filePath: string): Promise<Buffer> => {
35
+ return fs.readFile(filePath)
36
+ },
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ publishTask: async (data: any, profile: Profile): Promise<any> => {
39
+ const publishUrl = `${profile.url}/api/tasks/publish`
40
+
41
+ console.log(`Publishing task to ${publishUrl}...`)
42
+ const authToken = `${profile.apiKey}:${profile.apiSecret}`
43
+ const response = await axios.post(publishUrl, data, {
44
+ headers: {
45
+ Authorization: `Bearer ${authToken}`,
46
+ 'Content-Type': 'application/json'
47
+ }
48
+ })
49
+
50
+ return response.data
51
+ },
52
+
53
+ ensureBuildsFolder: async (): Promise<string> => {
54
+ const buildsPath = path.join(os.homedir(), '.forge', 'builds')
55
+ try {
56
+ await fs.access(buildsPath)
57
+ } catch {
58
+ await fs.mkdir(buildsPath, { recursive: true })
59
+ }
60
+
61
+ return buildsPath
62
+ }
63
+ }
64
+
65
+ export const publish = createTask(
66
+ schema,
67
+ boundaries,
68
+ async function ({ descriptorName }, {
69
+ getCwd,
70
+ ensureBuildsFolder,
71
+ loadConf,
72
+ bundleCreate,
73
+ bundleLoad,
74
+ readFileUtf8,
75
+ readFileBinary,
76
+ publishTask,
77
+ loadCurrentProfile
78
+ }) {
79
+ const cwd = await getCwd()
80
+ const forgeJson = await loadConf({})
81
+ const profile = await loadCurrentProfile({})
82
+
83
+ const taskDescriptor = forgeJson.tasks[descriptorName as keyof typeof forgeJson.tasks]
84
+ const projectName = forgeJson.project.name
85
+
86
+ if (taskDescriptor === undefined) {
87
+ throw new Error('Task is not defined on forge.json')
88
+ }
89
+
90
+ const entryPoint = path.join(cwd, taskDescriptor.path)
91
+ const buildsPath = await ensureBuildsFolder()
92
+ const outputFile = path.join(buildsPath, `${descriptorName}.js`)
93
+
94
+ console.log('entryPoint:', entryPoint)
95
+ console.log('buildsPath:', buildsPath)
96
+ console.log('outputFile:', outputFile)
97
+
98
+ // Bundle the task
99
+ await bundleCreate({
100
+ entryPoint,
101
+ outputFile
102
+ })
103
+
104
+ // Load the bundled task
105
+ const bundle = await bundleLoad({
106
+ bundlePath: outputFile
107
+ })
108
+
109
+ // Get the task handler
110
+ const task = bundle[taskDescriptor.handler]
111
+ const description = task.getDescription() ?? ''
112
+ const schema = task.getSchema() || new Schema({})
113
+ const schemaDescriptor = schema.describe()
114
+
115
+ // Read the task file content and bundle
116
+ const sourceCode = await readFileUtf8(entryPoint)
117
+ const bundleContent = await readFileBinary(outputFile)
118
+
119
+ const data = {
120
+ ...taskDescriptor,
121
+ taskName: descriptorName,
122
+ projectName,
123
+ description,
124
+ schemaDescriptor: JSON.stringify(schemaDescriptor),
125
+ sourceCode,
126
+ bundle: bundleContent.toString('base64')
127
+ }
128
+
129
+ // Publish to hive api server
130
+ const response = await publishTask(data, profile)
131
+
132
+ console.log('Publish response:', response)
133
+ return { descriptor: taskDescriptor, publishResponse: response }
134
+ }
135
+ )
@@ -39,3 +39,15 @@ export interface TaskName {
39
39
  fileName: string
40
40
  dir?: string
41
41
  }
42
+
43
+ export interface Profile {
44
+ name: string
45
+ apiKey: string
46
+ apiSecret: string
47
+ url: string
48
+ }
49
+
50
+ export interface Profiles {
51
+ default: string
52
+ profiles: Profile[]
53
+ }
@@ -12,6 +12,8 @@ const expectedContent = `// TASK: newTask
12
12
  import { createTask } from '@forgehive/task'
13
13
  import { Schema } from '@forgehive/schema'
14
14
 
15
+ const description = 'Add task description here'
16
+
15
17
  const schema = new Schema({
16
18
  // Add your schema definitions here
17
19
  // example: myParam: Schema.string()
@@ -34,6 +36,8 @@ export const newTask = createTask(
34
36
  return status
35
37
  }
36
38
  )
39
+
40
+ newTask.setDescription(description)
37
41
  `
38
42
 
39
43
  describe('Create task', () => {