@forgehive/forge-cli 0.3.9 → 0.3.11

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.
@@ -0,0 +1,106 @@
1
+ // TASK: link
2
+ // Run this task with:
3
+ // forge project:link [uuid]
4
+
5
+ import { createTask } from '@forgehive/task'
6
+ import { Schema } from '@forgehive/schema'
7
+ import fs from 'fs/promises'
8
+ import path from 'path'
9
+
10
+ import { load as loadConf } from '../conf/load'
11
+ import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
12
+ import { type ForgeConf, type Profile } from '../types'
13
+
14
+ const name = 'project:link'
15
+ const description = 'Link an existing remote project to the local project by UUID'
16
+
17
+ const schema = new Schema({
18
+ uuid: Schema.string()
19
+ })
20
+
21
+ const boundaries = {
22
+ loadConf: loadConf.asBoundary(),
23
+ loadCurrentProfile: loadCurrentProfile.asBoundary(),
24
+ writeFile: async (filePath: string, content: string): Promise<void> => {
25
+ await fs.writeFile(filePath, content, 'utf-8')
26
+ },
27
+ fetchProject: async (profile: Profile, uuid: string): Promise<Response> => {
28
+ const authToken = `${profile.apiKey}:${profile.apiSecret}`
29
+ console.log(`Fetching project ${uuid} from ${profile.url}...`)
30
+ return await fetch(`${profile.url}/api/projects/${uuid}`, {
31
+ method: 'GET',
32
+ headers: {
33
+ 'Authorization': `Bearer ${authToken}`
34
+ }
35
+ })
36
+ }
37
+ }
38
+
39
+ export const link = createTask({
40
+ name,
41
+ description,
42
+ schema,
43
+ boundaries,
44
+ fn: async function ({ uuid }, { loadConf, loadCurrentProfile, writeFile, fetchProject }) {
45
+ // Load current configuration and profile
46
+ const conf = await loadConf({})
47
+
48
+ // Check if project already has a UUID
49
+ if (conf.project.uuid) {
50
+ throw new Error(`Project is already linked to UUID: ${conf.project.uuid}. Use a different project or remove the existing UUID from forge.json first.`)
51
+ }
52
+
53
+ const profile = await loadCurrentProfile({})
54
+
55
+ console.log(`Checking if project ${uuid} exists on ${profile.url}...`)
56
+
57
+ // Check if project exists on remote
58
+ const response = await fetchProject(profile, uuid)
59
+
60
+ if (!response.ok) {
61
+ if (response.status === 404) {
62
+ throw new Error(`Project with UUID ${uuid} not found on ${profile.url}. Please verify the UUID is correct.`)
63
+ } else if (response.status === 401) {
64
+ throw new Error('Authentication failed. Please check your profile credentials with \'forge auth:list\'.')
65
+ } else {
66
+ const errorText = await response.text()
67
+ throw new Error(`Failed to verify project: ${response.status} ${response.statusText} - ${errorText}`)
68
+ }
69
+ }
70
+
71
+ const projectData = await response.json()
72
+ const project = projectData.project
73
+
74
+ console.log(`✓ Found project: ${project.projectName}`)
75
+ console.log(` Description: ${project.description || 'No description'}`)
76
+ console.log(` Tasks: ${project.tasks.length} task(s)`)
77
+
78
+ // Update forge.json with the UUID
79
+ const forgePath = path.join(process.cwd(), 'forge.json')
80
+ const updatedConf: ForgeConf = {
81
+ ...conf,
82
+ project: {
83
+ ...conf.project,
84
+ uuid: uuid
85
+ }
86
+ }
87
+
88
+ await writeFile(forgePath, JSON.stringify(updatedConf, null, 2))
89
+
90
+ console.log(`\n✓ Successfully linked project ${uuid} to local forge.json`)
91
+ console.log(` Local project name: ${conf.project.name}`)
92
+ console.log(` Remote project name: ${project.projectName}`)
93
+ console.log(`\n🌐 View your project on the dashboard: ${profile.url}/dashboard/projects/${uuid}`)
94
+
95
+ return {
96
+ success: true,
97
+ linkedProject: {
98
+ uuid: project.uuid,
99
+ name: project.projectName,
100
+ description: project.description,
101
+ tasksCount: project.tasks.length
102
+ }
103
+ }
104
+ }
105
+ })
106
+
@@ -0,0 +1,74 @@
1
+ // TASK: unlink
2
+ // Run this task with:
3
+ // forge project:unlink
4
+
5
+ import { createTask } from '@forgehive/task'
6
+ import { Schema } from '@forgehive/schema'
7
+ import fs from 'fs/promises'
8
+ import path from 'path'
9
+
10
+ import { load as loadConf } from '../conf/load'
11
+ import { type ForgeConf } from '../types'
12
+
13
+ const name = 'project:unlink'
14
+ const description = 'Remove the project UUID link from local forge.json'
15
+
16
+ const schema = new Schema({
17
+ // No parameters needed for unlink
18
+ })
19
+
20
+ const boundaries = {
21
+ loadConf: loadConf.asBoundary(),
22
+ writeFile: async (filePath: string, content: string): Promise<void> => {
23
+ await fs.writeFile(filePath, content, 'utf-8')
24
+ }
25
+ }
26
+
27
+ export const unlink = createTask({
28
+ name,
29
+ description,
30
+ schema,
31
+ boundaries,
32
+ fn: async function (argv, { loadConf, writeFile }) {
33
+ // Load current configuration
34
+ const conf = await loadConf({})
35
+
36
+ // Check if project has a UUID to unlink
37
+ if (!conf.project.uuid) {
38
+ throw new Error('No project UUID found in forge.json. The project is not currently linked to a remote project.')
39
+ }
40
+
41
+ const currentUuid = conf.project.uuid
42
+
43
+ // Remove the UUID from the project configuration
44
+ const updatedConf: ForgeConf = {
45
+ ...conf,
46
+ project: {
47
+ ...conf.project,
48
+ uuid: undefined
49
+ }
50
+ }
51
+
52
+ // Clean up undefined values by creating a new object without the uuid field
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
+ const { uuid, ...projectWithoutUuid } = updatedConf.project
55
+ const finalConf: ForgeConf = {
56
+ ...updatedConf,
57
+ project: projectWithoutUuid
58
+ }
59
+
60
+ // Write the updated configuration back to forge.json
61
+ const forgePath = path.join(process.cwd(), 'forge.json')
62
+ await writeFile(forgePath, JSON.stringify(finalConf, null, 2))
63
+
64
+ console.log(`✓ Successfully unlinked project from UUID: ${currentUuid}`)
65
+ console.log(' The project is no longer linked to a remote project.')
66
+ console.log(' You can now link to a different project using \'forge project:link [uuid]\'')
67
+
68
+ return {
69
+ success: true,
70
+ unlinkedUuid: currentUuid
71
+ }
72
+ }
73
+ })
74
+
@@ -1,5 +1,6 @@
1
1
  import { createTask } from '@forgehive/task'
