@fnndsc/chili 3.2.5

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/chefs/chefs.d.ts +15 -0
  4. package/dist/chefs/chefs.js +195 -0
  5. package/dist/commands/compute/fields.d.ts +6 -0
  6. package/dist/commands/compute/fields.js +13 -0
  7. package/dist/commands/compute/list.d.ts +19 -0
  8. package/dist/commands/compute/list.js +20 -0
  9. package/dist/commands/connect/login.d.ts +16 -0
  10. package/dist/commands/connect/login.js +18 -0
  11. package/dist/commands/connect/logout.d.ts +6 -0
  12. package/dist/commands/connect/logout.js +16 -0
  13. package/dist/commands/feed/comments.d.ts +41 -0
  14. package/dist/commands/feed/comments.js +45 -0
  15. package/dist/commands/feed/create.d.ts +13 -0
  16. package/dist/commands/feed/create.js +40 -0
  17. package/dist/commands/feed/note.d.ts +21 -0
  18. package/dist/commands/feed/note.js +24 -0
  19. package/dist/commands/feeds/delete.d.ts +14 -0
  20. package/dist/commands/feeds/delete.js +34 -0
  21. package/dist/commands/feeds/fields.d.ts +6 -0
  22. package/dist/commands/feeds/fields.js +17 -0
  23. package/dist/commands/feeds/list.d.ts +17 -0
  24. package/dist/commands/feeds/list.js +38 -0
  25. package/dist/commands/feeds/share.d.ts +17 -0
  26. package/dist/commands/feeds/share.js +19 -0
  27. package/dist/commands/file/view.d.ts +8 -0
  28. package/dist/commands/file/view.js +25 -0
  29. package/dist/commands/files/delete.d.ts +16 -0
  30. package/dist/commands/files/delete.js +36 -0
  31. package/dist/commands/files/fields.d.ts +7 -0
  32. package/dist/commands/files/fields.js +18 -0
  33. package/dist/commands/files/list.d.ts +11 -0
  34. package/dist/commands/files/list.js +23 -0
  35. package/dist/commands/fs/cat.d.ts +15 -0
  36. package/dist/commands/fs/cat.js +26 -0
  37. package/dist/commands/fs/cp.d.ts +17 -0
  38. package/dist/commands/fs/cp.js +26 -0
  39. package/dist/commands/fs/create.d.ts +15 -0
  40. package/dist/commands/fs/create.js +83 -0
  41. package/dist/commands/fs/download.d.ts +25 -0
  42. package/dist/commands/fs/download.js +235 -0
  43. package/dist/commands/fs/edit.d.ts +16 -0
  44. package/dist/commands/fs/edit.js +25 -0
  45. package/dist/commands/fs/ls.d.ts +18 -0
  46. package/dist/commands/fs/ls.js +142 -0
  47. package/dist/commands/fs/mkdir.d.ts +8 -0
  48. package/dist/commands/fs/mkdir.js +21 -0
  49. package/dist/commands/fs/mv.d.ts +13 -0
  50. package/dist/commands/fs/mv.js +73 -0
  51. package/dist/commands/fs/rm.d.ts +24 -0
  52. package/dist/commands/fs/rm.js +108 -0
  53. package/dist/commands/fs/touch.d.ts +16 -0
  54. package/dist/commands/fs/touch.js +62 -0
  55. package/dist/commands/fs/upload.d.ts +55 -0
  56. package/dist/commands/fs/upload.js +221 -0
  57. package/dist/commands/groups/fields.d.ts +6 -0
  58. package/dist/commands/groups/fields.js +13 -0
  59. package/dist/commands/groups/list.d.ts +16 -0
  60. package/dist/commands/groups/list.js +26 -0
  61. package/dist/commands/man/doc.d.ts +16 -0
  62. package/dist/commands/man/doc.js +32 -0
  63. package/dist/commands/man/topics.d.ts +6 -0
  64. package/dist/commands/man/topics.js +27 -0
  65. package/dist/commands/pipeline/fields.d.ts +6 -0
  66. package/dist/commands/pipeline/fields.js +13 -0
  67. package/dist/commands/pipeline/run.d.ts +18 -0
  68. package/dist/commands/pipeline/run.js +33 -0
  69. package/dist/commands/plugin/readme.d.ts +14 -0
  70. package/dist/commands/plugin/readme.js +49 -0
  71. package/dist/commands/plugin/run.d.ts +10 -0
  72. package/dist/commands/plugin/run.js +29 -0
  73. package/dist/commands/plugin/search.d.ts +7 -0
  74. package/dist/commands/plugin/search.js +18 -0
  75. package/dist/commands/plugininstances/fields.d.ts +6 -0
  76. package/dist/commands/plugininstances/fields.js +13 -0
  77. package/dist/commands/plugininstances/list.d.ts +16 -0
  78. package/dist/commands/plugininstances/list.js +26 -0
  79. package/dist/commands/pluginmetas/fields.d.ts +6 -0
  80. package/dist/commands/pluginmetas/fields.js +13 -0
  81. package/dist/commands/pluginmetas/list.d.ts +16 -0
  82. package/dist/commands/pluginmetas/list.js +26 -0
  83. package/dist/commands/plugins/add.d.ts +34 -0
  84. package/dist/commands/plugins/add.js +374 -0
  85. package/dist/commands/plugins/delete.d.ts +14 -0
  86. package/dist/commands/plugins/delete.js +34 -0
  87. package/dist/commands/plugins/fields.d.ts +6 -0
  88. package/dist/commands/plugins/fields.js +17 -0
  89. package/dist/commands/plugins/list.d.ts +17 -0
  90. package/dist/commands/plugins/list.js +40 -0
  91. package/dist/commands/plugins/overview.d.ts +6 -0
  92. package/dist/commands/plugins/overview.js +17 -0
  93. package/dist/commands/store/list.d.ts +23 -0
  94. package/dist/commands/store/list.js +48 -0
  95. package/dist/commands/tags/fields.d.ts +6 -0
  96. package/dist/commands/tags/fields.js +13 -0
  97. package/dist/commands/tags/list.d.ts +16 -0
  98. package/dist/commands/tags/list.js +26 -0
  99. package/dist/commands/workflows/fields.d.ts +6 -0
  100. package/dist/commands/workflows/fields.js +13 -0
  101. package/dist/commands/workflows/list.d.ts +16 -0
  102. package/dist/commands/workflows/list.js +26 -0
  103. package/dist/config/colorConfig.d.ts +56 -0
  104. package/dist/config/colorConfig.js +161 -0
  105. package/dist/connect/connectHandler.d.ts +15 -0
  106. package/dist/connect/connectHandler.js +48 -0
  107. package/dist/context/contextCommand.d.ts +13 -0
  108. package/dist/context/contextCommand.js +193 -0
  109. package/dist/controllers/baseController.d.ts +43 -0
  110. package/dist/controllers/baseController.js +67 -0
  111. package/dist/controllers/feedController.d.ts +35 -0
  112. package/dist/controllers/feedController.js +53 -0
  113. package/dist/controllers/fileController.d.ts +56 -0
  114. package/dist/controllers/fileController.js +93 -0
  115. package/dist/controllers/pluginContextController.d.ts +24 -0
  116. package/dist/controllers/pluginContextController.js +49 -0
  117. package/dist/controllers/pluginController.d.ts +70 -0
  118. package/dist/controllers/pluginController.js +98 -0
  119. package/dist/controllers/pluginMetaController.d.ts +41 -0
  120. package/dist/controllers/pluginMetaController.js +52 -0
  121. package/dist/feeds/feedHandler.d.ts +64 -0
  122. package/dist/feeds/feedHandler.js +203 -0
  123. package/dist/filesystem/fileGroupHandler.d.ts +92 -0
  124. package/dist/filesystem/fileGroupHandler.js +318 -0
  125. package/dist/filesystem/filesystemHandler.d.ts +12 -0
  126. package/dist/filesystem/filesystemHandler.js +135 -0
  127. package/dist/filesystem/inodeCommand.d.ts +12 -0
  128. package/dist/filesystem/inodeCommand.js +37 -0
  129. package/dist/handlers/baseGroupHandler.d.ts +103 -0
  130. package/dist/handlers/baseGroupHandler.js +342 -0
  131. package/dist/index.d.ts +7 -0
  132. package/dist/index.js +239 -0
  133. package/dist/lfs/lfs.d.ts +25 -0
  134. package/dist/lfs/lfs.js +74 -0
  135. package/dist/man/man.d.ts +15 -0
  136. package/dist/man/man.js +31 -0
  137. package/dist/man/renderer.d.ts +26 -0
  138. package/dist/man/renderer.js +187 -0
  139. package/dist/models/feed.d.ts +18 -0
  140. package/dist/models/feed.js +8 -0
  141. package/dist/models/listing.d.ts +33 -0
  142. package/dist/models/listing.js +9 -0
  143. package/dist/models/plugin.d.ts +27 -0
  144. package/dist/models/plugin.js +1 -0
  145. package/dist/models/resource.d.ts +39 -0
  146. package/dist/models/resource.js +9 -0
  147. package/dist/pacs/pacsQueryHandler.d.ts +55 -0
  148. package/dist/pacs/pacsQueryHandler.js +196 -0
  149. package/dist/pacs/pacsQueryPayload.d.ts +19 -0
  150. package/dist/pacs/pacsQueryPayload.js +44 -0
  151. package/dist/pacs/pacsResultRender.d.ts +20 -0
  152. package/dist/pacs/pacsResultRender.js +141 -0
  153. package/dist/pacs/pacsRetrieveHandler.d.ts +87 -0
  154. package/dist/pacs/pacsRetrieveHandler.js +272 -0
  155. package/dist/pacs/pacsServerHandler.d.ts +15 -0
  156. package/dist/pacs/pacsServerHandler.js +32 -0
  157. package/dist/path/pathCommand.d.ts +64 -0
  158. package/dist/path/pathCommand.js +805 -0
  159. package/dist/path/pathMapper.d.ts +189 -0
  160. package/dist/path/pathMapper.js +382 -0
  161. package/dist/plugins/pluginGroupHandler.d.ts +42 -0
  162. package/dist/plugins/pluginGroupHandler.js +90 -0
  163. package/dist/plugins/pluginHandler.d.ts +87 -0
  164. package/dist/plugins/pluginHandler.js +303 -0
  165. package/dist/plugins/pluginMetaHandler.d.ts +21 -0
  166. package/dist/plugins/pluginMetaHandler.js +27 -0
  167. package/dist/screen/screen.d.ts +214 -0
  168. package/dist/screen/screen.js +478 -0
  169. package/dist/utils/admin_prompt.d.ts +42 -0
  170. package/dist/utils/admin_prompt.js +105 -0
  171. package/dist/utils/cli.d.ts +67 -0
  172. package/dist/utils/cli.js +112 -0
  173. package/dist/utils/docker.d.ts +74 -0
  174. package/dist/utils/docker.js +155 -0
  175. package/dist/utils/input_format.d.ts +77 -0
  176. package/dist/utils/input_format.js +135 -0
  177. package/dist/utils/sort.d.ts +29 -0
  178. package/dist/utils/sort.js +63 -0
  179. package/dist/utils/ui.d.ts +7 -0
  180. package/dist/utils/ui.js +26 -0
  181. package/dist/utils.d.ts +15 -0
  182. package/dist/utils.js +14 -0
  183. package/dist/views/compute.d.ts +17 -0
  184. package/dist/views/compute.js +40 -0
  185. package/dist/views/connect.d.ts +13 -0
  186. package/dist/views/connect.js +35 -0
  187. package/dist/views/feed.d.ts +44 -0
  188. package/dist/views/feed.js +110 -0
  189. package/dist/views/file.d.ts +25 -0
  190. package/dist/views/file.js +71 -0
  191. package/dist/views/fs.d.ts +51 -0
  192. package/dist/views/fs.js +105 -0
  193. package/dist/views/ls.d.ts +37 -0
  194. package/dist/views/ls.js +190 -0
  195. package/dist/views/plugin.d.ts +28 -0
  196. package/dist/views/plugin.js +71 -0
  197. package/dist/views/pluginParameters.d.ts +17 -0
  198. package/dist/views/pluginParameters.js +79 -0
  199. package/docs/00_intro.adoc +25 -0
  200. package/docs/01_setup.adoc +63 -0
  201. package/docs/02_context.adoc +128 -0
  202. package/docs/03_searchable.adoc +134 -0
  203. package/docs/10_everyday_examples.adoc +9 -0
  204. package/docs/11_upload_data.adoc +146 -0
  205. package/docs/12_create_a_feed.adoc +9 -0
  206. package/docs/13_run_a_plugin.adoc +271 -0
  207. package/docs/20_plugins.adoc +16 -0
  208. package/docs/21_pluginReadme.adoc +123 -0
  209. package/docs/30_filesystem.adoc +93 -0
  210. package/docs/31_fileops.adoc +51 -0
  211. package/docs/32_file_content.adoc +471 -0
  212. package/docs/_architecture.adoc +48 -0
  213. package/docs/_deepContext.adoc +84 -0
  214. package/docs/_roadmap.adoc +42 -0
  215. package/docs/_searchable.adoc +187 -0
  216. package/docs/_state.adoc +68 -0
  217. package/docs/pacs.adoc +101 -0
  218. package/docs/pathMapper.md +246 -0
  219. package/package.json +92 -0
