intelligent-system-design-language 0.3.13

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 (209) hide show
  1. package/.claude/agents/langium-language-designer.md +38 -0
  2. package/.claude/agents/typescript-vscode-expert.md +29 -0
  3. package/.claude/agents/ui-ux-designer.md +36 -0
  4. package/.claude/settings.local.json +33 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +7 -0
  6. package/.idea/isdl.iml +14 -0
  7. package/.idea/modules.xml +9 -0
  8. package/.idea/vcs.xml +7 -0
  9. package/.idea/watcherTasks.xml +4 -0
  10. package/.vscodeignore +18 -0
  11. package/LICENSE +674 -0
  12. package/README.md +86 -0
  13. package/bin/cli.js +4 -0
  14. package/bin/lsp.js +8 -0
  15. package/isdl.png +0 -0
  16. package/out/_backgrounds.scss +91 -0
  17. package/out/_handlebars.scss +505 -0
  18. package/out/_isdlStyles.scss +1357 -0
  19. package/out/_vuetifyOverrides.scss +425 -0
  20. package/out/_vuetifyStyles.scss +31957 -0
  21. package/out/cli/cli-util.js +39 -0
  22. package/out/cli/cli-util.js.map +1 -0
  23. package/out/cli/components/_backgrounds.scss +91 -0
  24. package/out/cli/components/_handlebars.scss +505 -0
  25. package/out/cli/components/_isdlStyles.scss +1357 -0
  26. package/out/cli/components/_vuetifyOverrides.scss +425 -0
  27. package/out/cli/components/_vuetifyStyles.scss +31957 -0
  28. package/out/cli/components/active-effect-sheet-generator.js +643 -0
  29. package/out/cli/components/active-effect-sheet-generator.js.map +1 -0
  30. package/out/cli/components/base-actor-sheet-generator.js +125 -0
  31. package/out/cli/components/base-actor-sheet-generator.js.map +1 -0
  32. package/out/cli/components/base-sheet-generator.js +525 -0
  33. package/out/cli/components/base-sheet-generator.js.map +1 -0
  34. package/out/cli/components/chat-card-generator.js +683 -0
  35. package/out/cli/components/chat-card-generator.js.map +1 -0
  36. package/out/cli/components/css-generator.js +58 -0
  37. package/out/cli/components/css-generator.js.map +1 -0
  38. package/out/cli/components/damage-roll-generator.js +173 -0
  39. package/out/cli/components/damage-roll-generator.js.map +1 -0
  40. package/out/cli/components/datamodel-generator.js +672 -0
  41. package/out/cli/components/datamodel-generator.js.map +1 -0
  42. package/out/cli/components/derived-data-generator.js +1340 -0
  43. package/out/cli/components/derived-data-generator.js.map +1 -0
  44. package/out/cli/components/hotbar-drop-hook-generator.js +95 -0
  45. package/out/cli/components/hotbar-drop-hook-generator.js.map +1 -0
  46. package/out/cli/components/init-hook-generator.js +597 -0
  47. package/out/cli/components/init-hook-generator.js.map +1 -0
  48. package/out/cli/components/keywords-generator.js +220 -0
  49. package/out/cli/components/keywords-generator.js.map +1 -0
  50. package/out/cli/components/language-generator.js +110 -0
  51. package/out/cli/components/language-generator.js.map +1 -0
  52. package/out/cli/components/measured-template-preview.js +234 -0
  53. package/out/cli/components/measured-template-preview.js.map +1 -0
  54. package/out/cli/components/method-generator.js +1812 -0
  55. package/out/cli/components/method-generator.js.map +1 -0
  56. package/out/cli/components/ready-hook-generator.js +448 -0
  57. package/out/cli/components/ready-hook-generator.js.map +1 -0
  58. package/out/cli/components/token-generator.js +138 -0
  59. package/out/cli/components/token-generator.js.map +1 -0
  60. package/out/cli/components/utils.js +176 -0
  61. package/out/cli/components/utils.js.map +1 -0
  62. package/out/cli/components/vue/base-components/vue-attribute.js +148 -0
  63. package/out/cli/components/vue/base-components/vue-attribute.js.map +1 -0
  64. package/out/cli/components/vue/base-components/vue-boolean.js +77 -0
  65. package/out/cli/components/vue/base-components/vue-boolean.js.map +1 -0
  66. package/out/cli/components/vue/base-components/vue-calculator.js +106 -0
  67. package/out/cli/components/vue/base-components/vue-calculator.js.map +1 -0
  68. package/out/cli/components/vue/base-components/vue-damage-application.js +369 -0
  69. package/out/cli/components/vue/base-components/vue-damage-application.js.map +1 -0
  70. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +225 -0
  71. package/out/cli/components/vue/base-components/vue-damage-bonuses.js.map +1 -0
  72. package/out/cli/components/vue/base-components/vue-damage-resistances.js +256 -0
  73. package/out/cli/components/vue/base-components/vue-damage-resistances.js.map +1 -0
  74. package/out/cli/components/vue/base-components/vue-damage-track.js +134 -0
  75. package/out/cli/components/vue/base-components/vue-damage-track.js.map +1 -0
  76. package/out/cli/components/vue/base-components/vue-date-time.js +55 -0
  77. package/out/cli/components/vue/base-components/vue-date-time.js.map +1 -0
  78. package/out/cli/components/vue/base-components/vue-dice.js +111 -0
  79. package/out/cli/components/vue/base-components/vue-dice.js.map +1 -0
  80. package/out/cli/components/vue/base-components/vue-die.js +86 -0
  81. package/out/cli/components/vue/base-components/vue-die.js.map +1 -0
  82. package/out/cli/components/vue/base-components/vue-document-choice.js +172 -0
  83. package/out/cli/components/vue/base-components/vue-document-choice.js.map +1 -0
  84. package/out/cli/components/vue/base-components/vue-document-choices.js +203 -0
  85. package/out/cli/components/vue/base-components/vue-document-choices.js.map +1 -0
  86. package/out/cli/components/vue/base-components/vue-document-link.js +73 -0
  87. package/out/cli/components/vue/base-components/vue-document-link.js.map +1 -0
  88. package/out/cli/components/vue/base-components/vue-extended-choice.js +101 -0
  89. package/out/cli/components/vue/base-components/vue-extended-choice.js.map +1 -0
  90. package/out/cli/components/vue/base-components/vue-inventory.js +532 -0
  91. package/out/cli/components/vue/base-components/vue-inventory.js.map +1 -0
  92. package/out/cli/components/vue/base-components/vue-macro-choice.js +150 -0
  93. package/out/cli/components/vue/base-components/vue-macro-choice.js.map +1 -0
  94. package/out/cli/components/vue/base-components/vue-measured-template.js +543 -0
  95. package/out/cli/components/vue/base-components/vue-measured-template.js.map +1 -0
  96. package/out/cli/components/vue/base-components/vue-money.js +496 -0
  97. package/out/cli/components/vue/base-components/vue-money.js.map +1 -0
  98. package/out/cli/components/vue/base-components/vue-number.js +184 -0
  99. package/out/cli/components/vue/base-components/vue-number.js.map +1 -0
  100. package/out/cli/components/vue/base-components/vue-paperdoll.js +56 -0
  101. package/out/cli/components/vue/base-components/vue-paperdoll.js.map +1 -0
  102. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +89 -0
  103. package/out/cli/components/vue/base-components/vue-parent-property-reference.js.map +1 -0
  104. package/out/cli/components/vue/base-components/vue-prosemirror.js +31 -0
  105. package/out/cli/components/vue/base-components/vue-prosemirror.js.map +1 -0
  106. package/out/cli/components/vue/base-components/vue-resource.js +149 -0
  107. package/out/cli/components/vue/base-components/vue-resource.js.map +1 -0
  108. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +121 -0
  109. package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -0
  110. package/out/cli/components/vue/base-components/vue-self-property-reference.js +75 -0
  111. package/out/cli/components/vue/base-components/vue-self-property-reference.js.map +1 -0
  112. package/out/cli/components/vue/base-components/vue-string-choice.js +111 -0
  113. package/out/cli/components/vue/base-components/vue-string-choice.js.map +1 -0
  114. package/out/cli/components/vue/base-components/vue-string-choices.js +216 -0
  115. package/out/cli/components/vue/base-components/vue-string-choices.js.map +1 -0
  116. package/out/cli/components/vue/base-components/vue-string.js +73 -0
  117. package/out/cli/components/vue/base-components/vue-string.js.map +1 -0
  118. package/out/cli/components/vue/base-components/vue-text-field.js +66 -0
  119. package/out/cli/components/vue/base-components/vue-text-field.js.map +1 -0
  120. package/out/cli/components/vue/base-components/vue-tracker.js +444 -0
  121. package/out/cli/components/vue/base-components/vue-tracker.js.map +1 -0
  122. package/out/cli/components/vue/vue-action-component-generator.js +88 -0
  123. package/out/cli/components/vue/vue-action-component-generator.js.map +1 -0
  124. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +1016 -0
  125. package/out/cli/components/vue/vue-active-effect-sheet-generator.js.map +1 -0
  126. package/out/cli/components/vue/vue-base-components-generator.js +59 -0
  127. package/out/cli/components/vue/vue-base-components-generator.js.map +1 -0
  128. package/out/cli/components/vue/vue-datatable-component-generator.js +307 -0
  129. package/out/cli/components/vue/vue-datatable-component-generator.js.map +1 -0
  130. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +342 -0
  131. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js.map +1 -0
  132. package/out/cli/components/vue/vue-datatable2-component-generator.js +939 -0
  133. package/out/cli/components/vue/vue-datatable2-component-generator.js.map +1 -0
  134. package/out/cli/components/vue/vue-document-creation-app.js +140 -0
  135. package/out/cli/components/vue/vue-document-creation-app.js.map +1 -0
  136. package/out/cli/components/vue/vue-document-creation-sheet.js +105 -0
  137. package/out/cli/components/vue/vue-document-creation-sheet.js.map +1 -0
  138. package/out/cli/components/vue/vue-generator.js +240 -0
  139. package/out/cli/components/vue/vue-generator.js.map +1 -0
  140. package/out/cli/components/vue/vue-mixin.js +338 -0
  141. package/out/cli/components/vue/vue-mixin.js.map +1 -0
  142. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +306 -0
  143. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js.map +1 -0
  144. package/out/cli/components/vue/vue-prompt-generator.js +201 -0
  145. package/out/cli/components/vue/vue-prompt-generator.js.map +1 -0
  146. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +252 -0
  147. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js.map +1 -0
  148. package/out/cli/components/vue/vue-sheet-application-generator.js +2008 -0
  149. package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -0
  150. package/out/cli/components/vue/vue-sheet-class-generator.js +484 -0
  151. package/out/cli/components/vue/vue-sheet-class-generator.js.map +1 -0
  152. package/out/cli/generator.js +659 -0
  153. package/out/cli/generator.js.map +1 -0
  154. package/out/cli/main.js +43 -0
  155. package/out/cli/main.js.map +1 -0
  156. package/out/datatables.min.css +54 -0
  157. package/out/datatables.min.js +178 -0
  158. package/out/extension/github/githubAuthProvider.js +345 -0
  159. package/out/extension/github/githubAuthProvider.js.map +1 -0
  160. package/out/extension/github/githubConfig.js +132 -0
  161. package/out/extension/github/githubConfig.js.map +1 -0
  162. package/out/extension/github/githubGistActions.js +251 -0
  163. package/out/extension/github/githubGistActions.js.map +1 -0
  164. package/out/extension/github/githubGistManager.js +255 -0
  165. package/out/extension/github/githubGistManager.js.map +1 -0
  166. package/out/extension/github/githubManager.js +1735 -0
  167. package/out/extension/github/githubManager.js.map +1 -0
  168. package/out/extension/github/githubQuickActions.js +659 -0
  169. package/out/extension/github/githubQuickActions.js.map +1 -0
  170. package/out/extension/github/githubTreeProvider.js +181 -0
  171. package/out/extension/github/githubTreeProvider.js.map +1 -0
  172. package/out/extension/github/system-workflow.yml +48 -0
  173. package/out/extension/main.cjs +70315 -0
  174. package/out/extension/main.cjs.map +7 -0
  175. package/out/extension/main.js +237 -0
  176. package/out/extension/main.js.map +1 -0
  177. package/out/extension/package.json +426 -0
  178. package/out/isdl.png +0 -0
  179. package/out/language/generated/ast.js +2992 -0
  180. package/out/language/generated/ast.js.map +1 -0
  181. package/out/language/generated/grammar.js +13970 -0
  182. package/out/language/generated/grammar.js.map +1 -0
  183. package/out/language/generated/module.js +20 -0
  184. package/out/language/generated/module.js.map +1 -0
  185. package/out/language/intelligent-system-design-language-formatter.js +85 -0
  186. package/out/language/intelligent-system-design-language-formatter.js.map +1 -0
  187. package/out/language/intelligent-system-design-language-module.js +69 -0
  188. package/out/language/intelligent-system-design-language-module.js.map +1 -0
  189. package/out/language/intelligent-system-design-language-quickfixes.js +37 -0
  190. package/out/language/intelligent-system-design-language-quickfixes.js.map +1 -0
  191. package/out/language/intelligent-system-design-language-validator.js +515 -0
  192. package/out/language/intelligent-system-design-language-validator.js.map +1 -0
  193. package/out/language/isdl-hover-provider.js +77 -0
  194. package/out/language/isdl-hover-provider.js.map +1 -0
  195. package/out/language/isdl-scope-provider.js +149 -0
  196. package/out/language/isdl-scope-provider.js.map +1 -0
  197. package/out/language/main.cjs +47655 -0
  198. package/out/language/main.cjs.map +7 -0
  199. package/out/language/main.js +11 -0
  200. package/out/language/main.js.map +1 -0
  201. package/out/missing-character.png +0 -0
  202. package/out/package.json +426 -0
  203. package/out/paperdoll_default.png +0 -0
  204. package/out/progressbar.min.js +7 -0
  205. package/out/styles.scss +722 -0
  206. package/out/test/formatting/formatter.test.js +46 -0
  207. package/out/test/formatting/formatter.test.js.map +1 -0
  208. package/out/vuetify.esm.js +30279 -0
  209. package/package.json +426 -0
