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.
- package/.claude/agents/langium-language-designer.md +38 -0
- package/.claude/agents/typescript-vscode-expert.md +29 -0
- package/.claude/agents/ui-ux-designer.md +36 -0
- package/.claude/settings.local.json +33 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/isdl.iml +14 -0
- package/.idea/modules.xml +9 -0
- package/.idea/vcs.xml +7 -0
- package/.idea/watcherTasks.xml +4 -0
- package/.vscodeignore +18 -0
- package/LICENSE +674 -0
- package/README.md +86 -0
- package/bin/cli.js +4 -0
- package/bin/lsp.js +8 -0
- package/isdl.png +0 -0
- package/out/_backgrounds.scss +91 -0
- package/out/_handlebars.scss +505 -0
- package/out/_isdlStyles.scss +1357 -0
- package/out/_vuetifyOverrides.scss +425 -0
- package/out/_vuetifyStyles.scss +31957 -0
- package/out/cli/cli-util.js +39 -0
- package/out/cli/cli-util.js.map +1 -0
- package/out/cli/components/_backgrounds.scss +91 -0
- package/out/cli/components/_handlebars.scss +505 -0
- package/out/cli/components/_isdlStyles.scss +1357 -0
- package/out/cli/components/_vuetifyOverrides.scss +425 -0
- package/out/cli/components/_vuetifyStyles.scss +31957 -0
- package/out/cli/components/active-effect-sheet-generator.js +643 -0
- package/out/cli/components/active-effect-sheet-generator.js.map +1 -0
- package/out/cli/components/base-actor-sheet-generator.js +125 -0
- package/out/cli/components/base-actor-sheet-generator.js.map +1 -0
- package/out/cli/components/base-sheet-generator.js +525 -0
- package/out/cli/components/base-sheet-generator.js.map +1 -0
- package/out/cli/components/chat-card-generator.js +683 -0
- package/out/cli/components/chat-card-generator.js.map +1 -0
- package/out/cli/components/css-generator.js +58 -0
- package/out/cli/components/css-generator.js.map +1 -0
- package/out/cli/components/damage-roll-generator.js +173 -0
- package/out/cli/components/damage-roll-generator.js.map +1 -0
- package/out/cli/components/datamodel-generator.js +672 -0
- package/out/cli/components/datamodel-generator.js.map +1 -0
- package/out/cli/components/derived-data-generator.js +1340 -0
- package/out/cli/components/derived-data-generator.js.map +1 -0
- package/out/cli/components/hotbar-drop-hook-generator.js +95 -0
- package/out/cli/components/hotbar-drop-hook-generator.js.map +1 -0
- package/out/cli/components/init-hook-generator.js +597 -0
- package/out/cli/components/init-hook-generator.js.map +1 -0
- package/out/cli/components/keywords-generator.js +220 -0
- package/out/cli/components/keywords-generator.js.map +1 -0
- package/out/cli/components/language-generator.js +110 -0
- package/out/cli/components/language-generator.js.map +1 -0
- package/out/cli/components/measured-template-preview.js +234 -0
- package/out/cli/components/measured-template-preview.js.map +1 -0
- package/out/cli/components/method-generator.js +1812 -0
- package/out/cli/components/method-generator.js.map +1 -0
- package/out/cli/components/ready-hook-generator.js +448 -0
- package/out/cli/components/ready-hook-generator.js.map +1 -0
- package/out/cli/components/token-generator.js +138 -0
- package/out/cli/components/token-generator.js.map +1 -0
- package/out/cli/components/utils.js +176 -0
- package/out/cli/components/utils.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-attribute.js +148 -0
- package/out/cli/components/vue/base-components/vue-attribute.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-boolean.js +77 -0
- package/out/cli/components/vue/base-components/vue-boolean.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-calculator.js +106 -0
- package/out/cli/components/vue/base-components/vue-calculator.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-application.js +369 -0
- package/out/cli/components/vue/base-components/vue-damage-application.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-bonuses.js +225 -0
- package/out/cli/components/vue/base-components/vue-damage-bonuses.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-resistances.js +256 -0
- package/out/cli/components/vue/base-components/vue-damage-resistances.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-damage-track.js +134 -0
- package/out/cli/components/vue/base-components/vue-damage-track.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-date-time.js +55 -0
- package/out/cli/components/vue/base-components/vue-date-time.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-dice.js +111 -0
- package/out/cli/components/vue/base-components/vue-dice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-die.js +86 -0
- package/out/cli/components/vue/base-components/vue-die.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-document-choice.js +172 -0
- package/out/cli/components/vue/base-components/vue-document-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-document-choices.js +203 -0
- package/out/cli/components/vue/base-components/vue-document-choices.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-document-link.js +73 -0
- package/out/cli/components/vue/base-components/vue-document-link.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-extended-choice.js +101 -0
- package/out/cli/components/vue/base-components/vue-extended-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-inventory.js +532 -0
- package/out/cli/components/vue/base-components/vue-inventory.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-macro-choice.js +150 -0
- package/out/cli/components/vue/base-components/vue-macro-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-measured-template.js +543 -0
- package/out/cli/components/vue/base-components/vue-measured-template.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-money.js +496 -0
- package/out/cli/components/vue/base-components/vue-money.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-number.js +184 -0
- package/out/cli/components/vue/base-components/vue-number.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-paperdoll.js +56 -0
- package/out/cli/components/vue/base-components/vue-paperdoll.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-parent-property-reference.js +89 -0
- package/out/cli/components/vue/base-components/vue-parent-property-reference.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-prosemirror.js +31 -0
- package/out/cli/components/vue/base-components/vue-prosemirror.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-resource.js +149 -0
- package/out/cli/components/vue/base-components/vue-resource.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js +121 -0
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-self-property-reference.js +75 -0
- package/out/cli/components/vue/base-components/vue-self-property-reference.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-string-choice.js +111 -0
- package/out/cli/components/vue/base-components/vue-string-choice.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-string-choices.js +216 -0
- package/out/cli/components/vue/base-components/vue-string-choices.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-string.js +73 -0
- package/out/cli/components/vue/base-components/vue-string.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-text-field.js +66 -0
- package/out/cli/components/vue/base-components/vue-text-field.js.map +1 -0
- package/out/cli/components/vue/base-components/vue-tracker.js +444 -0
- package/out/cli/components/vue/base-components/vue-tracker.js.map +1 -0
- package/out/cli/components/vue/vue-action-component-generator.js +88 -0
- package/out/cli/components/vue/vue-action-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-active-effect-sheet-generator.js +1016 -0
- package/out/cli/components/vue/vue-active-effect-sheet-generator.js.map +1 -0
- package/out/cli/components/vue/vue-base-components-generator.js +59 -0
- package/out/cli/components/vue/vue-base-components-generator.js.map +1 -0
- package/out/cli/components/vue/vue-datatable-component-generator.js +307 -0
- package/out/cli/components/vue/vue-datatable-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +342 -0
- package/out/cli/components/vue/vue-datatable-sheet-class-generator.js.map +1 -0
- package/out/cli/components/vue/vue-datatable2-component-generator.js +939 -0
- package/out/cli/components/vue/vue-datatable2-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-document-creation-app.js +140 -0
- package/out/cli/components/vue/vue-document-creation-app.js.map +1 -0
- package/out/cli/components/vue/vue-document-creation-sheet.js +105 -0
- package/out/cli/components/vue/vue-document-creation-sheet.js.map +1 -0
- package/out/cli/components/vue/vue-generator.js +240 -0
- package/out/cli/components/vue/vue-generator.js.map +1 -0
- package/out/cli/components/vue/vue-mixin.js +338 -0
- package/out/cli/components/vue/vue-mixin.js.map +1 -0
- package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +306 -0
- package/out/cli/components/vue/vue-pinned-datatable-component-generator.js.map +1 -0
- package/out/cli/components/vue/vue-prompt-generator.js +201 -0
- package/out/cli/components/vue/vue-prompt-generator.js.map +1 -0
- package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +252 -0
- package/out/cli/components/vue/vue-prompt-sheet-class-generator.js.map +1 -0
- package/out/cli/components/vue/vue-sheet-application-generator.js +2008 -0
- package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -0
- package/out/cli/components/vue/vue-sheet-class-generator.js +484 -0
- package/out/cli/components/vue/vue-sheet-class-generator.js.map +1 -0
- package/out/cli/generator.js +659 -0
- package/out/cli/generator.js.map +1 -0
- package/out/cli/main.js +43 -0
- package/out/cli/main.js.map +1 -0
- package/out/datatables.min.css +54 -0
- package/out/datatables.min.js +178 -0
- package/out/extension/github/githubAuthProvider.js +345 -0
- package/out/extension/github/githubAuthProvider.js.map +1 -0
- package/out/extension/github/githubConfig.js +132 -0
- package/out/extension/github/githubConfig.js.map +1 -0
- package/out/extension/github/githubGistActions.js +251 -0
- package/out/extension/github/githubGistActions.js.map +1 -0
- package/out/extension/github/githubGistManager.js +255 -0
- package/out/extension/github/githubGistManager.js.map +1 -0
- package/out/extension/github/githubManager.js +1735 -0
- package/out/extension/github/githubManager.js.map +1 -0
- package/out/extension/github/githubQuickActions.js +659 -0
- package/out/extension/github/githubQuickActions.js.map +1 -0
- package/out/extension/github/githubTreeProvider.js +181 -0
- package/out/extension/github/githubTreeProvider.js.map +1 -0
- package/out/extension/github/system-workflow.yml +48 -0
- package/out/extension/main.cjs +70315 -0
- package/out/extension/main.cjs.map +7 -0
- package/out/extension/main.js +237 -0
- package/out/extension/main.js.map +1 -0
- package/out/extension/package.json +426 -0
- package/out/isdl.png +0 -0
- package/out/language/generated/ast.js +2992 -0
- package/out/language/generated/ast.js.map +1 -0
- package/out/language/generated/grammar.js +13970 -0
- package/out/language/generated/grammar.js.map +1 -0
- package/out/language/generated/module.js +20 -0
- package/out/language/generated/module.js.map +1 -0
- package/out/language/intelligent-system-design-language-formatter.js +85 -0
- package/out/language/intelligent-system-design-language-formatter.js.map +1 -0
- package/out/language/intelligent-system-design-language-module.js +69 -0
- package/out/language/intelligent-system-design-language-module.js.map +1 -0
- package/out/language/intelligent-system-design-language-quickfixes.js +37 -0
- package/out/language/intelligent-system-design-language-quickfixes.js.map +1 -0
- package/out/language/intelligent-system-design-language-validator.js +515 -0
- package/out/language/intelligent-system-design-language-validator.js.map +1 -0
- package/out/language/isdl-hover-provider.js +77 -0
- package/out/language/isdl-hover-provider.js.map +1 -0
- package/out/language/isdl-scope-provider.js +149 -0
- package/out/language/isdl-scope-provider.js.map +1 -0
- package/out/language/main.cjs +47655 -0
- package/out/language/main.cjs.map +7 -0
- package/out/language/main.js +11 -0
- package/out/language/main.js.map +1 -0
- package/out/missing-character.png +0 -0
- package/out/package.json +426 -0
- package/out/paperdoll_default.png +0 -0
- package/out/progressbar.min.js +7 -0
- package/out/styles.scss +722 -0
- package/out/test/formatting/formatter.test.js +46 -0
- package/out/test/formatting/formatter.test.js.map +1 -0
- package/out/vuetify.esm.js +30279 -0
- 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
|