2
2
  import { Schema } from '@forgehive/schema'
3
+ import { v4 as uuidv4 } from 'uuid'
3
4
 
4
5
  import Handlebars from 'handlebars'
5
6
  import path from 'path'
@@ -38,11 +39,10 @@ export const {{ taskName }} = createTask({
38
39
  boundaries,
39
40
  fn: async function (argv, boundaries) {
40
41
  console.log('input:', argv)
41
- console.log('boundaries:', boundaries)
42
+ console.log('boundaries:', Object.keys(boundaries))
42
43
  // Your task implementation goes here
43
- const status = { status: 'Ok' }
44
44
 
45
- return status
45
+ return {}
46
46
  }
47
47
  })
48
48
 
@@ -155,7 +155,8 @@ export const createTaskCommand = createTask({
155
155
 
156
156
  forge.tasks[descriptor] = {
157
157
  path: `${taskPath}/${fileName}`,
158
- handler: taskName
158
+ handler: taskName,
159
+ uuid: uuidv4()
159
160
  }
160
161
 
161
162
  await persistConf(forge, cwd)
@@ -31,15 +31,13 @@ const boundaries = {
31
31
  loadCurrentProfile: loadCurrentProfile.asBoundary(),
32
32
  bundleCreate: bundleCreate.asBoundary(),
33
33
  bundleLoad: bundleLoad.asBoundary(),
34
- verifyLogFolder: async (logsPath: string): Promise<boolean> => {
35
- // return true if the folder exists
34
+ ensureLogFolder: async (logsPath: string): Promise<void> => {
35
+ // create the folder if it doesn't exist
36
36
  try {
37
37
  await fs.access(logsPath)
38
38
  } catch (error) {
39
- return false
39
+ await fs.mkdir(logsPath, { recursive: true })
40
40
  }
41
-
42
- return true
43
41
  },
44
42
  ensureBuildsFolder: async (): Promise<string> => {
45
43
  const buildsPath = path.join(os.homedir(), '.forge', 'builds')
@@ -90,7 +88,7 @@ export const run = createTask({
90
88
  loadConf,
91
89
  bundleCreate,
92
90
  bundleLoad,
93
- verifyLogFolder,
91
+ ensureLogFolder,
94
92
  ensureBuildsFolder,
95
93
  loadCurrentProfile,
96
94
  sendLogToAPI
@@ -111,14 +109,12 @@ export const run = createTask({
111
109
  } catch (error) {
112
110
  // Profile not found or not configured, continue without it
113
111
  console.log('No profile found, logs will not be sent to remote API')
112
+ console.log('===============================================')
114
113
  }
115
114
 
116
- // Verify if log folder exists
115
+ // Ensure log folder exists
117
116
  const logFolderPath = path.join(process.cwd(), forge.paths.logs)
118
- const logFolderExists = await verifyLogFolder(logFolderPath)
119
- if (!logFolderExists) {
120
- throw new Error(`Log folder "${logFolderPath}" does not exist`)
121
- }
117
+ await ensureLogFolder(logFolderPath)
122
118
 
123
119
  // Prepare paths
124
120
  const logsPath = path.join(logFolderPath, descriptorName)
@@ -10,6 +10,7 @@ export interface RunnerDescriptor {
10
10
  export interface TaskDescriptor {
11
11
  path: string
12
12
  handler: string
13
+ uuid?: string
13
14
  }
14
15
 
15
16
  export interface Infra {
@@ -32,11 +32,10 @@ export const newTask = createTask({
32
32
  boundaries,
33
33
  fn: async function (argv, boundaries) {
34
34
  console.log('input:', argv)
35
- console.log('boundaries:', boundaries)
35
+ console.log('boundaries:', Object.keys(boundaries))
36
36
  // Your task implementation goes here
37
- const status = { status: 'Ok' }
38
37
 
39
- return status
38
+ return {}
40
39
  }
41
40
  })
42
41
 
@@ -103,5 +102,7 @@ describe('Create task', () => {
103
102
  const forgeContent = await fs.promises.readFile(path.join(rootDir, 'forge.json'), 'utf-8') as string
104
103
  const forgeConf = JSON.parse(forgeContent)
105
104
  expect(forgeConf.tasks['sample:newTask']).toBeDefined()
105
+ expect(forgeConf.tasks['sample:newTask'].uuid).toBeDefined()
106
+ expect(typeof forgeConf.tasks['sample:newTask'].uuid).toBe('string')
106
107
  })
107
108
  })