@forgehive/forge-cli 0.1.8 → 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 (41) hide show
  1. package/dist/runner.js +36 -1
  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/task/createTask.js +4 -0
  17. package/dist/tasks/task/download.d.ts +58 -0
  18. package/dist/tasks/task/download.js +153 -0
  19. package/dist/tasks/task/publish.d.ts +41 -0
  20. package/dist/tasks/task/publish.js +106 -0
  21. package/dist/tasks/types.d.ts +10 -0
  22. package/dist/test/tasks/create.test.js +4 -0
  23. package/forge.json +32 -0
  24. package/logs/auth:list.log +4 -0
  25. package/logs/auth:load.log +2 -0
  26. package/logs/auth:loadCurrent.log +1 -0
  27. package/logs/conf:info.log +1 -0
  28. package/package.json +5 -4
  29. package/src/runner.ts +37 -1
  30. package/src/tasks/auth/add.ts +57 -0
  31. package/src/tasks/auth/list.ts +43 -0
  32. package/src/tasks/auth/load.ts +51 -0
  33. package/src/tasks/auth/loadCurrent.ts +35 -0
  34. package/src/tasks/auth/remove.ts +66 -0
  35. package/src/tasks/auth/switch.ts +53 -0
  36. package/src/tasks/conf/info.ts +16 -8
  37. package/src/tasks/task/createTask.ts +4 -0
  38. package/src/tasks/task/download.ts +192 -0
  39. package/src/tasks/task/publish.ts +135 -0
  40. package/src/tasks/types.ts +12 -0
  41. package/src/test/tasks/create.test.ts +4 -0
