@forgehive/forge-cli 0.3.10 → 0.3.12

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 (38) hide show
  1. package/dist/runner.js +32 -6
  2. package/dist/tasks/auth/add.d.ts +16 -0
  3. package/dist/tasks/auth/add.js +56 -4
  4. package/dist/tasks/auth/clear.d.ts +16 -0
  5. package/dist/tasks/auth/clear.js +54 -0
  6. package/dist/tasks/auth/list.d.ts +7 -1
  7. package/dist/tasks/auth/list.js +28 -8
  8. package/dist/tasks/auth/switch.js +24 -8
  9. package/dist/tasks/project/create.d.ts +27 -0
  10. package/dist/tasks/project/create.js +96 -0
  11. package/dist/tasks/project/link.d.ts +22 -0
  12. package/dist/tasks/project/link.js +95 -0
  13. package/dist/tasks/project/sync.d.ts +116 -0
  14. package/dist/tasks/project/sync.js +152 -0
  15. package/dist/tasks/project/unlink.d.ts +11 -0
  16. package/dist/tasks/project/unlink.js +65 -0
  17. package/dist/tasks/task/createTask.d.ts +12 -0
  18. package/dist/tasks/task/createTask.js +55 -5
  19. package/dist/tasks/task/run.d.ts +10 -2
  20. package/dist/tasks/task/run.js +14 -6
  21. package/dist/tasks/types.d.ts +4 -0
  22. package/dist/test/tasks/create.test.js +4 -3
  23. package/forge.json +14 -0
  24. package/package.json +6 -5
  25. package/src/runner.ts +32 -7
  26. package/src/tasks/auth/add.ts +66 -4
  27. package/src/tasks/auth/clear.ts +63 -0
  28. package/src/tasks/auth/list.ts +30 -8
  29. package/src/tasks/auth/switch.ts +24 -8
  30. package/src/tasks/project/README.md +268 -0
  31. package/src/tasks/project/create.ts +111 -0
  32. package/src/tasks/project/link.ts +106 -0
  33. package/src/tasks/project/sync.ts +202 -0
  34. package/src/tasks/project/unlink.ts +74 -0
  35. package/src/tasks/task/createTask.ts +72 -5
  36. package/src/tasks/task/run.ts +17 -6
  37. package/src/tasks/types.ts +4 -0
  38. package/src/test/tasks/create.test.ts +4 -3
package/dist/runner.js CHANGED
@@ -29,6 +29,11 @@ const add_1 = require("./tasks/auth/add");
29
29
  const switch_1 = require("./tasks/auth/switch");
30
30
  const list_2 = require("./tasks/auth/list");
31
31
  const remove_3 = require("./tasks/auth/remove");