@@ -0,0 +1,203 @@
1
+ import { BaseGroupHandler } from "../handlers/baseGroupHandler.js";
2
+ import { errorStack } from "@fnndsc/cumin"; // Import errorStack
3
+ import { table_display } from "../screen/screen.js";
4
+ import { FeedController } from "../controllers/feedController.js";
5
+ import chalk from "chalk";
6
+ import { feedFields_fetch } from "../commands/feeds/fields.js";
7
+ import { feed_shareById } from "../commands/feeds/share.js";
8
+ import { feeds_searchByTerm, feed_deleteById } from "../commands/feeds/delete.js";
9
+ import { prompt_confirm } from "../utils/ui.js";
10
+ import { feed_create as feed_create_command } from "../commands/feed/create.js"; // Original name
11
+ import { feedCreate_render } from "../views/feed.js"; // Still needed for feedCreate_render
12
+ // import { FeedListResult } from "../commands/feeds/list.js"; // No longer needed
13
+ /**
14
+ * Handles commands related to groups of ChRIS feeds.
15
+ */
16
+ export class FeedGroupHandler {
17
+ constructor() {
18
+ this.assetName = "feeds";
19
+ this.controller = FeedController.controller_create();
20
+ this.baseGroupHandler = new BaseGroupHandler(this.assetName, this.controller.chrisObject);
21
+ }
22
+ // /**
23
+ // * Lists feeds using the new command logic.
24
+ // */
25
+ // async feeds_list(options: CLIoptions): Promise<void> {
26
+ // try {
27
+ // const { feeds, selectedFields }: FeedListResult = await feeds_fetchList(options);
28
+ // console.log(feedList_render(feeds, selectedFields, { table: options.table, csv: options.csv }));
29
+ // } catch (error: unknown) {
30
+ // const msg = error instanceof Error ? error.message : String(error);
31
+ // console.error(chalk.red(`Error listing feeds: ${msg}`));
32
+ // }
33
+ // }
34
+ /**
35
+ * Lists feed fields using the new command logic.
36
+ */
37
+ async feeds_fields() {
38
+ try {
39
+ const fields = await feedFields_fetch();
40
+ if (fields && fields.length > 0) {
41
+ table_display(fields, ["fields"]);
42
+ }
43
+ else {
44
+ console.log(`No resource fields found for ${this.assetName}.`);
45
+ }
46
+ }
47
+ catch (error) {
48
+ console.log(errorStack.stack_search(this.assetName)[0]);
49
+ }
50
+ }
51
+ /**
52
+ * Shares feeds matching a search term.
53
+ *
54
+ * @param searchable - The search term to find feeds.
55
+ * @param options - Sharing options (e.g. is_public).
56
+ */
57
+ async feeds_share(searchable, options) {
58
+ const searchParts = searchable.split("++").map((part) => part.trim());
59
+ for (const searchPart of searchParts) {
60
+ const feedIds = await this.baseGroupHandler.IDs_getFromSearch({ search: searchPart });
61
+ if (!feedIds || feedIds.length === 0) {
62
+ console.log(`No feeds found matching: ${searchPart}`);
63
+ continue;
64
+ }
65
+ for (const feedId of feedIds) {
66
+ console.log(`Sharing feed ID: ${feedId}...`);
67
+ const shareOptions = { is_public: options.is_public === true };
68
+ const success = await feed_shareById(Number(feedId), shareOptions);
69
+ if (success) {
70
+ console.log(`Feed ID ${feedId} shared successfully.`);
71
+ }
72
+ else {
73
+ console.error(`Failed to share feed ID ${feedId}.`);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ /**
79
+ * Deletes feeds using the new command logic.
80
+ * @param searchable - A string resolving to feed IDs.
81
+ * @param options - CLI options, including --force.
82
+ */
83
+ async feeds_delete(searchable, options) {
84
+ const searchParts = searchable.split("++").map((part) => part.trim());
85
+ for (const searchPart of searchParts) {
86
+ const items = await feeds_searchByTerm(searchPart);
87
+ if (items.length === 0) {
88
+ console.log(`No feeds found matching: ${searchPart}`);
89
+ continue;
90
+ }
91
+ for (const item of items) {
92
+ console.log(`Preparing to delete Feed: ID=${item.id}, Name=${item.name}`);
93
+ if (!options.force) {
94
+ const confirmed = await prompt_confirm(`Are you sure you want to delete feed ${item.name} (ID: ${item.id})?`);
95
+ if (!confirmed)
96
+ continue;
97
+ }
98
+ const id = typeof item.id === "number" ? item.id : Number(item.id);
99
+ const success = await feed_deleteById(id);
100
+ if (success) {
101
+ console.log(`Deleted feed ${item.id}`);
102
+ }
103
+ else {
104
+ console.error(`Failed to delete feed ${item.id}`);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ /**
110
+ * Sets up the Commander.js commands for feed group operations.
111
+ *
112
+ * @param program - The Commander.js program instance.
113
+ */
114
+ feedGroupCommand_setup(program) {
115
+ const feedGroupCommand = program
116
+ .command(this.assetName)
117
+ .description(`Interact with a group of ChRIS ${this.assetName}`);
118
+ const listCommand = this.baseGroupHandler.baseListCommand_create(async (options) => {
119
+ await this.baseGroupHandler.resources_list(options);
120
+ });
121
+ feedGroupCommand.addCommand(listCommand);
122
+ feedGroupCommand
123
+ .command("fieldslist")
124
+ .description(`list the ${this.assetName} resource fields`)
125
+ .action(async () => {
126
+ await this.feeds_fields();
127
+ });
128
+ feedGroupCommand
129
+ .command("delete <searchable>")
130
+ .description(`delete target ${this.assetName} resolved from '++' separated <searchable>, i.e. "id:77++id:33"`)
131
+ .option("-f, --force", `force the deletion without prompting for user confirmation`)
132
+ .action(async (searchable, options) => {
133
+ await this.feeds_delete(searchable, options);
134
+ });
135
+ feedGroupCommand
136
+ .command("share <searchable>")
137
+ .description("share a (group of) feed(s)")
138
+ .option("-i, --is_public", "Make the feed public or unpublic (default: public)")
139
+ .option("-f, --force", "force operation (do not ask for user confirmation)")
140
+ .action(async (searchable, options) => {
141
+ await this.feeds_share(searchable, options);
142
+ });
143
+ }
144
+ }
145
+ /**
146
+ * Handles commands related to individual ChRIS feeds.
147
+ */
148
+ export class FeedMemberHandler {
149
+ constructor() {
150
+ this.assetName = "feed";
151
+ this.controller = FeedController.controller_create();
152
+ }
153
+ /**
154
+ * Creates a new ChRIS feed based on CLI options.
155
+ *
156
+ * @param options - CLI options for feed creation.
157
+ * @returns A Promise resolving to the created Feed object, or null on failure.
158
+ */
159
+ async feed_create(options) {
160
+ try {
161
+ const feed = await feed_create_command(options);
162
+ if (feed) {
163
+ console.log(feedCreate_render(feed));
164
+ return feed;
165
+ }
166
+ console.error(chalk.red("Feed creation returned null result."));
167
+ const errors = errorStack.allOfType_get('error'); // Keep error reporting
168
+ if (errors.length > 0) {
169
+ console.error(chalk.red('Errors:'));
170
+ errors.forEach(e => console.error(chalk.red(` - ${e}`)));
171
+ }
172
+ return null;
173
+ }
174
+ catch (error) {
175
+ const message = error instanceof Error ? error.message : String(error);
176
+ console.error(chalk.red(`Error: ${message}`));
177
+ return null;
178
+ }
179
+ }
180
+ /**
181
+ * Sets up the Commander.js commands for individual feed operations.
182
+ *
183
+ * @param program - The Commander.js program instance.
184
+ */
185
+ feedCommand_setup(program) {
186
+ const feedCommand = program
187
+ .command(this.assetName)
188
+ .description(`Interact with a single ChRIS ${this.assetName}`);
189
+ if (feedCommand) {
190
+ feedCommand
191
+ .command("create")
192
+ .description("Create a new feed")
193
+ .option("-p, --params <FeedParams>", "comma separated list of feed parameters ('title', 'cpu_limit', etc.)")
194
+ .option("-d, --dirs <ChRISpath>", "a (comma separated) path inside the ChRIS FS containing data for the root node")
195
+ .action(async (options) => {
196
+ await this.feed_create(options);
197
+ });
198
+ }
199
+ else {
200
+ console.error(`Failed to find '${this.assetName}' command. The 'new' subcommand was not added.`);
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @file Manages command groups and member operations for ChRIS files.
3
+ *
4
+ * This module defines the `FileGroupHandler` and `FileMemberHandler` classes,
5
+ * which orchestrate CLI commands for lists of files and individual file operations.
6
+ * It integrates various command modules to provide a cohesive interface.
7
+ *
8
+ * @module
9
+ */
10
+ import { Command } from "commander";
11
+ import { CLIoptions } from "../utils/cli.js";
12
+ /**
13
+ * Handles commands related to groups of ChRIS files, links, or directories.
14
+ */
15
+ export declare class FileGroupHandler {
16
+ private baseGroupHandler;
17
+ private controller;
18
+ readonly assetName: string;
19
+ private constructor();
20
+ /**
21
+ * Factory method to create a new FileGroupHandler instance.
22
+ *
23
+ * @param assetName - The type of asset to handle ('files', 'links', 'dirs').
24
+ * @param path - Optional path within ChRIS FS. Defaults to current ChRIS folder context.
25
+ * @returns A Promise resolving to a new FileGroupHandler instance.
26
+ */
27
+ static handler_create(assetName: string, path?: string): Promise<FileGroupHandler>;
28
+ /**
29
+ * Removes duplicate column headers from FilteredResourceData results.
30
+ * Copied from BaseGroupHandler to support local listing logic.
31
+ */
32
+ private columns_removeDuplicates;
33
+ /**
34
+ * Lists files, links, or directories using the new command logic.
35
+ */
36
+ files_list(options: CLIoptions, path?: string): Promise<void>;
37
+ /**
38
+ * Lists file fields using the new command logic.
39
+ */
40
+ files_fields(): Promise<void>;
41
+ /**
42
+ * Deletes files using the new command logic.
43
+ */
44
+ files_delete(searchable: string, options: CLIoptions): Promise<void>;
45
+ /**
46
+ * Handles sharing of ChRIS files.
47
+ *
48
+ * @param options - CLI options for sharing files.
49
+ */
50
+ files_share(options: CLIoptions): Promise<void>;
51
+ /**
52
+ * Sets up the Commander.js commands for file group operations.
53
+ *
54
+ * @param program - The Commander.js program instance.
55
+ */
56
+ fileGroupCommand_setup(program: Command): void;
57
+ }
58
+ /**
59
+ * Handles commands related to individual ChRIS files.
60
+ */
61
+ export declare class FileMemberHandler {
62
+ private baseGroupHandler;
63
+ private controller;
64
+ assetName: string;
65
+ private constructor();
66
+ /**
67
+ * Factory method to create a new FileMemberHandler instance.
68
+ *
69
+ * @param path - The path to the individual file.
70
+ * @returns A Promise resolving to a new FileMemberHandler instance.
71
+ */
72
+ static handler_create(path: string): Promise<FileMemberHandler>;
73
+ /**
74
+ * Creates a new file in ChRIS with specified content.
75
+ *
76
+ * @param fileIdentifier - The primary argument for the file (name or path).
77
+ * @param options - CLI options including `--content`, `--from-file`, `--path`, `--name`.
78
+ */
79
+ file_create(fileIdentifier: string | undefined, options: CLIoptions): Promise<void>;
80
+ /**
81
+ * Sets up the Commander.js commands for individual file operations.
82
+ *
83
+ * @param program - The Commander.js program instance.
84
+ */
85
+ fileMemberCommand_setup(program: Command): void;
86
+ /**
87
+ * Displays the content of the file.
88
+ *
89
+ * @param options - CLI options for viewing the file.
90
+ */
91
+ file_cat(options: CLIoptions): Promise<void>;
92
+ }
@@ -0,0 +1,318 @@
1
+ import chalk from "chalk";
2
+ import { BaseGroupHandler } from "../handlers/baseGroupHandler.js";
3
+ import { path_resolveChrisFs } from "../utils/cli.js";
4
+ import { FileController } from "../controllers/fileController.js";
5
+ import { files_create } from "../commands/fs/create.js";
6
+ import { errorStack } from "@fnndsc/cumin";
7
+ import { table_display } from "../screen/screen.js";
8
+ import { files_fetchList } from "../commands/files/list.js";
9
+ import { fileFields_fetch } from "../commands/files/fields.js";
10
+ import { files_searchByTerm, files_deleteById } from "../commands/files/delete.js";
11
+ import { prompt_confirm } from "../utils/ui.js";
12
+ import { files_viewContent } from "../commands/file/view.js";
13
+ import { fileList_render } from "../views/file.js";
14
+ /**
15
+ * Handles commands related to groups of ChRIS files, links, or directories.
16
+ */
17
+ export class FileGroupHandler {
18
+ constructor(controller, assetName) {
19
+ this.controller = controller;
20
+ this.assetName = assetName;
21
+ // baseGroupHandler will be used for fieldslist, delete, share until those are refactored
22
+ this.baseGroupHandler = new BaseGroupHandler(this.assetName, this.controller.chrisObject);
23
+ }
24
+ /**
25
+ * Factory method to create a new FileGroupHandler instance.
26
+ *
27
+ * @param assetName - The type of asset to handle ('files', 'links', 'dirs').
28
+ * @param path - Optional path within ChRIS FS. Defaults to current ChRIS folder context.
29
+ * @returns A Promise resolving to a new FileGroupHandler instance.
30
+ */
31
+ static async handler_create(assetName, path) {
32
+ try {
33
+ const controller = await FileController.handler_create(assetName, path);
34
+ if (controller === null) {
35
+ throw new Error(`Failed to create FileController for asset type: ${assetName}`);
36
+ }
37
+ return new FileGroupHandler(controller, assetName);
38
+ }
39
+ catch (error) {
40
+ const errorMessage = error instanceof Error ? error.message : String(error);
41
+ throw new Error(`Failed to initialize FileGroupHandler for ${assetName}: ${errorMessage}`);
42
+ }
43
+ }
44
+ /**
45
+ * Removes duplicate column headers from FilteredResourceData results.
46
+ * Copied from BaseGroupHandler to support local listing logic.
47
+ */
48
+ columns_removeDuplicates(results) {
49
+ const uniqueHeaders = Array.from(new Set(results.selectedFields));
50
+ const uniqueTableData = results.tableData.map((row) => uniqueHeaders.reduce((acc, header) => {
51
+ if (typeof header === "string" && header in row) {
52
+ acc[header] = row[header];
53
+ }
54
+ return acc;
55
+ }, {}));
56
+ return {
57
+ ...results,
58
+ selectedFields: uniqueHeaders,
59
+ tableData: uniqueTableData,
60
+ };
61
+ }
62
+ /**
63
+ * Lists files, links, or directories using the new command logic.
64
+ */
65
+ async files_list(options, path) {
66
+ try {
67
+ const results = await files_fetchList(options, this.assetName, path);
68
+ if (!results) {
69
+ console.error(`No ${this.assetName} resources found. Perhaps check your current context?`);
70
+ return;
71
+ }
72
+ if (results.tableData.length === 0) {
73
+ console.log(`No ${this.assetName} found matching the criteria.`);
74
+ }
75
+ else {
76
+ const uniqueResults = this.columns_removeDuplicates(results);
77
+ // cumin returns dynamic table rows (Record<string, unknown>[]);
78
+ // narrow to the FileResource view model at this boundary.
79
+ console.log(fileList_render(uniqueResults.tableData, uniqueResults.selectedFields, { table: options.table, csv: options.csv }));
80
+ }
81
+ }
82
+ catch (error) {
83
+ const errors = errorStack.stack_search(this.assetName);
84
+ if (errors.length > 0) {
85
+ console.log(errors[0]);
86
+ }
87
+ else {
88
+ const msg = error instanceof Error ? error.message : String(error);
89
+ console.error(`Error: ${msg}`);
90
+ const errObj = error;
91
+ if (msg.includes("Internal server error") || (errObj && errObj.response && errObj.response.status === 500)) {
92
+ console.error(chalk.yellow("\nHint: This indicates a problem on the ChRIS server (CUBE)."));
93
+ console.error(chalk.yellow(" Please contact your system administrator or check the CUBE logs."));
94
+ console.error(chalk.yellow(" To see full debug output, run the command again with CHILI_DEBUG=true:"));
95
+ console.error(chalk.yellow(" CHILI_DEBUG=true chili ..."));
96
+ }
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Lists file fields using the new command logic.
102
+ */
103
+ async files_fields() {
104
+ try {
105
+ const fields = await fileFields_fetch(this.assetName);
106
+ if (fields && fields.length > 0) {
107
+ table_display(fields, ["fields"]);
108
+ }
109
+ else {
110
+ console.log(`No resource fields found for ${this.assetName}.`);
111
+ }
112
+ }
113
+ catch (error) {
114
+ console.log(errorStack.stack_search(this.assetName)[0]);
115
+ }
116
+ }
117
+ /**
118
+ * Deletes files using the new command logic.
119
+ */
120
+ async files_delete(searchable, options) {
121
+ const searchParts = searchable.split("++").map((part) => part.trim());
122
+ for (const searchPart of searchParts) {
123
+ const items = await files_searchByTerm(searchPart, this.assetName);
124
+ if (items.length === 0) {
125
+ console.log(`No ${this.assetName} found matching: ${searchPart}`);
126
+ continue;
127
+ }
128
+ for (const item of items) {
129
+ const displayName = item.fname || item.path || item.id;
130
+ if (!item.id) {
131
+ console.error(`Cannot delete item without ID. Details: ${JSON.stringify(item)}`);
132
+ continue;
133
+ }
134
+ console.log(`Preparing to delete ${this.assetName}: ID=${item.id}, Name=${displayName}`);
135
+ if (!options.force) {
136
+ const confirmed = await prompt_confirm(`Are you sure you want to delete ${this.assetName} ${displayName} (ID: ${item.id})?`);
137
+ if (!confirmed)
138
+ continue;
139
+ }
140
+ const success = await files_deleteById(item.id, this.assetName);
141
+ if (success) {
142
+ console.log(`Deleted ${this.assetName} ${item.id}`);
143
+ }
144
+ else {
145
+ console.error(`Failed to delete ${this.assetName} ${item.id}`);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ /**
151
+ * Handles sharing of ChRIS files.
152
+ *
153
+ * @param options - CLI options for sharing files.
154
+ */
155
+ async files_share(options) {
156
+ try {
157
+ console.log(`Sharing ${this.assetName} from ${this.controller.path_get}...`);
158
+ if (options.force) {
159
+ console.log("Force sharing enabled");
160
+ }
161
+ await this.controller.files_share(options);
162
+ }
163
+ catch (error) {
164
+ if (error instanceof Error) {
165
+ console.error(`Error sharing ${this.assetName}: ${error.message}`);
166
+ }
167
+ else {
168
+ console.error(`An unknown error occurred while sharing the ${this.assetName}`);
169
+ }
170
+ }
171
+ }
172
+ /**
173
+ * Sets up the Commander.js commands for file group operations.
174
+ *
175
+ * @param program - The Commander.js program instance.
176
+ */
177
+ fileGroupCommand_setup(program) {
178
+ const fileGroupCommand = program
179
+ .command(this.assetName)
180
+ .description(`Interact with a group of ChRIS ${this.assetName}`);
181
+ const listCommand = this.baseGroupHandler.baseListCommand_create(async (options) => {
182
+ await this.files_list(options);
183
+ });
184
+ // Add path argument and override action to handle it properly
185
+ listCommand.argument('[path]', 'Optional path to list');
186
+ listCommand.action(async (path, options) => {
187
+ await this.files_list(options, path);
188
+ });
189
+ fileGroupCommand.addCommand(listCommand);
190
+ fileGroupCommand
191
+ .command("fieldslist")
192
+ .description(`list the ${this.assetName} resource fields`)
193
+ .action(async () => {
194
+ await this.files_fields();
195
+ });
196
+ fileGroupCommand
197
+ .command("delete <searchable>")
198
+ .description(`delete target ${this.assetName} resolved from '++' separated <searchable>, i.e. "id:77++id:33"`)
199
+ .option("-f, --force", "force sharing (do not ask for user confirmation)")
200
+ .action(async (searchable, options) => {
201
+ await this.files_delete(searchable, options);
202
+ });
203
+ fileGroupCommand
204
+ .command("share")
205
+ .description(`share a (group of) ${this.assetName}`)
206
+ .option("-f, --force", "force sharing (do not ask for user confirmation)")
207
+ .action(async (options) => {
208
+ // Still delegates to baseGroupHandler for now
209
+ await this.files_share(options);
210
+ });
211
+ }
212
+ }
213
+ /**
214
+ * Handles commands related to individual ChRIS files.
215
+ */
216
+ export class FileMemberHandler {
217
+ constructor(controller) {
218
+ this.assetName = "file";
219
+ this.controller = controller;
220
+ this.baseGroupHandler = new BaseGroupHandler(this.assetName, this.controller.chrisObject);
221
+ }
222
+ /**
223
+ * Factory method to create a new FileMemberHandler instance.
224
+ *
225
+ * @param path - The path to the individual file.
226
+ * @returns A Promise resolving to a new FileMemberHandler instance.
227
+ */
228
+ static async handler_create(path) {
229
+ try {
230
+ const controller = await FileController.member_create(path);
231
+ if (controller === null) {
232
+ throw new Error(`Failed to create FileController for path: ${path}`);
233
+ }
234
+ return new FileMemberHandler(controller);
235
+ }
236
+ catch (error) {
237
+ const errorMessage = error instanceof Error ? error.message : String(error);
238
+ throw new Error(`Failed to initialize FileMemberHandler for path ${path}: ${errorMessage}`);
239
+ }
240
+ }
241
+ /**
242
+ * Creates a new file in ChRIS with specified content.
243
+ *
244
+ * @param fileIdentifier - The primary argument for the file (name or path).
245
+ * @param options - CLI options including `--content`, `--from-file`, `--path`, `--name`.
246
+ */
247
+ async file_create(fileIdentifier, options) {
248
+ try {
249
+ // files_create now throws on error
250
+ const success = await files_create(fileIdentifier, options);
251
+ if (success) {
252
+ const resolvedChRISPath = await path_resolveChrisFs(fileIdentifier, options);
253
+ console.log(`File created successfully at: ${resolvedChRISPath}`);
254
+ }
255
+ // If success is false, files_create would have thrown an error which is caught below.
256
+ }
257
+ catch (error) {
258
+ // Log the error from files_create
259
+ const message = error instanceof Error ? error.message : String(error);
260
+ console.error(message);
261
+ }
262
+ }
263
+ /**
264
+ * Sets up the Commander.js commands for individual file operations.
265
+ *
266
+ * @param program - The Commander.js program instance.
267
+ */
268
+ fileMemberCommand_setup(program) {
269
+ const fileCommand = program
270
+ .command(this.assetName)
271
+ .description(`Interact with a single ChRIS ${this.assetName}`);
272
+ const existingCommand = program.commands.find(cmd => cmd.name() === this.assetName);
273
+ const commandToUse = existingCommand || fileCommand;
274
+ commandToUse
275
+ .command("create [fileIdentifier]")
276
+ .description("Create a new file in ChRIS with optional content")
277
+ .option("-c, --content <text>", "Inline content for the new file")
278
+ .option("-f, --from-file <localPath>", "Path to a local file to upload as content")
279
+ .option("-p, --path <path>", "Base ChRIS directory for the file (overrides current context if relative)")
280
+ .option("-n, --name <name>", "Explicit filename (useful when fileIdentifier is a path or omitted)")
281
+ .action(async (fileIdentifier, options) => {
282
+ await this.file_create(fileIdentifier, options);
283
+ });
284
+ // TODO: Add cat/view command here
285
+ // Note: The original file_cat was present but not hooked up in setup?
286
+ // Wait, looking at previous file, file_cat existed but wasn't in fileMemberCommand_setup?
287
+ // Ah, I should add it.
288
+ commandToUse
289
+ .command("cat")
290
+ .description("View the content of the file")
291
+ .action(async (options) => {
292
+ await this.file_cat(options);
293
+ });
294
+ console.log("FileMemberHandler commands set up successfully");
295
+ }
296
+ /**
297
+ * Displays the content of the file.
298
+ *
299
+ * @param options - CLI options for viewing the file.
300
+ */
301
+ async file_cat(options) {
302
+ try {
303
+ const path = this.controller.path_get;
304
+ console.log(`Viewing file at ${path}`);
305
+ const content = await files_viewContent(path);
306
+ if (content !== null) {
307
+ console.log(content);
308
+ }
309
+ else {
310
+ console.error("Failed to view file content (empty or error).");
311
+ }
312
+ }
313
+ catch (error) {
314
+ const message = error instanceof Error ? error.message : String(error);
315
+ console.error(`Error viewing file: ${message}`);
316
+ }
317
+ }
318
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file Commander setup for the ChRIS file-browser command group.
3
+ *
4
+ * @module
5
+ */
6
+ import { Command } from "commander";
7
+ /**
8
+ * Sets up the 'fobj' command for interacting with ChRIS filesystem objects.
9
+ *
10
+ * @param program - The Commander.js program instance.
11
+ */
12
+ export declare function fileBrowserCommand_setup(program: Command): Promise<void>;