@@ -0,0 +1,35 @@
1
+ // TASK: loadCurrent
2
+ // Run this task with:
3
+ // forge task:run auth:loadCurrent
4
+
5
+ import { createTask } from '@forgehive/task'
6
+ import { Schema } from '@forgehive/schema'
7
+
8
+ import { load as loadProfiles } from './load'
9
+ import { type Profile } from '../types'
10
+
11
+ const schema = new Schema({})
12
+
13
+ const boundaries = {
14
+ loadProfiles: loadProfiles.asBoundary()
15
+ }
16
+
17
+ export const loadCurrent = createTask(
18
+ schema,
19
+ boundaries,
20
+ async function (_argv, { loadProfiles }): Promise<Profile> {
21
+ const profiles = await loadProfiles({})
22
+
23
+ if (!profiles.default || profiles.default === '') {
24
+ throw new Error('No default profile set. Please run forge task:run auth:add to create a profile.')
25
+ }
26
+
27
+ const defaultProfile = profiles.profiles.find(profile => profile.name === profiles.default)
28
+
29
+ if (!defaultProfile) {
30
+ throw new Error(`Default profile "${profiles.default}" not found in profiles.`)
31
+ }
32
+
33
+ return { ...defaultProfile, name: profiles.default }
34
+ }
35
+ )
@@ -0,0 +1,66 @@
1
+ // TASK: remove
2
+ // Run this task with:
3
+ // forge task:run auth:remove --profileName [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 os from 'os'
10
+
11
+ import { load as loadProfiles } from './load'
12
+ import { type Profiles } from '../types'
13
+
14
+ const schema = new Schema({
15
+ profileName: Schema.string()
16
+ })
17
+
18
+ const boundaries = {
19
+ loadProfiles: loadProfiles.asBoundary(),
20
+ persistProfiles: async (profiles: Profiles): Promise<void> => {
21
+ const buildsPath = path.join(os.homedir(), '.forge')
22
+ const profilesPath = path.join(buildsPath, 'profiles.json')
23
+ await fs.writeFile(profilesPath, JSON.stringify(profiles, null, 2))
24
+ }
25
+ }
26
+
27
+ export const remove = createTask(
28
+ schema,
29
+ boundaries,
30
+ async function ({ profileName }, { loadProfiles, persistProfiles }) {
31
+ const profiles = await loadProfiles({})
32
+
33
+ // Check if profile exists
34
+ const profileExists = profiles.profiles.some(profile => profile.name === profileName)
35
+
36
+ if (!profileExists) {
37
+ throw new Error(`Profile "${profileName}" not found. Use auth:list to see available profiles.`)
38
+ }
39
+
40
+ // Remove the profile using filter
41
+ profiles.profiles = profiles.profiles.filter(profile => profile.name !== profileName)
42
+
43
+ // If the removed profile was the default, update the default
44
+ if (profiles.default === profileName) {
45
+ if (profiles.profiles.length > 0) {
46
+ // Set the first available profile as default
47
+ profiles.default = profiles.profiles[0].name
48
+ console.log(`Default profile set to: ${profiles.default}`)
49
+ } else {
50
+ // No profiles left, set default to empty
51
+ profiles.default = ''
52
+ console.log('No profiles left. Default set to empty.')
53
+ }
54
+ }
55
+
56
+ // Persist updated profiles
57
+ await persistProfiles(profiles)
58
+
59
+ console.log(`Profile "${profileName}" has been removed.`)
60
+
61
+ return {
62
+ status: 'Ok',
63
+ message: `Profile "${profileName}" has been removed.`
64
+ }
65
+ }
66
+ )
@@ -0,0 +1,53 @@
1
+ // TASK: switch
2
+ // Run this task with:
3
+ // forge task:run auth:switch --profileName [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 os from 'os'
10
+
11
+ import { load as loadProfiles } from './load'
12
+ import { type Profiles } from '../types'
13
+
14
+ const schema = new Schema({
15
+ profileName: Schema.string()
16
+ })
17
+
18
+ const boundaries = {
19
+ loadProfiles: loadProfiles.asBoundary(),
20
+ persistProfiles: async (profiles: Profiles): Promise<void> => {
21
+ const buildsPath = path.join(os.homedir(), '.forge')
22
+ const profilesPath = path.join(buildsPath, 'profiles.json')
23
+ await fs.writeFile(profilesPath, JSON.stringify(profiles, null, 2))
24
+ }
25
+ }
26
+
27
+ export const switchProfile = createTask(
28
+ schema,
29
+ boundaries,
30
+ async function ({ profileName }, { loadProfiles, persistProfiles }) {
31
+ // Load profiles
32
+ const profiles = await loadProfiles({})
33
+
34
+ // Check if profile exists
35
+ const profileExists = profiles.profiles.some(profile => profile.name === profileName)
36
+
37
+ if (!profileExists) {
38
+ throw new Error(`Profile "${profileName}" not found. Use auth:list to see available profiles.`)
39
+ }
40
+
41
+ // Update default profile
42
+ profiles.default = profileName
43
+
44
+ // Save updated profiles
45
+ await persistProfiles(profiles)
46
+
47
+ console.log(`Switched to profile: ${profileName}`)
48
+
49
+ return {
50
+ default: profileName
51
+ }
52
+ }
53
+ )
@@ -7,26 +7,34 @@ import { Schema } from '@forgehive/schema'
7
7
  import * as fs from 'fs'
8
8
  import * as path from 'path'
9
9
 
10
- const schema = new Schema({
11
- // No parameters needed for this task
12
- })
10
+ import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
11
+
12
+ const schema = new Schema({})
13
13
 
14
14
  const boundaries = {
15
- readFile: async (filePath: string): Promise<string> => fs.promises.readFile(filePath, 'utf-8')
15
+ readFile: async (filePath: string): Promise<string> => fs.promises.readFile(filePath, 'utf-8'),
16
+ loadCurrentProfile: loadCurrentProfile.asBoundary()
16
17
  }
17
18
 
18
19
  export const info = createTask(
19
20
  schema,
20
21
  boundaries,
21
- async function (_argv, boundaries) {
22
+ async function (_argv, { loadCurrentProfile, readFile }) {
22
23
  const packageJsonPath = path.join(__dirname, '../../../package.json')
23
- console.log('packageJsonPath', packageJsonPath)
24
24
 
25
- const packageJsonContent = await boundaries.readFile(packageJsonPath)
25
+
26
+ const packageJsonContent = await readFile(packageJsonPath)
26
27
  const packageJson = JSON.parse(packageJsonContent)
27
28
 
29
+ const profile = await loadCurrentProfile({})
30
+
28
31
  return {
29
- version: packageJson.version
32
+ version: packageJson.version,
33
+ profile: {
34
+ name: profile.name,
35
+ url: profile.url,
36
+ apiKey: profile.apiKey
37
+ }
30
38
  }
31
39
  }
32
40
  )
@@ -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', () => {