32
+ const clear_1 = require("./tasks/auth/clear");
33
+ const create_2 = require("./tasks/project/create");
34
+ const link_1 = require("./tasks/project/link");
35
+ const unlink_1 = require("./tasks/project/unlink");
36
+ const sync_1 = require("./tasks/project/sync");
32
37
  const runner = new runner_1.Runner((data) => {
33
38
  const { _, ...filteredObj } = data;
34
39
  return {
@@ -64,6 +69,12 @@ runner.load('auth:add', add_1.add);
64
69
  runner.load('auth:switch', switch_1.switchProfile);
65
70
  runner.load('auth:list', list_2.list);
66
71
  runner.load('auth:remove', remove_3.remove);
72
+ runner.load('auth:clear', clear_1.clear);
73
+ // Project commands
74
+ runner.load('project:create', create_2.create);
75
+ runner.load('project:link', link_1.link);
76
+ runner.load('project:unlink', unlink_1.unlink);
77
+ runner.load('project:sync', sync_1.sync);
67
78
  // Set handler
68
79
  runner.setHandler(async (data) => {
69
80
  const parsedArgs = runner.parseArguments(data);
@@ -74,12 +85,14 @@ runner.setHandler(async (data) => {
74
85
  let silent = false;
75
86
  const task = runner.getTask(taskName);
76
87
  if (!task) {
77
- throw new Error(`Task "${taskName}" not found`);
88
+ throw new Error(`Forge command "${taskName}" not found`);
78
89
  }
79
90
  try {
80
91
  let result;
81
92
  const commandsWithDescriptor = ['task:create', 'task:remove', 'task:publish', 'task:describe', 'task:fingerprint'];
82
93
  const commandsWithRunner = ['runner:create', 'runner:remove'];
94
+ const commandsWithoutParams = ['project:unlink', 'project:sync', 'auth:clear'];
95
+ const silentCommands = ['task:describe', 'task:list', 'auth:list', 'info'];
83
96
  if (commandsWithDescriptor.includes(taskName)) {
84
97
  result = await task.run({ descriptorName: action });
85
98
  }
@@ -146,7 +159,7 @@ runner.setHandler(async (data) => {
146
159
  }
147
160
  else if (taskName === 'auth:switch' || taskName === 'auth:remove') {
148
161
  result = await task.run({
149
- profileName: action
162
+ profileName: String(action)
150
163
  });
151
164
  }
152
165
  else if (taskName === 'docs:download') {
@@ -155,13 +168,26 @@ runner.setHandler(async (data) => {
155
168
  path
156
169
  });
157
170
  }
171
+ else if (taskName === 'project:create') {
172
+ const { projectName, description } = args;
173
+ result = await task.run({
174
+ projectName,
175
+ description
176
+ });
177
+ }
178
+ else if (taskName === 'project:link') {
179
+ const { uuid } = args;
180
+ result = await task.run({
181
+ uuid
182
+ });
183
+ }
184
+ else if (commandsWithoutParams.includes(taskName)) {
185
+ result = await task.run({});
186
+ }
158
187
  else {
159
188
  result = await task.run(args);
160
- if (taskName === 'info') {
161
- silent = true;
162
- }
163
189
  }
164
- if (taskName === 'task:describe' || taskName === 'task:list') {
190
+ if (silentCommands.includes(taskName)) {
165
191
  silent = true;
166
192
  }
167
193
  return {
@@ -7,10 +7,26 @@ export declare const add: import("@forgehive/task").TaskInstanceType<(argv: {
7
7
  }, boundaries: import("@forgehive/task").WrappedBoundaries<{
8
8
  loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
9
9
  persistProfiles: (profiles: Profiles) => Promise<void>;
10
+ fetchMeInfo: (apiKey: string, apiSecret: string, url: string) => Promise<{
11
+ success: boolean;
12
+ teamName?: string;
13
+ teamUuid?: string;
14
+ userName?: string;
15
+ error?: string;
16
+ }>;
10
17
  }>) => Promise<{
11
18
  status: string;
12
19
  message: string;
20
+ teamName: string | undefined;
21
+ userName: string | undefined;
13
22
  }>, {
14
23
  loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
15
24
  persistProfiles: (profiles: Profiles) => Promise<void>;
25
+ fetchMeInfo: (apiKey: string, apiSecret: string, url: string) => Promise<{
26
+ success: boolean;
27
+ teamName?: string;
28
+ teamUuid?: string;
29
+ userName?: string;
30
+ error?: string;
31
+ }>;
16
32
  }>;
@@ -25,22 +25,72 @@ const boundaries = {
25
25
  const buildsPath = path_1.default.join(os_1.default.homedir(), '.forge');
26
26
  const profilesPath = path_1.default.join(buildsPath, 'profiles.json');
27
27
  await promises_1.default.writeFile(profilesPath, JSON.stringify(profiles, null, 2));
28
+ },
29
+ fetchMeInfo: async (apiKey, apiSecret, url) => {
30
+ try {
31
+ const response = await fetch(`${url}/api/me`, {
32
+ method: 'GET',
33
+ headers: {
34
+ 'Authorization': `Bearer ${apiKey}:${apiSecret}`,
35
+ 'Content-Type': 'application/json'
36
+ }
37
+ });
38
+ if (response.ok) {
39
+ const data = await response.json();
40
+ return {
41
+ success: true,
42
+ teamName: data.team?.name,
43
+ teamUuid: data.team?.uuid,
44
+ userName: data.user?.name
45
+ };
46
+ }
47
+ else {
48
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
49
+ return { success: false, error: errorData.error || `HTTP ${response.status}` };
50
+ }
51
+ }
52
+ catch (error) {
53
+ return { success: false, error: error instanceof Error ? error.message : 'Network error' };
54
+ }
28
55
  }
29
56
  };
30
57
  exports.add = (0, task_1.createTask)({
31
58
  schema,
32
59
  boundaries,
33
- fn: async function ({ name, apiKey, apiSecret, url }, { loadProfiles, persistProfiles }) {
60
+ fn: async function ({ name, apiKey, apiSecret, url }, { loadProfiles, persistProfiles, fetchMeInfo }) {
34
61
  const profiles = await loadProfiles({});
62
+ console.log('Verifying credentials...');
63
+ // Fetch team and user information from /me endpoint
64
+ const meInfo = await fetchMeInfo(apiKey, apiSecret, url);
65
+ if (!meInfo.success) {
66
+ throw new Error(`Failed to verify credentials: ${meInfo.error}`);
67
+ }
68
+ console.log('āœ… Credentials verified');
69
+ if (meInfo.userName) {
70
+ console.log(` User: ${meInfo.userName}`);
71
+ }
72
+ if (meInfo.teamName) {
73
+ console.log(` Team: ${meInfo.teamName}`);
74
+ }
75
+ // Create profile with team information
76
+ const profile = {
77
+ name,
78
+ apiKey,
79
+ apiSecret,
80
+ url,
81
+ teamName: meInfo.teamName,
82
+ teamUuid: meInfo.teamUuid,
83
+ userName: meInfo.userName
84
+ };
35
85
  // Check if profile with same name already exists
36
86
  const existingProfileIndex = profiles.profiles.findIndex(p => p.name === name);
37
87
  if (existingProfileIndex >= 0) {
38
88
  // Replace existing profile
39
- profiles.profiles[existingProfileIndex] = { name, apiKey, apiSecret, url };
89
+ profiles.profiles[existingProfileIndex] = profile;
40
90
  }
41
91
  else {
42
92
  // Add new profile
43
- profiles.profiles.push({ name, apiKey, apiSecret, url });
93
+ profiles.profiles.push(profile);
44
94
  }
45
95
  // Set as default profile
46
96
  profiles.default = name;
@@ -48,7 +98,9 @@ exports.add = (0, task_1.createTask)({
48
98
  await persistProfiles(profiles);
49
99
  return {
50
100
  status: 'Ok',
51
- message: `Profile '${name}' added and set as default`
101
+ message: `Profile '${name}' added and set as default`,
102
+ teamName: meInfo.teamName,
103
+ userName: meInfo.userName
52
104
  };
53
105
  }
54
106
  });
@@ -0,0 +1,16 @@
1
+ import { type Profiles } from '../types';
2
+ export declare const clear: import("@forgehive/task").TaskInstanceType<(argv: {}, boundaries: import("@forgehive/task").WrappedBoundaries<{
3
+ loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
4
+ clearProfiles: () => Promise<void>;
5
+ }>) => Promise<{
6
+ status: string;
7
+ message: string;
8
+ clearedCount?: undefined;
9
+ } | {
10
+ status: string;
11
+ message: string;
12
+ clearedCount: number;
13
+ }>, {
14
+ loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
15
+ clearProfiles: () => Promise<void>;
16
+ }>;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ // TASK: clear
3
+ // Run this task with:
4
+ // forge task:run auth:clear
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.clear = void 0;
10
+ const task_1 = require("@forgehive/task");
11
+ const schema_1 = require("@forgehive/schema");
12
+ const path_1 = __importDefault(require("path"));
13
+ const promises_1 = __importDefault(require("fs/promises"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const load_1 = require("./load");
16
+ const schema = new schema_1.Schema({});
17
+ const boundaries = {
18
+ loadProfiles: load_1.load.asBoundary(),
19
+ clearProfiles: async () => {
20
+ const buildsPath = path_1.default.join(os_1.default.homedir(), '.forge');
21
+ const profilesPath = path_1.default.join(buildsPath, 'profiles.json');
22
+ // Create empty profiles structure
23
+ const emptyProfiles = {
24
+ default: '',
25
+ profiles: []
26
+ };
27
+ await promises_1.default.writeFile(profilesPath, JSON.stringify(emptyProfiles, null, 2));
28
+ }
29
+ };
30
+ exports.clear = (0, task_1.createTask)({
31
+ schema,
32
+ boundaries,
33
+ fn: async function (_argv, { loadProfiles, clearProfiles }) {
34
+ const profiles = await loadProfiles({});
35
+ if (profiles.profiles.length === 0) {
36
+ console.log('No profiles found to clear.');
37
+ return { status: 'Ok', message: 'No profiles found' };
38
+ }
39
+ const profileCount = profiles.profiles.length;
40
+ console.log(`Found ${profileCount} profile(s) to clear:`);
41
+ profiles.profiles.forEach(profile => {
42
+ console.log(` - ${profile.name} (${profile.teamName || 'Unknown team'})`);
43
+ });
44
+ console.log('\\nClearing all profiles...');
45
+ // Clear all profiles
46
+ await clearProfiles();
47
+ console.log('āœ… All profiles cleared successfully');
48
+ return {
49
+ status: 'Ok',
50
+ message: `Cleared ${profileCount} profile(s)`,
51
+ clearedCount: profileCount
52
+ };
53
+ }
54
+ });
@@ -7,8 +7,14 @@ export declare const list: import("@forgehive/task").TaskInstanceType<(argv: {},
7
7
  default?: undefined;
8
8
  } | {
9
9
  default: string;
10
+ profiles: {
11
+ Name: string;
12
+ 'API Key': string;
13
+ URL: string;
14
+ Team: string;
15
+ User: string;
16
+ }[];
10
17
  status?: undefined;
11
- profiles?: undefined;
12
18
  }>, {
13
19
  loadProfiles: (args: {}) => Promise<Promise<Profiles>>;
14
20
  }>;
@@ -20,16 +20,36 @@ exports.list = (0, task_1.createTask)({
20
20
  console.log('No profiles found. Use auth:add to create one.');
21
21
  return { status: 'Ok', profiles: [] };
22
22
  }
23
- console.log('Available profiles:');
24
- profiles.profiles.forEach(profile => {
25
- const isDefault = profile.name === profiles.default;
26
- const prefix = isDefault ? '* ' : ' ';
27
- console.log(`${prefix}${profile.name} - API Key: ${profile.apiKey}`);
28
- });
23
+ // Show current profile
24
+ const currentProfile = profiles.profiles.find(profile => profile.name === profiles.default);
25
+ if (currentProfile) {
26
+ console.log('Current Profile:');
27
+ console.log(` Name: ${currentProfile.name}`);
28
+ console.log(` API Key: ${currentProfile.apiKey}`);
29
+ console.log(` URL: ${currentProfile.url}`);
30
+ if (currentProfile.userName) {
31
+ console.log(` User: ${currentProfile.userName}`);
32
+ }
33
+ if (currentProfile.teamName) {
34
+ console.log(` Team: ${currentProfile.teamName}`);
35
+ }
36
+ console.log('');
37
+ }
38
+ console.log('Available profiles:\n');
39
+ const tableData = profiles.profiles.map(profile => ({
40
+ Name: profile.name,
41
+ 'API Key': profile.apiKey,
42
+ URL: profile.url,
43
+ Team: profile.teamName || 'Unknown',
44
+ User: profile.userName || 'Unknown'
45
+ }));
46
+ console.table(tableData, ['Name', 'API Key', 'URL', 'Team', 'User']);
29
47
  console.log('\nUse auth:add to create or update a profile');
30
- console.log('\nUse auth:switch to switch to a profile');
48
+ console.log('Use auth:switch [name] or auth:switch [index] to switch profiles');
49
+ console.log('========================================');
31
50
  return {
32
- default: profiles.default
51
+ default: profiles.default,
52
+ profiles: tableData
33
53
  };
34
54
  }
35
55
  });
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // TASK: switch
3
3
  // Run this task with:
4
- // forge task:run auth:switch --profileName [name]
4
+ // forge auth:switch [name] or forge auth:switch [index]
5
5
  var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  return (mod && mod.__esModule) ? mod : { "default": mod };
7
7
  };
@@ -30,18 +30,34 @@ exports.switchProfile = (0, task_1.createTask)({
30
30
  fn: async function ({ profileName }, { loadProfiles, persistProfiles }) {
31
31
  // Load profiles
32
32
  const profiles = await loadProfiles({});
33
- // Check if profile exists
34
- const profileExists = profiles.profiles.some(profile => profile.name === profileName);
35
- if (!profileExists) {
36
- throw new Error(`Profile "${profileName}" not found. Use auth:list to see available profiles.`);
33
+ if (profiles.profiles.length === 0) {
34
+ throw new Error('No profiles found. Use auth:add to create one.');
35
+ }
36
+ let targetProfile;
37
+ // Check if profileName is a number (index)
38
+ const indexInput = parseInt(profileName, 10);
39
+ if (!isNaN(indexInput)) {
40
+ // Using index
41
+ if (indexInput < 0 || indexInput >= profiles.profiles.length) {
42
+ throw new Error(`Profile index ${indexInput} is out of range. Use auth:list to see available profiles (0-${profiles.profiles.length - 1}).`);
43
+ }
44
+ targetProfile = profiles.profiles[indexInput].name;
45
+ }
46
+ else {
47
+ // Using profile name
48
+ const profileExists = profiles.profiles.some(profile => profile.name === profileName);
49
+ if (!profileExists) {
50
+ throw new Error(`Profile "${profileName}" not found. Use auth:list to see available profiles.`);
51
+ }
52
+ targetProfile = profileName;
37
53
  }
38
54
  // Update default profile
39
- profiles.default = profileName;
55
+ profiles.default = targetProfile;
40
56
  // Save updated profiles
41
57
  await persistProfiles(profiles);
42
- console.log(`Switched to profile: ${profileName}`);
58
+ console.log(`Switched to profile: ${targetProfile}`);
43
59
  return {
44
- default: profileName
60
+ default: targetProfile
45
61
  };
46
62
  }
47
63
  });
@@ -0,0 +1,27 @@
1
+ import { type ForgeConf, type Profile } from '../types';
2
+ export declare const create: import("@forgehive/task").TaskInstanceType<(argv: {
3
+ description?: string | undefined;
4
+ projectName?: string | undefined;
5
+ }, boundaries: import("@forgehive/task").WrappedBoundaries<{
6
+ loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
7
+ loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
8
+ writeFile: (filePath: string, content: string) => Promise<void>;
9
+ createProject: (profile: Profile, payload: {
10
+ projectName: string;
11
+ description: string;
12
+ uuid: string;
13
+ }) => Promise<Response>;
14
+ }>) => Promise<{
15
+ success: boolean;
16
+ project: any;
17
+ localUuid: string;
18
+ }>, {
19
+ loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
20
+ loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
21
+ writeFile: (filePath: string, content: string) => Promise<void>;
22
+ createProject: (profile: Profile, payload: {
23
+ projectName: string;
24
+ description: string;
25
+ uuid: string;
26
+ }) => Promise<Response>;
27
+ }>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ // TASK: create
3
+ // Run this task with:
4
+ // forge task:run project:create
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.create = void 0;
10
+ const task_1 = require("@forgehive/task");
11
+ const schema_1 = require("@forgehive/schema");
12
+ const uuid_1 = require("uuid");
13
+ const promises_1 = __importDefault(require("fs/promises"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const load_1 = require("../conf/load");
16
+ const loadCurrent_1 = require("../auth/loadCurrent");
17
+ const name = 'project:create';
18
+ const description = 'Create a new project in ForgeHive';
19
+ const schema = new schema_1.Schema({
20
+ projectName: schema_1.Schema.string().optional(),
21
+ description: schema_1.Schema.string().optional()
22
+ });
23
+ const boundaries = {
24
+ loadConf: load_1.load.asBoundary(),
25
+ loadCurrentProfile: loadCurrent_1.loadCurrent.asBoundary(),
26
+ writeFile: async (filePath, content) => {
27
+ await promises_1.default.writeFile(filePath, content, 'utf-8');
28
+ },
29
+ createProject: async (profile, payload) => {
30
+ const authToken = `${profile.apiKey}:${profile.apiSecret}`;
31
+ return await fetch(`${profile.url}/api/projects`, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ 'Authorization': `Bearer ${authToken}`
36
+ },
37
+ body: JSON.stringify(payload)
38
+ });
39
+ }
40
+ };
41
+ exports.create = (0, task_1.createTask)({
42
+ name,
43
+ description,
44
+ schema,
45
+ boundaries,
46
+ fn: async function (argv, { loadConf, loadCurrentProfile, writeFile, createProject }) {
47
+ const { projectName: inputProjectName, description } = argv;
48
+ // Load current configuration
49
+ const conf = await loadConf({});
50
+ // Use provided projectName or fall back to forge.json project name
51
+ const projectName = inputProjectName || conf.project.name;
52
+ if (!projectName) {
53
+ throw new Error('Project name is required. Provide --projectName or ensure forge.json has a project name.');
54
+ }
55
+ // Check if project already has a UUID, generate one if not
56
+ let projectUuid = conf.project.uuid;
57
+ if (!projectUuid) {
58
+ projectUuid = (0, uuid_1.v4)();
59
+ // Update forge.json with the new UUID
60
+ const forgePath = path_1.default.join(process.cwd(), 'forge.json');
61
+ const updatedConf = {
62
+ ...conf,
63
+ project: {
64
+ ...conf.project,
65
+ uuid: projectUuid
66
+ }
67
+ };
68
+ await writeFile(forgePath, JSON.stringify(updatedConf, null, 2));
69
+ console.log(`Generated and saved project UUID: ${projectUuid}`);
70
+ }
71
+ // Load current profile for API authentication
72
+ const profile = await loadCurrentProfile({});
73
+ // Prepare API request payload
74
+ const payload = {
75
+ projectName,
76
+ description: description || '',
77
+ uuid: projectUuid
78
+ };
79
+ // Make API request to create project
80
+ const response = await createProject(profile, payload);
81
+ if (!response.ok) {
82
+ const errorText = await response.text();
83
+ throw new Error(`Failed to create project: ${response.status} ${response.statusText} - ${errorText}`);
84
+ }
85
+ const result = await response.json();
86
+ console.log('Project created successfully!');
87
+ console.log(`Project UUID: ${result.project.uuid}`);
88
+ console.log(`Project Name: ${result.project.projectName}`);
89
+ console.log(`\n🌐 View your project on the dashboard: ${profile.url}/dashboard/projects/${result.project.uuid}`);
90
+ return {
91
+ success: true,
92
+ project: result.project,
93
+ localUuid: projectUuid
94
+ };
95
+ }
96
+ });
@@ -0,0 +1,22 @@
1
+ import { type ForgeConf, type Profile } from '../types';
2
+ export declare const link: import("@forgehive/task").TaskInstanceType<(argv: {
3
+ uuid: string;
4
+ }, boundaries: import("@forgehive/task").WrappedBoundaries<{
5
+ loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
6
+ loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
7
+ writeFile: (filePath: string, content: string) => Promise<void>;
8
+ fetchProject: (profile: Profile, uuid: string) => Promise<Response>;
9
+ }>) => Promise<{
10
+ success: boolean;
11
+ linkedProject: {
12
+ uuid: any;
13
+ name: any;
14
+ description: any;
15
+ tasksCount: any;
16
+ };
17
+ }>, {
18
+ loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
19
+ loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
20
+ writeFile: (filePath: string, content: string) => Promise<void>;
21
+ fetchProject: (profile: Profile, uuid: string) => Promise<Response>;
22
+ }>;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ // TASK: link
3
+ // Run this task with:
4
+ // forge project:link [uuid]
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.link = void 0;
10
+ const task_1 = require("@forgehive/task");
11
+ const schema_1 = require("@forgehive/schema");
12
+ const promises_1 = __importDefault(require("fs/promises"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const load_1 = require("../conf/load");
15
+ const loadCurrent_1 = require("../auth/loadCurrent");
16
+ const name = 'project:link';
17
+ const description = 'Link an existing remote project to the local project by UUID';
18
+ const schema = new schema_1.Schema({
19
+ uuid: schema_1.Schema.string()
20
+ });
21
+ const boundaries = {
22
+ loadConf: load_1.load.asBoundary(),
23
+ loadCurrentProfile: loadCurrent_1.loadCurrent.asBoundary(),
24
+ writeFile: async (filePath, content) => {
25
+ await promises_1.default.writeFile(filePath, content, 'utf-8');
26
+ },
27
+ fetchProject: async (profile, uuid) => {
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
+ exports.link = (0, task_1.createTask)({
39
+ name,
40
+ description,
41
+ schema,
42
+ boundaries,
43
+ fn: async function ({ uuid }, { loadConf, loadCurrentProfile, writeFile, fetchProject }) {
44
+ // Load current configuration and profile
45
+ const conf = await loadConf({});
46
+ // Check if project already has a UUID
47
+ if (conf.project.uuid) {
48
+ 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.`);
49
+ }
50
+ const profile = await loadCurrentProfile({});
51
+ console.log(`Checking if project ${uuid} exists on ${profile.url}...`);
52
+ // Check if project exists on remote
53
+ const response = await fetchProject(profile, uuid);
54
+ if (!response.ok) {
55
+ if (response.status === 404) {
56
+ throw new Error(`Project with UUID ${uuid} not found on ${profile.url}. Please verify the UUID is correct.`);
57
+ }
58
+ else if (response.status === 401) {
59
+ throw new Error('Authentication failed. Please check your profile credentials with \'forge auth:list\'.');
60
+ }
61
+ else {
62
+ const errorText = await response.text();
63
+ throw new Error(`Failed to verify project: ${response.status} ${response.statusText} - ${errorText}`);
64
+ }
65
+ }
66
+ const projectData = await response.json();
67
+ const project = projectData.project;
68
+ console.log(`āœ“ Found project: ${project.projectName}`);
69
+ console.log(` Description: ${project.description || 'No description'}`);
70
+ console.log(` Tasks: ${project.tasks.length} task(s)`);
71
+ // Update forge.json with the UUID
72
+ const forgePath = path_1.default.join(process.cwd(), 'forge.json');
73
+ const updatedConf = {
74
+ ...conf,
75
+ project: {
76
+ ...conf.project,
77
+ uuid: uuid
78
+ }
79
+ };
80
+ await writeFile(forgePath, JSON.stringify(updatedConf, null, 2));
81
+ console.log(`\nāœ“ Successfully linked project ${uuid} to local forge.json`);
82
+ console.log(` Local project name: ${conf.project.name}`);
83
+ console.log(` Remote project name: ${project.projectName}`);
84
+ console.log(`\n🌐 View your project on the dashboard: ${profile.url}/dashboard/projects/${uuid}`);
85
+ return {
86
+ success: true,
87
+ linkedProject: {
88
+ uuid: project.uuid,
89
+ name: project.projectName,
90
+ description: project.description,
91
+ tasksCount: project.tasks.length
92
+ }
93
+ };
94
+ }
95
+ });