@@ -0,0 +1,1735 @@
1
+ import * as vscode from 'vscode';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as crypto from 'crypto';
5
+ import { GitHubAuthProvider } from './githubAuthProvider.js';
6
+ import { GitHubConfigurationManager } from './githubConfig.js';
7
+ import { Octokit } from '@octokit/rest';
8
+ import { createIntelligentSystemDesignLanguageServices } from '../../language/intelligent-system-design-language-module.js';
9
+ import { NodeFileSystem } from 'langium/node';
10
+ import { isConfigExpression } from '../../language/generated/ast.js';
11
+ import { URI } from 'langium';
12
+ /**
13
+ * Extension-safe document extraction that doesn't call process.exit()
14
+ */
15
+ async function extractDocumentSafe(fileName, services) {
16
+ var _a;
17
+ const extensions = services.LanguageMetaData.fileExtensions;
18
+ if (!extensions.includes(path.extname(fileName))) {
19
+ console.error(`Invalid file extension. Expected one of: ${extensions}`);
20
+ return null;
21
+ }
22
+ if (!fs.existsSync(fileName)) {
23
+ console.error(`File ${fileName} does not exist.`);
24
+ return null;
25
+ }
26
+ try {
27
+ const document = await services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName)));
28
+ await services.shared.workspace.DocumentBuilder.build([document], { validation: true });
29
+ const validationErrors = ((_a = document.diagnostics) !== null && _a !== void 0 ? _a : []).filter(e => e.severity === 1);
30
+ if (validationErrors.length > 0) {
31
+ console.error('Validation errors found:');
32
+ for (const validationError of validationErrors) {
33
+ console.error(`line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`);
34
+ }
35
+ return null;
36
+ }
37
+ return document;
38
+ }
39
+ catch (error) {
40
+ console.error('Error processing document:', error);
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Unified GitHub manager that handles all GitHub operations
46
+ * Combines authentication, repository management, and publishing
47
+ */
48
+ export class GitHubManager {
49
+ constructor(context) {
50
+ this._onDidChangeState = new vscode.EventEmitter();
51
+ this.onDidChangeState = this._onDidChangeState.event;
52
+ this.octokit = null;
53
+ this.currentRepository = null;
54
+ this.authProvider = new GitHubAuthProvider(context);
55
+ this.configManager = new GitHubConfigurationManager();
56
+ }
57
+ // Authentication methods
58
+ async isAuthenticated() {
59
+ return await this.authProvider.isAuthenticated();
60
+ }
61
+ async authenticate() {
62
+ const userInfo = await this.authProvider.authenticateUser();
63
+ if (userInfo) {
64
+ await this.initializeOctokit();
65
+ this._onDidChangeState.fire();
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ async signOut() {
71
+ await this.authProvider.signOut();
72
+ this.octokit = null;
73
+ this.currentRepository = null;
74
+ this._onDidChangeState.fire();
75
+ }
76
+ async getUserInfo() {
77
+ return await this.authProvider.getStoredUserInfo();
78
+ }
79
+ // Configuration methods
80
+ getConfigManager() {
81
+ return this.configManager;
82
+ }
83
+ getAuthProvider() {
84
+ return this.authProvider;
85
+ }
86
+ // Repository methods
87
+ getCurrentRepository() {
88
+ return this.currentRepository;
89
+ }
90
+ async setRepository(repository) {
91
+ this.currentRepository = repository;
92
+ this._onDidChangeState.fire();
93
+ }
94
+ async disconnectRepository() {
95
+ this.currentRepository = null;
96
+ this._onDidChangeState.fire();
97
+ }
98
+ async listRepositories() {
99
+ if (!await this.initializeOctokit())
100
+ return [];
101
+ try {
102
+ const response = await this.octokit.repos.listForAuthenticatedUser({
103
+ type: 'owner',
104
+ sort: 'updated',
105
+ per_page: 50
106
+ });
107
+ return response.data.map((repo) => ({
108
+ id: repo.id,
109
+ name: repo.name,
110
+ full_name: repo.full_name,
111
+ html_url: repo.html_url,
112
+ clone_url: repo.clone_url,
113
+ ssh_url: repo.ssh_url,
114
+ private: repo.private,
115
+ description: repo.description || '',
116
+ topics: repo.topics || []
117
+ }));
118
+ }
119
+ catch (error) {
120
+ console.error('Failed to list repositories:', error);
121
+ return [];
122
+ }
123
+ }
124
+ async createRepository(name, description, isPrivate = false, licenseTemplate) {
125
+ if (!await this.initializeOctokit())
126
+ return undefined;
127
+ try {
128
+ const response = await this.octokit.repos.createForAuthenticatedUser({
129
+ name,
130
+ description,
131
+ private: isPrivate,
132
+ auto_init: false,
133
+ has_issues: true,
134
+ has_projects: false,
135
+ has_wiki: true,
136
+ has_discussions: true,
137
+ license_template: licenseTemplate,
138
+ gitignore_template: 'Node'
139
+ });
140
+ const repository = response.data;
141
+ await this.setRepository(repository);
142
+ return repository;
143
+ }
144
+ catch (error) {
145
+ vscode.window.showErrorMessage(`Failed to create repository: ${error.message}`);
146
+ return undefined;
147
+ }
148
+ }
149
+ /**
150
+ * Add topics to a repository
151
+ */
152
+ async addRepositoryTopics(repository, topics) {
153
+ if (!await this.initializeOctokit())
154
+ return;
155
+ try {
156
+ await this.octokit.repos.replaceAllTopics({
157
+ owner: repository.full_name.split('/')[0],
158
+ repo: repository.name,
159
+ names: topics
160
+ });
161
+ }
162
+ catch (error) {
163
+ console.error('Failed to add topics:', error);
164
+ }
165
+ }
166
+ /**
167
+ * Create or update a file in the repository
168
+ */
169
+ async createFile(repository, path, content, message, branch) {
170
+ if (!await this.initializeOctokit())
171
+ return;
172
+ try {
173
+ const config = this.configManager.getConfig();
174
+ const targetBranch = branch || config.defaultBranch;
175
+ await this.octokit.repos.createOrUpdateFileContents({
176
+ owner: repository.full_name.split('/')[0],
177
+ repo: repository.name,
178
+ path,
179
+ message,
180
+ content: Buffer.from(content).toString('base64'),
181
+ branch: targetBranch
182
+ });
183
+ }
184
+ catch (error) {
185
+ console.error(`Failed to create file ${path}:`, error);
186
+ }
187
+ }
188
+ /**
189
+ * Initialize repository with main branch using Git Data API
190
+ */
191
+ async initializeMainBranch(repository) {
192
+ if (!await this.initializeOctokit())
193
+ return;
194
+ try {
195
+ const config = this.configManager.getConfig();
196
+ const owner = repository.full_name.split('/')[0];
197
+ // Check if repository has existing files on the default branch (usually master)
198
+ let baseTreeSha;
199
+ let parentCommitSha;
200
+ try {
201
+ // Try to get the existing master/main branch to preserve existing files
202
+ const defaultRef = await this.octokit.git.getRef({
203
+ owner,
204
+ repo: repository.name,
205
+ ref: 'heads/master' // GitHub default for new repos with license
206
+ });
207
+ // Get the commit to extract the tree
208
+ const commit = await this.octokit.git.getCommit({
209
+ owner,
210
+ repo: repository.name,
211
+ commit_sha: defaultRef.data.object.sha
212
+ });
213
+ baseTreeSha = commit.data.tree.sha;
214
+ parentCommitSha = defaultRef.data.object.sha;
215
+ console.log('Found existing files on master branch, preserving them...');
216
+ }
217
+ catch (error) {
218
+ // No existing branch or files, we'll create from scratch
219
+ console.log('No existing files found, creating new repository structure...');
220
+ }
221
+ let tree;
222
+ let commitParents = [];
223
+ if (baseTreeSha && parentCommitSha) {
224
+ // Use existing tree as base to preserve LICENSE, .gitignore, etc.
225
+ tree = await this.octokit.git.createTree({
226
+ owner,
227
+ repo: repository.name,
228
+ base_tree: baseTreeSha,
229
+ tree: [{
230
+ path: '.gitkeep',
231
+ mode: '100644',
232
+ type: 'blob',
233
+ content: '# Repository initialized with ISDL\n'
234
+ }]
235
+ });
236
+ commitParents = [parentCommitSha];
237
+ }
238
+ else {
239
+ // Create new tree from scratch
240
+ const blob = await this.octokit.git.createBlob({
241
+ owner,
242
+ repo: repository.name,
243
+ content: Buffer.from('# Repository initialized with ISDL\n').toString('base64'),
244
+ encoding: 'base64'
245
+ });
246
+ tree = await this.octokit.git.createTree({
247
+ owner,
248
+ repo: repository.name,
249
+ tree: [{
250
+ path: '.gitkeep',
251
+ mode: '100644',
252
+ type: 'blob',
253
+ sha: blob.data.sha
254
+ }]
255
+ });
256
+ }
257
+ // Create the initial commit
258
+ const commit = await this.octokit.git.createCommit({
259
+ owner,
260
+ repo: repository.name,
261
+ message: 'Initialize main branch',
262
+ tree: tree.data.sha,
263
+ parents: commitParents
264
+ });
265
+ // Create the main branch reference
266
+ await this.octokit.git.createRef({
267
+ owner,
268
+ repo: repository.name,
269
+ ref: `refs/heads/${config.defaultBranch}`,
270
+ sha: commit.data.sha
271
+ });
272
+ // Now set the default branch
273
+ if (config.defaultBranch !== 'master') {
274
+ await this.updateDefaultBranch(repository, config.defaultBranch);
275
+ }
276
+ }
277
+ catch (error) {
278
+ console.error('Failed to initialize main branch:', error);
279
+ throw error;
280
+ }
281
+ }
282
+ /**
283
+ * Update the default branch of a repository
284
+ */
285
+ async updateDefaultBranch(repository, branchName) {
286
+ if (!await this.initializeOctokit())
287
+ return;
288
+ try {
289
+ const owner = repository.full_name.split('/')[0];
290
+ await this.octokit.repos.update({
291
+ owner,
292
+ repo: repository.name,
293
+ default_branch: branchName
294
+ });
295
+ // Delete master branch if we switched to a different branch
296
+ if (branchName !== 'master') {
297
+ await this.deleteBranch(repository, 'master');
298
+ }
299
+ }
300
+ catch (error) {
301
+ console.error(`Failed to update default branch to ${branchName}:`, error);
302
+ }
303
+ }
304
+ /**
305
+ * Delete a branch from the repository
306
+ */
307
+ async deleteBranch(repository, branchName) {
308
+ if (!await this.initializeOctokit())
309
+ return;
310
+ try {
311
+ const owner = repository.full_name.split('/')[0];
312
+ // Check if branch exists before trying to delete
313
+ try {
314
+ await this.octokit.git.getRef({
315
+ owner,
316
+ repo: repository.name,
317
+ ref: `heads/${branchName}`
318
+ });
319
+ // Branch exists, delete it
320
+ await this.octokit.git.deleteRef({
321
+ owner,
322
+ repo: repository.name,
323
+ ref: `heads/${branchName}`
324
+ });
325
+ console.log(`Successfully deleted ${branchName} branch`);
326
+ }
327
+ catch (error) {
328
+ if (error.status === 404) {
329
+ // Branch doesn't exist, which is fine
330
+ console.log(`Branch ${branchName} doesn't exist, nothing to delete`);
331
+ }
332
+ else {
333
+ throw error;
334
+ }
335
+ }
336
+ }
337
+ catch (error) {
338
+ console.error(`Failed to delete branch ${branchName}:`, error);
339
+ }
340
+ }
341
+ /**
342
+ * Check if files need to be committed by comparing content hashes
343
+ */
344
+ async getFilesNeedingCommit(repository, files, progressCallback) {
345
+ if (!await this.initializeOctokit())
346
+ return files.map(f => (Object.assign(Object.assign({}, f), { hash: '', needsCommit: true })));
347
+ const owner = repository.full_name.split('/')[0];
348
+ const config = this.configManager.getConfig();
349
+ const filesWithStatus = [];
350
+ console.log(`🔍 Checking ${files.length} files for changes...`);
351
+ for (let i = 0; i < files.length; i++) {
352
+ const file = files[i];
353
+ if (progressCallback) {
354
+ const progress = (i / files.length) * 80; // Reserve 20% for final processing
355
+ progressCallback(progress, `Analyzing ${file.path}...`);
356
+ }
357
+ try {
358
+ // Calculate local file hash
359
+ const localHash = crypto.createHash('sha1')
360
+ .update(`blob ${Buffer.byteLength(file.content, 'utf8')}\0${file.content}`)
361
+ .digest('hex');
362
+ let needsCommit = true;
363
+ try {
364
+ // Check if file exists in repository
365
+ const existingFile = await this.octokit.repos.getContent({
366
+ owner,
367
+ repo: repository.name,
368
+ path: file.path,
369
+ ref: config.defaultBranch
370
+ });
371
+ // Compare SHA hashes (GitHub uses Git's blob SHA)
372
+ if ('sha' in existingFile.data && existingFile.data.sha === localHash) {
373
+ needsCommit = false;
374
+ console.log(`✓ ${file.path} - no changes needed`);
375
+ }
376
+ else {
377
+ console.log(`🔄 ${file.path} - content changed`);
378
+ }
379
+ }
380
+ catch (error) {
381
+ if (error.status === 404) {
382
+ console.log(`➕ ${file.path} - new file`);
383
+ }
384
+ else {
385
+ console.log(`⚠️ ${file.path} - cannot check, assuming changed`);
386
+ }
387
+ }
388
+ filesWithStatus.push(Object.assign(Object.assign({}, file), { hash: localHash, needsCommit }));
389
+ }
390
+ catch (error) {
391
+ console.warn(`Failed to process ${file.path}:`, error);
392
+ filesWithStatus.push(Object.assign(Object.assign({}, file), { hash: '', needsCommit: true }));
393
+ }
394
+ }
395
+ if (progressCallback) {
396
+ progressCallback(90, 'Finalizing change analysis...');
397
+ }
398
+ const changedFiles = filesWithStatus.filter(f => f.needsCommit);
399
+ console.log(`📊 Change summary: ${changedFiles.length}/${files.length} files need updating`);
400
+ if (progressCallback) {
401
+ progressCallback(100, `Analysis complete: ${changedFiles.length}/${files.length} files need updating`);
402
+ }
403
+ return filesWithStatus;
404
+ }
405
+ /**
406
+ * Analyze ISDL file changes to determine semantic version bump type
407
+ */
408
+ async analyzeChangeType(files) {
409
+ // Find ISDL file in current files
410
+ const isdlFile = files.find(f => f.path.endsWith('.isdl'));
411
+ if (!isdlFile) {
412
+ console.log('📝 No ISDL file found in changes, defaulting to patch version');
413
+ return {
414
+ changeType: 'patch',
415
+ changes: [{ type: 'modified', category: 'implementation', description: 'System implementation updated' }]
416
+ };
417
+ }
418
+ console.log(`🔍 Analyzing ISDL changes in ${isdlFile.path}...`);
419
+ try {
420
+ // Get previous version of ISDL file from repository
421
+ const previousIsdlContent = await this.getPreviousFileContent(isdlFile.path);
422
+ if (!previousIsdlContent) {
423
+ console.log('📝 No previous ISDL version found, treating as new system (minor)');
424
+ return { changeType: 'minor', changes: [{ type: 'added', category: 'system', description: 'New ISDL system created' }] };
425
+ }
426
+ // Parse both versions and compare
427
+ const changes = await this.compareIsdlVersions(previousIsdlContent, isdlFile.content);
428
+ const changeType = this.determineVersionFromIsdlChanges(changes);
429
+ console.log(`📊 ISDL analysis complete: ${changeType} version bump with ${changes.length} changes`);
430
+ return { changeType, changes };
431
+ }
432
+ catch (error) {
433
+ console.error('❌ Failed to analyze ISDL changes, defaulting to patch version:', error);
434
+ return {
435
+ changeType: 'patch',
436
+ changes: [{ type: 'modified', category: 'implementation', description: 'System updated (ISDL analysis failed)' }]
437
+ };
438
+ }
439
+ }
440
+ /**
441
+ * Upload multiple files to repository as a single commit with efficient change detection
442
+ */
443
+ async uploadFiles(repository, files, commitMessage, progressCallback) {
444
+ if (!await this.initializeOctokit())
445
+ return { success: false, hasChanges: false, changedFiles: 0 };
446
+ if (files.length === 0)
447
+ return { success: true, hasChanges: false, changedFiles: 0 };
448
+ try {
449
+ const owner = repository.full_name.split('/')[0];
450
+ const config = this.configManager.getConfig();
451
+ if (progressCallback) {
452
+ progressCallback(5, 'Analyzing file changes...');
453
+ }
454
+ // Check which files actually need to be committed with progress updates
455
+ const filesWithStatus = await this.getFilesNeedingCommit(repository, files, progressCallback ? (analyzeProgress, message) => {
456
+ // Scale analysis progress to 5-25% of total progress
457
+ const scaledProgress = 5 + (analyzeProgress * 0.2);
458
+ progressCallback(scaledProgress, message);
459
+ } : undefined);
460
+ const filesToCommit = filesWithStatus.filter(f => f.needsCommit);
461
+ if (filesToCommit.length === 0) {
462
+ console.log('✓ No files need updating - all files are already up to date');
463
+ if (progressCallback) {
464
+ progressCallback(100, 'All files up to date!');
465
+ }
466
+ return { success: true, hasChanges: false, changedFiles: 0 };
467
+ }
468
+ console.log(`🚀 Committing ${filesToCommit.length} changed files out of ${files.length} total`);
469
+ if (progressCallback) {
470
+ progressCallback(30, `Preparing to commit ${filesToCommit.length} changed files...`);
471
+ }
472
+ // Get the current default branch reference
473
+ let branchRef;
474
+ try {
475
+ branchRef = await this.octokit.git.getRef({
476
+ owner,
477
+ repo: repository.name,
478
+ ref: `heads/${config.defaultBranch}`
479
+ });
480
+ }
481
+ catch (error) {
482
+ // If branch doesn't exist, initialize it first
483
+ if (error.status === 404) {
484
+ await this.initializeMainBranch(repository);
485
+ branchRef = await this.octokit.git.getRef({
486
+ owner,
487
+ repo: repository.name,
488
+ ref: `heads/${config.defaultBranch}`
489
+ });
490
+ }
491
+ else {
492
+ throw error;
493
+ }
494
+ }
495
+ if (progressCallback) {
496
+ progressCallback(20, 'Creating file blobs...');
497
+ }
498
+ // Create blobs only for files that need committing
499
+ const blobs = [];
500
+ for (let i = 0; i < filesToCommit.length; i++) {
501
+ const file = filesToCommit[i];
502
+ try {
503
+ // Always create blob for files that need committing
504
+ const blob = await this.octokit.git.createBlob({
505
+ owner,
506
+ repo: repository.name,
507
+ content: Buffer.from(file.content).toString('base64'),
508
+ encoding: 'base64'
509
+ });
510
+ blobs.push({
511
+ path: file.path.replace(/\\/g, '/'),
512
+ sha: blob.data.sha
513
+ });
514
+ if (progressCallback) {
515
+ const progress = 35 + ((i + 1) / filesToCommit.length) * 40; // 35-75%
516
+ progressCallback(progress, `Processing ${file.path}...`);
517
+ }
518
+ }
519
+ catch (error) {
520
+ console.error(`Failed to process blob for ${file.path}:`, error);
521
+ }
522
+ }
523
+ if (blobs.length === 0) {
524
+ console.log('✓ No blobs needed - using existing file hashes');
525
+ return { success: true, hasChanges: false, changedFiles: 0 };
526
+ }
527
+ if (progressCallback) {
528
+ progressCallback(75, 'Creating commit tree...');
529
+ }
530
+ // Create a tree with all the blobs
531
+ const treeParams = {
532
+ owner,
533
+ repo: repository.name,
534
+ tree: blobs.map(blob => ({
535
+ path: blob.path,
536
+ mode: '100644',
537
+ type: 'blob',
538
+ sha: blob.sha
539
+ }))
540
+ };
541
+ // Only include base_tree if we have a valid commit SHA (not for empty repositories)
542
+ if (branchRef.data.object.sha && branchRef.data.object.sha !== '0000000000000000000000000000000000000000') {
543
+ // Get the tree SHA from the commit, not the commit SHA itself
544
+ try {
545
+ const commit = await this.octokit.git.getCommit({
546
+ owner,
547
+ repo: repository.name,
548
+ commit_sha: branchRef.data.object.sha
549
+ });
550
+ treeParams.base_tree = commit.data.tree.sha;
551
+ console.log(`🌳 Using base tree: ${commit.data.tree.sha} from commit ${branchRef.data.object.sha}`);
552
+ }
553
+ catch (error) {
554
+ console.warn('Failed to get base tree from commit, creating tree without base:', error);
555
+ // Continue without base_tree - will create a full tree
556
+ }
557
+ }
558
+ console.log(`🌳 Creating tree with ${blobs.length} blobs, base_tree: ${treeParams.base_tree || 'none'}`);
559
+ const tree = await this.octokit.git.createTree(treeParams);
560
+ console.log(`✅ Tree created successfully: ${tree.data.sha}`);
561
+ if (progressCallback) {
562
+ progressCallback(85, 'Creating commit...');
563
+ }
564
+ // Update commit message to reflect actual changes
565
+ const updatedCommitMessage = filesToCommit.length === files.length
566
+ ? commitMessage
567
+ : `${commitMessage} (${filesToCommit.length}/${files.length} files changed)`;
568
+ // Create the commit
569
+ const commitParams = {
570
+ owner,
571
+ repo: repository.name,
572
+ message: updatedCommitMessage,
573
+ tree: tree.data.sha,
574
+ parents: []
575
+ };
576
+ // Only include parent if we have a valid SHA (not for the first commit)
577
+ if (branchRef.data.object.sha && branchRef.data.object.sha !== '0000000000000000000000000000000000000000') {
578
+ commitParams.parents = [branchRef.data.object.sha];
579
+ }
580
+ const commit = await this.octokit.git.createCommit(commitParams);
581
+ if (progressCallback) {
582
+ progressCallback(95, 'Updating branch reference...');
583
+ }
584
+ // Update the branch reference to point to the new commit
585
+ await this.octokit.git.updateRef({
586
+ owner,
587
+ repo: repository.name,
588
+ ref: `heads/${config.defaultBranch}`,
589
+ sha: commit.data.sha
590
+ });
591
+ if (progressCallback) {
592
+ progressCallback(100, 'Upload complete!');
593
+ }
594
+ return { success: true, hasChanges: true, changedFiles: filesToCommit.length };
595
+ }
596
+ catch (error) {
597
+ console.error('Failed to upload files as batch commit:', error);
598
+ return { success: false, hasChanges: false, changedFiles: 0 };
599
+ }
600
+ }
601
+ // Publishing methods
602
+ async updateSystem() {
603
+ if (!this.currentRepository) {
604
+ vscode.window.showErrorMessage('No repository connected. Please select a repository first.');
605
+ return false;
606
+ }
607
+ return await vscode.window.withProgress({
608
+ location: vscode.ProgressLocation.Notification,
609
+ title: 'Updating system files...',
610
+ cancellable: false
611
+ }, async (progress) => {
612
+ try {
613
+ progress.report({ message: 'Selecting ISDL file...', increment: 10 });
614
+ // Select which .isdl file to use
615
+ const selectedFile = await this.selectIsdlFile();
616
+ if (!selectedFile) {
617
+ vscode.window.showWarningMessage('No ISDL file selected. Update cancelled.');
618
+ return false;
619
+ }
620
+ progress.report({ message: 'Collecting system files...', increment: 10 });
621
+ // Get the system files
622
+ const systemFiles = await this.collectSystemFilesForPublish(selectedFile);
623
+ if (systemFiles.length === 0) {
624
+ vscode.window.showWarningMessage('No system files found. Please generate your system first.');
625
+ return false;
626
+ }
627
+ progress.report({ message: 'Ensuring workflow file exists...', increment: 10 });
628
+ // Always ensure workflow file exists
629
+ await this.ensureWorkflowFile(this.currentRepository);
630
+ progress.report({ message: 'Uploading files...', increment: 20 });
631
+ // Upload files without creating a release
632
+ const systemJsonFile = systemFiles.find(f => f.path === 'system.json');
633
+ const systemInfo = systemJsonFile ? JSON.parse(systemJsonFile.content) : null;
634
+ const systemId = (systemInfo === null || systemInfo === void 0 ? void 0 : systemInfo.id) || this.currentRepository.name;
635
+ const uploadResult = await this.uploadFiles(this.currentRepository, systemFiles, `Update ${systemId} system files`, (progressPercent, currentStep) => {
636
+ progress.report({
637
+ message: currentStep,
638
+ increment: Math.min(progressPercent * 0.5, 50) // Use up to 50% of remaining progress
639
+ });
640
+ });
641
+ if (!uploadResult.success) {
642
+ vscode.window.showErrorMessage('Failed to update system files. Check the output for details.');
643
+ return false;
644
+ }
645
+ // Check if there were actually any changes to update
646
+ if (!uploadResult.hasChanges) {
647
+ progress.report({ message: 'No changes detected', increment: 10 });
648
+ const action = await vscode.window.showInformationMessage('No changes detected in your system files. All files are already up to date in the repository.', 'Regenerate System', 'Open Repository', 'Cancel');
649
+ if (action === 'Regenerate System') {
650
+ await vscode.commands.executeCommand('isdl.generate');
651
+ }
652
+ else if (action === 'Open Repository') {
653
+ vscode.env.openExternal(vscode.Uri.parse(this.currentRepository.html_url));
654
+ }
655
+ return false;
656
+ }
657
+ progress.report({ message: 'Update complete!', increment: 10 });
658
+ const repo = this.currentRepository;
659
+ vscode.window.showInformationMessage(`System files updated in ${repo.name} successfully! (${uploadResult.changedFiles} of ${systemFiles.length} files changed)`, 'Open Repository', 'View Commits').then(selection => {
660
+ if (selection === 'Open Repository') {
661
+ vscode.env.openExternal(vscode.Uri.parse(repo.html_url));
662
+ }
663
+ else if (selection === 'View Commits') {
664
+ vscode.env.openExternal(vscode.Uri.parse(`${repo.html_url}/commits`));
665
+ }
666
+ });
667
+ return true;
668
+ }
669
+ catch (error) {
670
+ vscode.window.showErrorMessage(`Failed to update system: ${error.message}`);
671
+ return false;
672
+ }
673
+ });
674
+ }
675
+ async publishSystem() {
676
+ if (!this.currentRepository) {
677
+ vscode.window.showErrorMessage('No repository connected. Please select a repository first.');
678
+ return false;
679
+ }
680
+ return await vscode.window.withProgress({
681
+ location: vscode.ProgressLocation.Notification,
682
+ title: 'Publishing system...',
683
+ cancellable: false
684
+ }, async (progress) => {
685
+ try {
686
+ progress.report({ message: 'Selecting ISDL file...', increment: 5 });
687
+ // Select which .isdl file to use
688
+ const selectedFile = await this.selectIsdlFile();
689
+ if (!selectedFile) {
690
+ vscode.window.showWarningMessage('No ISDL file selected. Publishing cancelled.');
691
+ return false;
692
+ }
693
+ progress.report({ message: 'Collecting system files...', increment: 5 });
694
+ // Get the system files
695
+ const systemFiles = await this.collectSystemFilesForPublish(selectedFile);
696
+ if (systemFiles.length === 0) {
697
+ vscode.window.showWarningMessage('No system files found. Please generate your system first.');
698
+ return false;
699
+ }
700
+ // Get version from system.json for release
701
+ const systemJsonFile = systemFiles.find(f => f.path === 'system.json');
702
+ const systemInfo = systemJsonFile ? JSON.parse(systemJsonFile.content) : null;
703
+ progress.report({ message: 'Ensuring workflow file exists...', increment: 5 });
704
+ // Always ensure workflow file exists
705
+ await this.ensureWorkflowFile(this.currentRepository);
706
+ progress.report({ message: 'Creating GitHub release...', increment: 5 });
707
+ // Create a GitHub release
708
+ const releaseUrl = await this.createRelease(systemInfo, systemFiles, progress);
709
+ // Handle case where no changes were detected
710
+ if (releaseUrl === 'NO_CHANGES') {
711
+ progress.report({ message: 'No changes detected', increment: 10 });
712
+ const action = await vscode.window.showInformationMessage('No changes detected in your system files. All files are already up to date in the repository.', 'Regenerate System', 'Open Repository', 'Cancel');
713
+ if (action === 'Regenerate System') {
714
+ await vscode.commands.executeCommand('isdl.generate');
715
+ }
716
+ else if (action === 'Open Repository') {
717
+ vscode.env.openExternal(vscode.Uri.parse(this.currentRepository.html_url));
718
+ }
719
+ return false;
720
+ }
721
+ progress.report({ message: 'Publish complete!', increment: 10 });
722
+ const repo = this.currentRepository;
723
+ const actions = releaseUrl ?
724
+ ['View Release', 'Open Repository', 'View Commits'] :
725
+ ['Open Repository', 'View Commits'];
726
+ vscode.window.showInformationMessage(`System published to ${repo.name} successfully! (${systemFiles.length} files)`, ...actions).then(selection => {
727
+ if (selection === 'Open Repository') {
728
+ vscode.env.openExternal(vscode.Uri.parse(repo.html_url));
729
+ }
730
+ else if (selection === 'View Release' && releaseUrl) {
731
+ vscode.env.openExternal(vscode.Uri.parse(releaseUrl));
732
+ }
733
+ else if (selection === 'View Commits') {
734
+ vscode.env.openExternal(vscode.Uri.parse(`${repo.html_url}/commits`));
735
+ }
736
+ });
737
+ return true;
738
+ }
739
+ catch (error) {
740
+ vscode.window.showErrorMessage(`Failed to publish system: ${error.message}`);
741
+ return false;
742
+ }
743
+ });
744
+ }
745
+ /**
746
+ * Select .isdl file to use for system generation
747
+ */
748
+ async selectIsdlFile() {
749
+ // Find all .isdl files in the workspace
750
+ const isdlFiles = await vscode.workspace.findFiles('**/*.isdl');
751
+ if (isdlFiles.length === 0) {
752
+ vscode.window.showErrorMessage('No .isdl files found in the workspace.');
753
+ return undefined;
754
+ }
755
+ // If only one file, use it
756
+ if (isdlFiles.length === 1) {
757
+ return isdlFiles[0].fsPath;
758
+ }
759
+ // Multiple files - prompt user to select
760
+ const items = isdlFiles.map(uri => {
761
+ const relativePath = vscode.workspace.asRelativePath(uri);
762
+ return {
763
+ label: path.basename(uri.fsPath),
764
+ description: relativePath,
765
+ detail: uri.fsPath,
766
+ uri: uri
767
+ };
768
+ });
769
+ const selection = await vscode.window.showQuickPick(items, {
770
+ title: 'Select ISDL File',
771
+ placeHolder: 'Choose which .isdl file to use for publishing',
772
+ ignoreFocusOut: true
773
+ });
774
+ if (!selection) {
775
+ return undefined;
776
+ }
777
+ return selection.uri.fsPath;
778
+ }
779
+ /**
780
+ * Collect system files for publishing
781
+ */
782
+ async collectSystemFilesForPublish(isdlFilePath) {
783
+ var _a;
784
+ try {
785
+ // Get the configuration to find the last selected folder
786
+ const config = vscode.workspace.getConfiguration('isdl');
787
+ const lastSelectedFolder = config.get('lastSelectedFolder');
788
+ if (!lastSelectedFolder || !fs.existsSync(lastSelectedFolder)) {
789
+ vscode.window.showErrorMessage('No generated system files found. Please generate your system first.');
790
+ return [];
791
+ }
792
+ // Parse the ISDL file to get the system ID
793
+ const services = createIntelligentSystemDesignLanguageServices(NodeFileSystem).IntelligentSystemDesignLanguage;
794
+ const model = await this.extractAstNodeSafe(isdlFilePath, services);
795
+ if (!model) {
796
+ vscode.window.showErrorMessage('Failed to parse the selected ISDL file.');
797
+ return [];
798
+ }
799
+ const id = (_a = model.config.body.find(x => isConfigExpression(x) && x.type === "id")) === null || _a === void 0 ? void 0 : _a.value;
800
+ if (!id) {
801
+ vscode.window.showErrorMessage('Could not find system ID in the selected ISDL file.');
802
+ return [];
803
+ }
804
+ const systemFolder = path.join(lastSelectedFolder, id);
805
+ if (!fs.existsSync(systemFolder)) {
806
+ vscode.window.showErrorMessage(`System folder not found: ${systemFolder}. Please generate your system first.`);
807
+ return [];
808
+ }
809
+ // Collect all files from the system folder
810
+ const files = await this.collectFilesRecursively(systemFolder, systemFolder, []);
811
+ // Add the source ISDL file to the repository
812
+ try {
813
+ const isdlContent = fs.readFileSync(isdlFilePath, 'utf8');
814
+ const isdlFileName = path.basename(isdlFilePath);
815
+ files.push({
816
+ path: isdlFileName,
817
+ content: isdlContent
818
+ });
819
+ console.log(`📝 Added source ISDL file: ${isdlFileName}`);
820
+ }
821
+ catch (error) {
822
+ console.warn(`Failed to read ISDL file ${isdlFilePath}:`, error);
823
+ }
824
+ return files;
825
+ }
826
+ catch (error) {
827
+ console.error('Error collecting system files for publish:', error);
828
+ vscode.window.showErrorMessage(`Failed to collect system files: ${error.message}`);
829
+ return [];
830
+ }
831
+ }
832
+ /**
833
+ * Recursively collect files from directory
834
+ */
835
+ async collectFilesRecursively(currentPath, basePath, files) {
836
+ const items = fs.readdirSync(currentPath, { withFileTypes: true });
837
+ for (const item of items) {
838
+ const fullPath = path.join(currentPath, item.name);
839
+ if (item.isDirectory()) {
840
+ // Skip common directories that shouldn't be uploaded
841
+ if (this.shouldSkipDirectory(item.name)) {
842
+ continue;
843
+ }
844
+ // Recursively process subdirectories
845
+ await this.collectFilesRecursively(fullPath, basePath, files);
846
+ }
847
+ else if (item.isFile()) {
848
+ // Skip files that shouldn't be uploaded
849
+ if (this.shouldSkipFile(item.name)) {
850
+ continue;
851
+ }
852
+ try {
853
+ const content = fs.readFileSync(fullPath, 'utf8');
854
+ const relativePath = path.relative(basePath, fullPath).replace(/\\/g, '/');
855
+ files.push({
856
+ path: relativePath,
857
+ content: content
858
+ });
859
+ }
860
+ catch (error) {
861
+ console.warn(`Failed to read file ${fullPath}:`, error);
862
+ }
863
+ }
864
+ }
865
+ return files;
866
+ }
867
+ /**
868
+ * Determine if a directory should be skipped
869
+ */
870
+ shouldSkipDirectory(dirName) {
871
+ const skipDirs = [
872
+ 'node_modules',
873
+ '.git',
874
+ '.vscode',
875
+ 'dist',
876
+ 'build',
877
+ 'out',
878
+ '.next',
879
+ '.cache',
880
+ 'coverage',
881
+ '.nyc_output',
882
+ 'logs'
883
+ ];
884
+ return skipDirs.includes(dirName) || !!dirName.match(/.*\.log$/);
885
+ }
886
+ /**
887
+ * Determine if a file should be skipped
888
+ */
889
+ shouldSkipFile(fileName) {
890
+ const skipFiles = [
891
+ '.DS_Store',
892
+ 'Thumbs.db',
893
+ '.env',
894
+ '.env.local',
895
+ '.env.production'
896
+ ];
897
+ return skipFiles.includes(fileName) ||
898
+ !!fileName.match(/.*\.(log|tmp|temp|key|pem|p12|pfx)$/);
899
+ }
900
+ /**
901
+ * Get the GitHub workflow content for system releases
902
+ */
903
+ async getWorkflowContent() {
904
+ try {
905
+ const workflowPath = path.join(__dirname, '/github/system-workflow.yml');
906
+ return fs.readFileSync(workflowPath, 'utf8');
907
+ }
908
+ catch (error) {
909
+ // Fallback to inline workflow content if file is not found
910
+ console.warn('Workflow file not found');
911
+ throw error;
912
+ }
913
+ }
914
+ /**
915
+ * Create a GitHub release for the system
916
+ */
917
+ async createRelease(systemInfo, systemFiles, progress) {
918
+ var _a;
919
+ if (!await this.initializeOctokit() || !this.currentRepository)
920
+ return undefined;
921
+ try {
922
+ console.log('🚀 Starting release creation...');
923
+ const owner = this.currentRepository.full_name.split('/')[0];
924
+ const repo = this.currentRepository.name;
925
+ console.log(`📁 Repository: ${owner}/${repo}`);
926
+ console.log('📊 System info:', JSON.stringify(systemInfo, null, 2));
927
+ // Analyze changes and determine version bump type if files provided
928
+ const changeAnalysis = systemFiles ? await this.analyzeChangeType(systemFiles) : { changeType: 'patch', changes: [] };
929
+ console.log(`🔍 Detected change type: ${changeAnalysis.changeType}`);
930
+ // Generate version number based on change analysis
931
+ const rawVersion = await this.generateVersionNumber(changeAnalysis.changeType);
932
+ console.log(`📝 Raw version: "${rawVersion}"`);
933
+ // Sanitize and validate version
934
+ const version = this.sanitizeVersion(rawVersion);
935
+ const tagName = `v${version}`;
936
+ console.log(`🏷️ Sanitized version: "${version}"`);
937
+ console.log(`🏷️ Tag name: "${tagName}"`);
938
+ // Validate tag name
939
+ if (!this.isValidTagName(tagName)) {
940
+ throw new Error(`Invalid tag name: "${tagName}". Tag names must contain only letters, numbers, periods, hyphens, and underscores.`);
941
+ }
942
+ // Generate release notes with ISDL changes
943
+ console.log('📄 Generating release notes...');
944
+ const releaseNotes = await this.generateReleaseNotes(systemInfo, changeAnalysis.changes, tagName);
945
+ console.log(`📄 Release notes length: ${releaseNotes.length} characters`);
946
+ progress.report({ message: 'Analyzing and uploading files to repository...', increment: 5 });
947
+ // Upload files with progress tracking including analysis phase
948
+ let lastProgress = 25;
949
+ const uploadResult = await this.uploadFiles(this.currentRepository, systemFiles, `Update system files (${systemFiles.length} files)`, (progressPercent, currentStep) => {
950
+ const increment = (progressPercent * 0.55) - (lastProgress - 25); // 55% of remaining progress
951
+ lastProgress = 25 + (progressPercent * 0.55);
952
+ progress.report({
953
+ message: currentStep,
954
+ increment: increment
955
+ });
956
+ });
957
+ if (!uploadResult.success) {
958
+ vscode.window.showErrorMessage('Failed to upload system files. Check the output for details.');
959
+ return;
960
+ }
961
+ // Check if there were actually any changes to publish
962
+ if (!uploadResult.hasChanges) {
963
+ console.log('📋 No changes detected, skipping release creation');
964
+ return 'NO_CHANGES';
965
+ }
966
+ // Create the release
967
+ console.log('🎯 Creating GitHub release...');
968
+ const releaseData = {
969
+ owner,
970
+ repo,
971
+ tag_name: tagName,
972
+ name: `${(systemInfo === null || systemInfo === void 0 ? void 0 : systemInfo.title) || repo} v${version}`,
973
+ body: releaseNotes,
974
+ draft: false,
975
+ prerelease: false,
976
+ make_latest: "true",
977
+ generate_release_notes: false, // We provide our own notes
978
+ };
979
+ console.log('📋 Release data:', JSON.stringify(releaseData, null, 2));
980
+ const response = await this.octokit.repos.createRelease(releaseData);
981
+ console.log('✅ Release created successfully!');
982
+ console.log(`🔗 Release URL: ${response.data.html_url}`);
983
+ return response.data.html_url;
984
+ }
985
+ catch (error) {
986
+ console.error('❌ Failed to create release:', error);
987
+ console.error('❌ Error details:', {
988
+ status: error.status,
989
+ message: error.message,
990
+ response: (_a = error.response) === null || _a === void 0 ? void 0 : _a.data
991
+ });
992
+ // Check if it's a duplicate tag error
993
+ if (error.status === 422 && error.message.includes('tag_name already exists')) {
994
+ vscode.window.showWarningMessage('A release with this version already exists. The files have been updated but no new release was created.', 'View Releases').then(action => {
995
+ if (action === 'View Releases') {
996
+ vscode.env.openExternal(vscode.Uri.parse(`${this.currentRepository.html_url}/releases`));
997
+ }
998
+ });
999
+ }
1000
+ else {
1001
+ vscode.window.showWarningMessage(`Could not create release: ${error.message}`);
1002
+ }
1003
+ return undefined;
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Sanitize version string to ensure it's valid for Git tags
1008
+ */
1009
+ sanitizeVersion(version) {
1010
+ if (!version || typeof version !== 'string') {
1011
+ console.warn('⚠️ Invalid version input, using default 1.0.0');
1012
+ return '1.0.0';
1013
+ }
1014
+ // Remove any 'v' prefix if present
1015
+ let sanitized = version.replace(/^v/, '');
1016
+ // Replace invalid characters with valid ones
1017
+ sanitized = sanitized.replace(/[^a-zA-Z0-9.-]/g, '');
1018
+ // Ensure it starts with a number
1019
+ if (!/^\d/.test(sanitized)) {
1020
+ sanitized = '1.0.0';
1021
+ }
1022
+ // If empty after sanitization, use default
1023
+ if (!sanitized) {
1024
+ sanitized = '1.0.0';
1025
+ }
1026
+ // Ensure it's a valid semver-like format
1027
+ const parts = sanitized.split('.');
1028
+ while (parts.length < 3) {
1029
+ parts.push('0');
1030
+ }
1031
+ // Take only first 3 parts and ensure they're numbers
1032
+ const validParts = parts.slice(0, 3).map(part => {
1033
+ const num = parseInt(part) || 0;
1034
+ return num.toString();
1035
+ });
1036
+ return validParts.join('.');
1037
+ }
1038
+ /**
1039
+ * Validate that a tag name is acceptable to GitHub
1040
+ */
1041
+ isValidTagName(tagName) {
1042
+ // GitHub tag name requirements:
1043
+ // - Cannot be empty
1044
+ // - Cannot contain ASCII control characters
1045
+ // - Cannot contain spaces
1046
+ // - Cannot contain: ~ ^ : ? * [ ]
1047
+ // - Cannot start or end with /
1048
+ // - Cannot contain consecutive /
1049
+ // - Cannot end with .lock
1050
+ if (!tagName || tagName.length === 0) {
1051
+ return false;
1052
+ }
1053
+ // Check for invalid characters
1054
+ const invalidChars = /[\s~^:?*[\]]/;
1055
+ if (invalidChars.test(tagName)) {
1056
+ return false;
1057
+ }
1058
+ // Check for forward slash issues
1059
+ if (tagName.startsWith('/') || tagName.endsWith('/') || tagName.includes('//')) {
1060
+ return false;
1061
+ }
1062
+ // Check for .lock ending
1063
+ if (tagName.endsWith('.lock')) {
1064
+ return false;
1065
+ }
1066
+ // Check for ASCII control characters (0-31, 127)
1067
+ for (let i = 0; i < tagName.length; i++) {
1068
+ const charCode = tagName.charCodeAt(i);
1069
+ if (charCode <= 31 || charCode === 127) {
1070
+ return false;
1071
+ }
1072
+ }
1073
+ return true;
1074
+ }
1075
+ /**
1076
+ * Generate a semantic version number based on existing releases and change analysis
1077
+ */
1078
+ async generateVersionNumber(changeType = 'patch') {
1079
+ if (!await this.initializeOctokit() || !this.currentRepository) {
1080
+ console.log('📝 No GitHub connection, using default version 1.0.0');
1081
+ return '1.0.0';
1082
+ }
1083
+ try {
1084
+ console.log('🔍 Fetching existing tags to determine next version...');
1085
+ const owner = this.currentRepository.full_name.split('/')[0];
1086
+ const repo = this.currentRepository.name;
1087
+ // Get all tags instead of just releases to catch all versions
1088
+ const tags = await this.octokit.repos.listTags({
1089
+ owner,
1090
+ repo,
1091
+ per_page: 100
1092
+ });
1093
+ console.log(`📊 Found ${tags.data.length} existing tags`);
1094
+ if (tags.data.length === 0) {
1095
+ console.log('📝 No existing tags, using version 1.0.0');
1096
+ return '1.0.0';
1097
+ }
1098
+ // Parse all versions and find the highest semantic version
1099
+ const versions = tags.data
1100
+ .map(tag => {
1101
+ const version = tag.name.replace(/^v/, '');
1102
+ const parts = version.split('.').map(n => parseInt(n) || 0);
1103
+ // Ensure we have major.minor.patch format
1104
+ while (parts.length < 3)
1105
+ parts.push(0);
1106
+ return {
1107
+ original: tag.name,
1108
+ version: version,
1109
+ major: parts[0],
1110
+ minor: parts[1],
1111
+ patch: parts[2],
1112
+ numeric: parts[0] * 10000 + parts[1] * 100 + parts[2]
1113
+ };
1114
+ })
1115
+ .filter(v => !isNaN(v.major) && !isNaN(v.minor) && !isNaN(v.patch))
1116
+ .sort((a, b) => b.numeric - a.numeric);
1117
+ if (versions.length === 0) {
1118
+ console.log('📝 No valid semantic versions found, using version 1.0.0');
1119
+ return '1.0.0';
1120
+ }
1121
+ const latest = versions[0];
1122
+ console.log(`🏷️ Latest semantic version: "${latest.version}" (${latest.original})`);
1123
+ // Increment based on change type
1124
+ let newMajor = latest.major;
1125
+ let newMinor = latest.minor;
1126
+ let newPatch = latest.patch;
1127
+ switch (changeType) {
1128
+ case 'major':
1129
+ newMajor += 1;
1130
+ newMinor = 0;
1131
+ newPatch = 0;
1132
+ break;
1133
+ case 'minor':
1134
+ newMinor += 1;
1135
+ newPatch = 0;
1136
+ break;
1137
+ case 'patch':
1138
+ default:
1139
+ newPatch += 1;
1140
+ break;
1141
+ }
1142
+ const newVersion = `${newMajor}.${newMinor}.${newPatch}`;
1143
+ console.log(`📝 Generated new ${changeType} version: "${newVersion}"`);
1144
+ return newVersion;
1145
+ }
1146
+ catch (error) {
1147
+ console.error('❌ Failed to generate version number:', error);
1148
+ console.log('📝 Falling back to default version 1.0.0');
1149
+ return '1.0.0';
1150
+ }
1151
+ }
1152
+ /**
1153
+ * Generate release notes for the system with ISDL change analysis
1154
+ */
1155
+ async generateReleaseNotes(systemInfo, changes = [], tagName) {
1156
+ var _a, _b;
1157
+ const currentDate = new Date().toISOString().split('T')[0];
1158
+ const systemName = (systemInfo === null || systemInfo === void 0 ? void 0 : systemInfo.title) || (systemInfo === null || systemInfo === void 0 ? void 0 : systemInfo.id) || 'ISDL System';
1159
+ // Generate changelog section from ISDL changes
1160
+ const changelogSection = this.generateChangelogSection(changes);
1161
+ return `## ${systemName} Release
1162
+
1163
+ 📅 **Release Date:** ${currentDate}
1164
+ 🎲 **Foundry VTT Compatibility:** v${((_a = systemInfo === null || systemInfo === void 0 ? void 0 : systemInfo.compatibility) === null || _a === void 0 ? void 0 : _a.minimum) || '12'} - v${((_b = systemInfo === null || systemInfo === void 0 ? void 0 : systemInfo.compatibility) === null || _b === void 0 ? void 0 : _b.verified) || '13'}
1165
+
1166
+ ### 📦 Installation
1167
+
1168
+ **Manifest URL:**
1169
+ \`\`\`
1170
+ https://github.com/${this.currentRepository.full_name}/releases/download/${tagName}/system.json
1171
+ \`\`\`
1172
+
1173
+ ${changelogSection}
1174
+
1175
+ ### 📖 Documentation
1176
+
1177
+ For installation instructions, usage guides, and troubleshooting:
1178
+ - 📚 [Repository README](https://github.com/${this.currentRepository.full_name}#readme)
1179
+ - 🐛 [Report Issues](https://github.com/${this.currentRepository.full_name}/issues)
1180
+ - 💬 [Community Discussions](https://github.com/${this.currentRepository.full_name}/discussions)
1181
+
1182
+ ### ⚡ Quick Start
1183
+
1184
+ 1. Copy the manifest URL above
1185
+ 2. Open Foundry VTT
1186
+ 3. Go to "Game Systems" → "Install System"
1187
+ 4. Paste the manifest URL and click "Install"
1188
+ 5. Create a new world using this system
1189
+
1190
+ ---
1191
+
1192
+ *Built with ❤️ using [ISDL](https://marketplace.visualstudio.com/items?itemName=IronMooseDevelopment.isdl)*`;
1193
+ }
1194
+ /**
1195
+ * Generate changelog section from ISDL changes
1196
+ */
1197
+ generateChangelogSection(changes) {
1198
+ if (changes.length === 0) {
1199
+ return `### 🚀 What's New
1200
+
1201
+ - 🛠️ System implementation updated
1202
+ - 🔧 Bug fixes and performance improvements
1203
+ - 📱 Enhanced user interface`;
1204
+ }
1205
+ // Group changes by type
1206
+ const addedChanges = changes.filter(c => c.type === 'added');
1207
+ const removedChanges = changes.filter(c => c.type === 'removed');
1208
+ const modifiedChanges = changes.filter(c => c.type === 'modified');
1209
+ let changelog = '### 🚀 What\'s New\n\n';
1210
+ // Breaking changes first (removed/renamed)
1211
+ if (removedChanges.length > 0) {
1212
+ changelog += '#### ⚠️ Breaking Changes\n\n';
1213
+ for (const change of removedChanges) {
1214
+ changelog += `- **REMOVED:** ${change.description}\n`;
1215
+ if (change.details) {
1216
+ changelog += ` - ${change.details}\n`;
1217
+ }
1218
+ }
1219
+ changelog += '\n';
1220
+ }
1221
+ // New features (added fields/actions)
1222
+ if (addedChanges.length > 0) {
1223
+ const fieldAdditions = addedChanges.filter(c => c.category === 'field');
1224
+ const actionAdditions = addedChanges.filter(c => c.category === 'action');
1225
+ const systemAdditions = addedChanges.filter(c => c.category === 'system');
1226
+ if (fieldAdditions.length > 0 || actionAdditions.length > 0 || systemAdditions.length > 0) {
1227
+ changelog += '#### ✨ New Features\n\n';
1228
+ for (const change of [...systemAdditions, ...fieldAdditions, ...actionAdditions]) {
1229
+ const icon = change.category === 'action' ? '⚡' :
1230
+ change.category === 'field' ? '📝' : '🔧';
1231
+ changelog += `- ${icon} ${change.description}\n`;
1232
+ if (change.details) {
1233
+ changelog += ` - ${change.details}\n`;
1234
+ }
1235
+ }
1236
+ changelog += '\n';
1237
+ }
1238
+ }
1239
+ // Improvements and bug fixes (modified)
1240
+ if (modifiedChanges.length > 0) {
1241
+ changelog += '#### 🔧 Improvements\n\n';
1242
+ for (const change of modifiedChanges) {
1243
+ const icon = change.category === 'field' ? '📝' :
1244
+ change.category === 'action' ? '⚡' :
1245
+ change.category === 'system' ? '🔧' : '🛠️';
1246
+ changelog += `- ${icon} ${change.description}\n`;
1247
+ if (change.details) {
1248
+ changelog += ` - ${change.details}\n`;
1249
+ }
1250
+ }
1251
+ }
1252
+ return changelog;
1253
+ }
1254
+ /**
1255
+ * Extension-safe AST node extraction that doesn't call process.exit()
1256
+ */
1257
+ async extractAstNodeSafe(fileName, services) {
1258
+ var _a;
1259
+ const document = await extractDocumentSafe(fileName, services);
1260
+ if (!document) {
1261
+ return null;
1262
+ }
1263
+ return ((_a = document.parseResult) === null || _a === void 0 ? void 0 : _a.value) || null;
1264
+ }
1265
+ /**
1266
+ * Ensure GitHub workflow file exists in the repository and update it if content differs
1267
+ */
1268
+ async ensureWorkflowFile(repository) {
1269
+ var _a;
1270
+ if (!await this.initializeOctokit())
1271
+ return false;
1272
+ try {
1273
+ console.log('🔧 Starting workflow file check/update...');
1274
+ const owner = repository.full_name.split('/')[0];
1275
+ const repo = repository.name;
1276
+ console.log(`📁 Repository: ${owner}/${repo}`);
1277
+ // Get current workflow content that we want
1278
+ console.log('📄 Getting desired workflow content...');
1279
+ const desiredWorkflowContent = await this.getWorkflowContent();
1280
+ console.log(`📄 Desired workflow content length: ${desiredWorkflowContent.length} characters`);
1281
+ // Check if workflow file already exists and get its content
1282
+ let existingContent = null;
1283
+ let existingSha;
1284
+ try {
1285
+ console.log('🔍 Checking existing workflow file...');
1286
+ const existingFile = await this.octokit.repos.getContent({
1287
+ owner,
1288
+ repo,
1289
+ path: '.github/workflows/main.yml'
1290
+ });
1291
+ if ('content' in existingFile.data && existingFile.data.content) {
1292
+ existingContent = Buffer.from(existingFile.data.content, 'base64').toString('utf8');
1293
+ existingSha = existingFile.data.sha;
1294
+ console.log(`📄 Existing workflow content length: ${existingContent.length} characters`);
1295
+ }
1296
+ }
1297
+ catch (error) {
1298
+ if (error.status === 404) {
1299
+ console.log('📝 Workflow file does not exist, will create it');
1300
+ }
1301
+ else {
1302
+ console.error('❌ Error checking for existing workflow:', error);
1303
+ throw error;
1304
+ }
1305
+ }
1306
+ // Compare content and decide if update is needed
1307
+ let needsUpdate = false;
1308
+ let commitMessage = '';
1309
+ if (existingContent === null) {
1310
+ console.log('📝 No existing workflow file, creating new one');
1311
+ needsUpdate = true;
1312
+ commitMessage = 'Add GitHub workflow for automated system releases';
1313
+ }
1314
+ else if (existingContent.trim() !== desiredWorkflowContent.trim()) {
1315
+ console.log('🔄 Workflow content differs, updating...');
1316
+ needsUpdate = true;
1317
+ commitMessage = 'Update GitHub workflow for automated system releases';
1318
+ }
1319
+ else {
1320
+ console.log('✅ Workflow file is already up to date');
1321
+ return true;
1322
+ }
1323
+ if (needsUpdate) {
1324
+ // Create or update the workflow file
1325
+ console.log('🚀 Updating workflow file via GitHub API...');
1326
+ const updateParams = {
1327
+ owner,
1328
+ repo,
1329
+ path: '.github/workflows/main.yml',
1330
+ message: commitMessage,
1331
+ content: Buffer.from(desiredWorkflowContent).toString('base64'),
1332
+ committer: {
1333
+ name: 'ISDL Extension',
1334
+ email: 'noreply@isdl.dev'
1335
+ },
1336
+ branch: 'main'
1337
+ };
1338
+ // Include SHA if updating existing file
1339
+ if (existingSha) {
1340
+ updateParams.sha = existingSha;
1341
+ }
1342
+ const result = await this.octokit.repos.createOrUpdateFileContents(updateParams);
1343
+ console.log('✅ Workflow file updated successfully');
1344
+ console.log(`📊 Commit SHA: ${result.data.commit.sha}`);
1345
+ }
1346
+ return true;
1347
+ }
1348
+ catch (error) {
1349
+ console.error('❌ Failed to ensure workflow file:', error);
1350
+ console.error('❌ Error details:', {
1351
+ status: error.status,
1352
+ message: error.message,
1353
+ response: (_a = error.response) === null || _a === void 0 ? void 0 : _a.data
1354
+ });
1355
+ vscode.window.showWarningMessage(`Could not create/update GitHub workflow file: ${error.message}. The system files were published but automated releases may not work.`, 'View Documentation').then(action => {
1356
+ if (action === 'View Documentation') {
1357
+ vscode.env.openExternal(vscode.Uri.parse('https://docs.github.com/en/actions/quickstart'));
1358
+ }
1359
+ });
1360
+ return false;
1361
+ }
1362
+ }
1363
+ // Private helper methods
1364
+ async initializeOctokit() {
1365
+ const session = await this.authProvider.getCurrentSession();
1366
+ if (!session) {
1367
+ vscode.window.showErrorMessage('GitHub authentication required.');
1368
+ return false;
1369
+ }
1370
+ this.octokit = new Octokit({
1371
+ auth: session.accessToken,
1372
+ userAgent: 'ISDL-VSCode-Extension'
1373
+ });
1374
+ return true;
1375
+ }
1376
+ /**
1377
+ * Get the latest semantic version tag from the repository
1378
+ */
1379
+ async getLatestSemanticTag() {
1380
+ if (!await this.initializeOctokit() || !this.currentRepository)
1381
+ return null;
1382
+ try {
1383
+ const owner = this.currentRepository.full_name.split('/')[0];
1384
+ const repo = this.currentRepository.name;
1385
+ // Get all tags
1386
+ const tags = await this.octokit.repos.listTags({
1387
+ owner,
1388
+ repo,
1389
+ per_page: 100
1390
+ });
1391
+ if (tags.data.length === 0) {
1392
+ return null;
1393
+ }
1394
+ // Parse all versions and find the highest semantic version
1395
+ const versions = tags.data
1396
+ .map(tag => {
1397
+ const version = tag.name.replace(/^v/, '');
1398
+ const parts = version.split('.');
1399
+ // Must have at least 2 parts and all parts must be valid numbers
1400
+ if (parts.length < 2)
1401
+ return null;
1402
+ const numericParts = parts.map(part => {
1403
+ const num = parseInt(part, 10);
1404
+ return isNaN(num) ? null : num;
1405
+ });
1406
+ // Check if any part failed to parse
1407
+ if (numericParts.some(part => part === null))
1408
+ return null;
1409
+ // Ensure we have major.minor.patch format
1410
+ while (numericParts.length < 3)
1411
+ numericParts.push(0);
1412
+ return {
1413
+ original: tag.name,
1414
+ version: version,
1415
+ major: numericParts[0],
1416
+ minor: numericParts[1],
1417
+ patch: numericParts[2],
1418
+ numeric: numericParts[0] * 10000 + numericParts[1] * 100 + numericParts[2]
1419
+ };
1420
+ })
1421
+ .filter((v) => v !== null)
1422
+ .sort((a, b) => b.numeric - a.numeric);
1423
+ if (versions.length === 0) {
1424
+ return null;
1425
+ }
1426
+ const latestVersion = versions[0];
1427
+ console.log(`🏷️ Latest semantic version: "${latestVersion.version}" (${latestVersion.original})`);
1428
+ return latestVersion.original;
1429
+ }
1430
+ catch (error) {
1431
+ console.warn('Failed to get latest semantic tag:', error);
1432
+ return null;
1433
+ }
1434
+ }
1435
+ /**
1436
+ * Get previous version of a file from the repository
1437
+ */
1438
+ async getPreviousFileContent(filePath) {
1439
+ if (!await this.initializeOctokit() || !this.currentRepository)
1440
+ return null;
1441
+ try {
1442
+ const owner = this.currentRepository.full_name.split('/')[0];
1443
+ // Get the latest tag to compare against
1444
+ const latestTag = await this.getLatestSemanticTag();
1445
+ if (!latestTag) {
1446
+ console.log(`📝 No previous tags found, treating as new system`);
1447
+ return null;
1448
+ }
1449
+ console.log(`🏷️ Comparing against last release: ${latestTag}`);
1450
+ const response = await this.octokit.repos.getContent({
1451
+ owner,
1452
+ repo: this.currentRepository.name,
1453
+ path: filePath,
1454
+ ref: latestTag // Use the latest tag instead of default branch
1455
+ });
1456
+ if ('content' in response.data && response.data.content) {
1457
+ return Buffer.from(response.data.content, 'base64').toString('utf8');
1458
+ }
1459
+ }
1460
+ catch (error) {
1461
+ if (error.status === 404) {
1462
+ console.log(`📝 File ${filePath} not found in last release (${await this.getLatestSemanticTag()})`);
1463
+ }
1464
+ else {
1465
+ console.warn(`Failed to get previous version of ${filePath}:`, error);
1466
+ }
1467
+ }
1468
+ return null;
1469
+ }
1470
+ /**
1471
+ * Compare two ISDL file versions and identify changes
1472
+ */
1473
+ async compareIsdlVersions(previousContent, currentContent) {
1474
+ const changes = [];
1475
+ try {
1476
+ // Parse both versions using the language services
1477
+ const services = createIntelligentSystemDesignLanguageServices(NodeFileSystem).IntelligentSystemDesignLanguage;
1478
+ const previousAst = await this.parseIsdlContent("previous", previousContent, services);
1479
+ const currentAst = await this.parseIsdlContent("current", currentContent, services);
1480
+ if (!previousAst || !currentAst) {
1481
+ changes.push({ type: 'modified', category: 'system', description: 'ISDL file structure changed (parsing failed)' });
1482
+ return changes;
1483
+ }
1484
+ // Extract field information from both versions
1485
+ const previousFields = this.extractIsdlFields(previousAst);
1486
+ const currentFields = this.extractIsdlFields(currentAst);
1487
+ // Compare fields to find changes
1488
+ changes.push(...this.compareIsdlFields(previousFields, currentFields));
1489
+ // Check for system-level changes (config)
1490
+ changes.push(...this.compareIsdlConfig(previousAst, currentAst));
1491
+ }
1492
+ catch (error) {
1493
+ console.error('Failed to compare ISDL versions:', error);
1494
+ changes.push({ type: 'modified', category: 'system', description: 'ISDL file changed (comparison failed)' });
1495
+ }
1496
+ return changes;
1497
+ }
1498
+ /**
1499
+ * Parse ISDL content into AST
1500
+ */
1501
+ async parseIsdlContent(name, content, services) {
1502
+ var _a;
1503
+ try {
1504
+ // Create a temporary document for parsing
1505
+ const tempDocument = services.shared.workspace.LangiumDocuments.createDocument(URI.file(`/${name}-temp.isdl`), content);
1506
+ await services.shared.workspace.DocumentBuilder.build([tempDocument], { validation: false });
1507
+ return ((_a = tempDocument.parseResult) === null || _a === void 0 ? void 0 : _a.value) || null;
1508
+ }
1509
+ catch (error) {
1510
+ console.error('Failed to parse ISDL content:', error);
1511
+ return null;
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Extract field and action information from ISDL AST
1516
+ */
1517
+ extractIsdlFields(ast) {
1518
+ const fields = [];
1519
+ try {
1520
+ // Process actors and items
1521
+ for (const document of ast.documents || []) {
1522
+ const docType = document.$type;
1523
+ const docName = document.name;
1524
+ if (docType === 'Actor' || docType === 'Item') {
1525
+ this.extractFieldsFromDocument(document, docName, fields);
1526
+ }
1527
+ }
1528
+ }
1529
+ catch (error) {
1530
+ console.error('Failed to extract ISDL fields:', error);
1531
+ }
1532
+ return fields;
1533
+ }
1534
+ /**
1535
+ * Recursively extract fields from a document or layout block
1536
+ */
1537
+ extractFieldsFromDocument(node, location, fields) {
1538
+ if (!node || !node.body)
1539
+ return;
1540
+ for (const item of node.body) {
1541
+ const itemType = item.$type;
1542
+ // Handle properties (fields)
1543
+ if (this.isPropertyType(itemType)) {
1544
+ const fieldInfo = {
1545
+ name: item.name,
1546
+ type: itemType,
1547
+ category: 'field',
1548
+ location: location,
1549
+ modifiers: item.modifier ? [item.modifier] : [],
1550
+ parameters: this.extractParameters(item)
1551
+ };
1552
+ fields.push(fieldInfo);
1553
+ }
1554
+ // Handle actions
1555
+ else if (itemType === 'Action') {
1556
+ const actionInfo = {
1557
+ name: item.name,
1558
+ type: 'Action',
1559
+ category: 'action',
1560
+ location: location,
1561
+ modifiers: [
1562
+ ...(item.isQuick ? ['quick'] : []),
1563
+ ...(item.isMacro ? ['macro'] : []),
1564
+ ...(item.modifier ? [item.modifier] : [])
1565
+ ],
1566
+ parameters: this.extractParameters(item)
1567
+ };
1568
+ fields.push(actionInfo);
1569
+ }
1570
+ // Handle layout elements (sections, rows, columns, etc.)
1571
+ else if (['Section', 'Row', 'Column', 'Page', 'Tab'].includes(itemType)) {
1572
+ const layoutLocation = item.name ? `${location} - ${item.name}` : location;
1573
+ this.extractFieldsFromDocument(item, layoutLocation, fields);
1574
+ }
1575
+ }
1576
+ }
1577
+ /**
1578
+ * Check if a type represents a property/field
1579
+ */
1580
+ isPropertyType(type) {
1581
+ const propertyTypes = [
1582
+ 'StringExp', 'NumberExp', 'BooleanExp', 'HtmlExp',
1583
+ 'ResourceExp', 'TrackerExp', 'AttributeExp', 'DamageTrackExp',
1584
+ 'DateExp', 'TimeExp', 'DateTimeExp',
1585
+ 'DieField', 'DiceField',
1586
+ 'SingleDocumentExp', 'DocumentChoiceExp',
1587
+ 'ParentPropertyRefExp', 'StringChoiceField', 'MeasuredTemplateField',
1588
+ 'PaperDollExp', 'MacroField', 'TableField'
1589
+ ];
1590
+ return propertyTypes.includes(type);
1591
+ }
1592
+ /**
1593
+ * Extract parameters from a field or action
1594
+ */
1595
+ extractParameters(item) {
1596
+ const params = [];
1597
+ if (item.params) {
1598
+ for (const param of item.params) {
1599
+ if (param.value !== undefined) {
1600
+ params.push(`${param.$type}=${param.value}`);
1601
+ }
1602
+ else {
1603
+ params.push(param.$type);
1604
+ }
1605
+ }
1606
+ }
1607
+ return params;
1608
+ }
1609
+ /**
1610
+ * Compare field lists to find changes
1611
+ */
1612
+ compareIsdlFields(previousFields, currentFields) {
1613
+ var _a, _b;
1614
+ const changes = [];
1615
+ // Create maps for easier comparison
1616
+ const previousMap = new Map(previousFields.map(f => [`${f.location}.${f.name}`, f]));
1617
+ const currentMap = new Map(currentFields.map(f => [`${f.location}.${f.name}`, f]));
1618
+ // Find removed fields (MAJOR change)
1619
+ for (const [key, field] of previousMap) {
1620
+ if (!currentMap.has(key)) {
1621
+ changes.push({
1622
+ type: 'removed',
1623
+ category: field.category,
1624
+ description: `Removed ${field.category} '${field.name}' from ${field.location}`,
1625
+ name: field.name,
1626
+ details: `Type: ${field.name}${((_a = field.modifiers) === null || _a === void 0 ? void 0 : _a.length) ? `, Modifiers: ${field.modifiers.join(', ')}` : ''}`
1627
+ });
1628
+ }
1629
+ }
1630
+ // Find added fields (MINOR change)
1631
+ for (const [key, field] of currentMap) {
1632
+ if (!previousMap.has(key)) {
1633
+ changes.push({
1634
+ type: 'added',
1635
+ category: field.category,
1636
+ description: `Added ${field.category} '${field.name}' to ${field.location}`,
1637
+ name: field.name,
1638
+ details: `Type: ${field.name}${((_b = field.modifiers) === null || _b === void 0 ? void 0 : _b.length) ? `, Modifiers: ${field.modifiers.join(', ')}` : ''}`
1639
+ });
1640
+ }
1641
+ }
1642
+ // Find modified fields (MINOR or PATCH change depending on modification)
1643
+ for (const [key, currentField] of currentMap) {
1644
+ const previousField = previousMap.get(key);
1645
+ if (previousField && !this.fieldsEqual(previousField, currentField)) {
1646
+ const changeDetails = this.getFieldChangeDetails(previousField, currentField);
1647
+ changes.push({
1648
+ type: 'modified',
1649
+ category: currentField.category,
1650
+ description: `Modified ${currentField.category} '${currentField.name}' in ${currentField.location}`,
1651
+ name: currentField.name,
1652
+ details: changeDetails
1653
+ });
1654
+ }
1655
+ }
1656
+ return changes;
1657
+ }
1658
+ /**
1659
+ * Compare ISDL config sections
1660
+ */
1661
+ compareIsdlConfig(previousAst, currentAst) {
1662
+ var _a, _b;
1663
+ const changes = [];
1664
+ try {
1665
+ const previousConfig = ((_a = previousAst.config) === null || _a === void 0 ? void 0 : _a.body) || [];
1666
+ const currentConfig = ((_b = currentAst.config) === null || _b === void 0 ? void 0 : _b.body) || [];
1667
+ const previousConfigMap = new Map(previousConfig.map((c) => [c.type, c.value]));
1668
+ const currentConfigMap = new Map(currentConfig.map((c) => [c.type, c.value]));
1669
+ // Check for config changes
1670
+ for (const [key, value] of currentConfigMap) {
1671
+ const previousValue = previousConfigMap.get(key);
1672
+ if (previousValue !== value) {
1673
+ changes.push({
1674
+ type: previousValue ? 'modified' : 'added',
1675
+ category: 'system',
1676
+ description: `${previousValue ? 'Updated' : 'Added'} system ${key}: ${value}`,
1677
+ details: previousValue ? `Changed from '${previousValue}' to '${value}'` : undefined
1678
+ });
1679
+ }
1680
+ }
1681
+ }
1682
+ catch (error) {
1683
+ console.error('Failed to compare ISDL config:', error);
1684
+ }
1685
+ return changes;
1686
+ }
1687
+ /**
1688
+ * Check if two fields are equal
1689
+ */
1690
+ fieldsEqual(field1, field2) {
1691
+ return field1.type === field2.type &&
1692
+ JSON.stringify(field1.modifiers) === JSON.stringify(field2.modifiers) &&
1693
+ JSON.stringify(field1.parameters) === JSON.stringify(field2.parameters);
1694
+ }
1695
+ /**
1696
+ * Get details about what changed in a field
1697
+ */
1698
+ getFieldChangeDetails(oldField, newField) {
1699
+ var _a, _b;
1700
+ const details = [];
1701
+ if (oldField.type !== newField.type) {
1702
+ details.push(`Type changed from ${oldField.name} to ${newField.name}`);
1703
+ }
1704
+ if (JSON.stringify(oldField.modifiers) !== JSON.stringify(newField.modifiers)) {
1705
+ details.push(`Modifiers changed from [${(_a = oldField.modifiers) === null || _a === void 0 ? void 0 : _a.join(', ')}] to [${(_b = newField.modifiers) === null || _b === void 0 ? void 0 : _b.join(', ')}]`);
1706
+ }
1707
+ if (JSON.stringify(oldField.parameters) !== JSON.stringify(newField.parameters)) {
1708
+ details.push(`Parameters changed`);
1709
+ }
1710
+ return details.join('; ');
1711
+ }
1712
+ /**
1713
+ * Determine version bump type from ISDL changes
1714
+ */
1715
+ determineVersionFromIsdlChanges(changes) {
1716
+ // Any removed or renamed fields = MAJOR version (breaking change)
1717
+ const hasBreakingChanges = changes.some(c => c.type === 'removed' || c.type === 'renamed');
1718
+ if (hasBreakingChanges) {
1719
+ return 'major';
1720
+ }
1721
+ // Any added fields or actions = MINOR version (new feature)
1722
+ const hasNewFeatures = changes.some(c => c.type === 'added' && (c.category === 'field' || c.category === 'action'));
1723
+ if (hasNewFeatures) {
1724
+ return 'minor';
1725
+ }
1726
+ // System config changes = MINOR version
1727
+ const hasSystemChanges = changes.some(c => c.category === 'system');
1728
+ if (hasSystemChanges) {
1729
+ return 'minor';
1730
+ }
1731
+ // Other modifications = PATCH version (implementation details)
1732
+ return 'patch';
1733
+ }
1734
+ }
1735
+ //# sourceMappingURL=githubManager.js.map