4040
4141-- VSIX installation (for VSCode recognition)
4242function M .install_via_vsix (vsix_path )
43- local ok , output = shell .try_exec (' code --install-extension "%s"' , vsix_path )
43+ local ok , output = shell .try_exec (' code --install-extension "%s" 2>&1 ' , vsix_path )
4444
45- if not ok then
45+ -- VSCode might return non-zero exit code even on success, so check output content
46+ if output and (output :match (" successfully installed" ) or output :match (" Extension.*installed" )) then
47+ logger .done (" VSCode extension installed via VSIX" )
48+ -- Print the success message
49+ for line in output :gmatch (" [^\n ]+" ) do
50+ if line :match (" successfully installed" ) or line :match (" Extension.*installed" ) then
51+ print (" " .. line )
52+ end
53+ end
54+ return true , " installed"
55+ elseif output and output :match (" is already installed" ) then
56+ logger .info (" VSCode extension already installed" )
57+ return true , " already_installed"
58+ else
59+ -- If we get here, there was likely a real failure
4660 logger .fail (" VSCode VSIX installation failed" )
61+ if output and output ~= " " then
62+ print (" Error: " .. output )
63+ end
4764 return false , output
4865 end
66+ end
67+
68+ -- Create required VSIX manifest files
69+ function M .create_vsix_manifest (temp_dir , ext_id , ext_path )
70+ -- Read package.json to extract extension metadata
71+ local package_json_path = ext_path .. " /package.json"
72+ local package_json_content = " "
73+ local ok , result = shell .try_exec (' cat "%s"' , package_json_path )
74+ if ok then
75+ package_json_content = result
76+ end
77+ local package_data = {}
4978
50- if output and output :match (" is already installed" ) then
51- logger .info (" VSCode extension already installed" )
52- return true , " already_installed"
79+ if package_json_content then
80+ -- Simple JSON parsing for the fields we need
81+ package_data .name = package_json_content :match (' "name"%s*:%s*"([^"]+)"' ) or ext_id
82+ package_data .displayName = package_json_content :match (' "displayName"%s*:%s*"([^"]+)"' ) or package_data .name
83+ package_data .description = package_json_content :match (' "description"%s*:%s*"([^"]+)"' ) or " "
84+ package_data .version = package_json_content :match (' "version"%s*:%s*"([^"]+)"' ) or " 1.0.0"
85+ package_data .publisher = package_json_content :match (' "publisher"%s*:%s*"([^"]+)"' ) or " unknown"
86+ package_data .categories = package_json_content :match (' "categories"%s*:%s*%[([^%]]+)%]' ) or " "
87+ package_data .keywords = package_json_content :match (' "keywords"%s*:%s*%[([^%]]+)%]' ) or " "
88+ package_data .icon = package_json_content :match (' "icon"%s*:%s*"([^"]+)"' ) or " "
89+ package_data .license = package_json_content :match (' "license"%s*:%s*"([^"]+)"' ) or " "
90+
91+ -- Parse engine version
92+ local engines = package_json_content :match (' "engines"%s*:%s*{([^}]+)}' )
93+ if engines then
94+ package_data .engine = engines :match (' "vscode"%s*:%s*"([^"]+)"' ) or " ^1.74.0"
95+ else
96+ package_data .engine = " ^1.74.0"
97+ end
5398 else
54- logger .done (" VSCode extension installed via VSIX" )
55- if output and output ~= " " then
56- -- Print relevant success messages from VSCode
57- for line in output :gmatch (" [^\n ]+" ) do
58- if line :match (" successfully installed" ) or line :match (" Extension.*installed" ) then
59- print (" " .. line )
60- end
99+ package_data .name = ext_id
100+ package_data .displayName = ext_id
101+ package_data .description = " "
102+ package_data .version = " 1.0.0"
103+ package_data .publisher = " unknown"
104+ package_data .categories = " "
105+ package_data .keywords = " "
106+ package_data .icon = " "
107+ package_data .license = " "
108+ package_data .engine = " ^1.74.0"
109+ end
110+
111+ -- Create [Content_Types].xml with common file types for VSCode extensions
112+ local content_types = [[ <?xml version="1.0" encoding="utf-8"?>
113+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension=".json" ContentType="application/json"/><Default Extension=".vsixmanifest" ContentType="text/xml"/><Default Extension=".md" ContentType="text/markdown"/><Default Extension=".js" ContentType="application/javascript"/><Default Extension=".ts" ContentType="application/typescript"/><Default Extension=".html" ContentType="text/html"/><Default Extension=".css" ContentType="text/css"/><Default Extension=".scss" ContentType="text/css"/><Default Extension=".less" ContentType="text/css"/><Default Extension=".xml" ContentType="text/xml"/><Default Extension=".yaml" ContentType="text/yaml"/><Default Extension=".yml" ContentType="text/yaml"/><Default Extension=".txt" ContentType="text/plain"/><Default Extension=".log" ContentType="text/plain"/><Default Extension=".py" ContentType="text/plain"/><Default Extension=".go" ContentType="text/plain"/><Default Extension=".java" ContentType="text/plain"/><Default Extension=".c" ContentType="text/plain"/><Default Extension=".cpp" ContentType="text/plain"/><Default Extension=".h" ContentType="text/plain"/><Default Extension=".hpp" ContentType="text/plain"/><Default Extension=".rs" ContentType="text/plain"/><Default Extension=".php" ContentType="text/plain"/><Default Extension=".rb" ContentType="text/plain"/><Default Extension=".sh" ContentType="text/plain"/><Default Extension=".png" ContentType="image/png"/><Default Extension=".jpg" ContentType="image/jpeg"/><Default Extension=".jpeg" ContentType="image/jpeg"/><Default Extension=".gif" ContentType="image/gif"/><Default Extension=".svg" ContentType="image/svg+xml"/><Default Extension=".ico" ContentType="image/x-icon"/><Default Extension=".ttf" ContentType="font/ttf"/><Default Extension=".woff" ContentType="font/woff"/><Default Extension=".woff2" ContentType="font/woff2"/><Default Extension=".eot" ContentType="application/vnd.ms-fontobject"/></Types>]]
114+
115+ shell.exec('cat > "%s/[Content_Types].xml" << \'EOF\'\n%sEOF', temp_dir, content_types)
116+
117+ -- Determine icon and license paths
118+ local icon_path = ""
119+ local license_path = ""
120+
121+ if package_data.icon ~= "" then
122+ icon_path = "extension/" .. package_data.icon
123+ else
124+ -- Look for common icon files
125+ local common_icons = {"icon.png", "images/icon.png", "media/icon.png", "assets/icon.png"}
126+ for _, icon_file in ipairs(common_icons) do
127+ local ok, _ = shell.try_exec('test -f "%s"', ext_path .. "/" .. icon_file)
128+ if ok then
129+ icon_path = "extension/" .. icon_file
130+ break
61131 end
62132 end
63- return true , " installed"
64133 end
134+
135+ -- Look for license files
136+ local common_licenses = {"LICENSE", "LICENSE.txt", "LICENSE.md", "license", "license.txt", "license.md"}
137+ for _, license_file in ipairs(common_licenses) do
138+ local ok, _ = shell.try_exec('test -f "%s"', ext_path .. "/" .. license_file)
139+ if ok then
140+ license_path = "extension/" .. license_file
141+ break
142+ end
143+ end
144+
145+ -- Clean up categories and keywords
146+ local categories = package_data.categories:gsub('"', ''):gsub('%s*,%s*', ',')
147+ local tags = package_data.keywords:gsub('"', ''):gsub('%s*,%s*', ',')
148+
149+ -- Create extension.vsixmanifest
150+ local vsix_manifest = string.format([[<?xml version="1.0" encoding="utf-8"?>
151+ <PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
152+ <Metadata>
153+ <Identity Language="en-US" Id="%s" Version="%s" Publisher="%s" />
154+ <DisplayName>%s</DisplayName>
155+ <Description xml:space="preserve">%s</Description>
156+ <Tags>%s</Tags>
157+ <Categories>%s</Categories>
158+ <GalleryFlags>Public</GalleryFlags>
159+
160+ <Properties>
161+ <Property Id="Microsoft.VisualStudio.Code.Engine" Value="%s" />
162+ <Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="" />
163+ <Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="" />
164+ <Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="workspace" />
165+ <Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="" />
166+
167+ <Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="" />
168+ <Property Id="Microsoft.VisualStudio.Services.Links.Getstarted" Value="" />
169+ <Property Id="Microsoft.VisualStudio.Services.Links.GitHub" Value="" />
170+ <Property Id="Microsoft.VisualStudio.Services.Links.Support" Value="" />
171+ <Property Id="Microsoft.VisualStudio.Services.Links.Learn" Value="" />
172+ <Property Id="Microsoft.VisualStudio.Services.Branding.Color" Value="#F2F2F2" />
173+ <Property Id="Microsoft.VisualStudio.Services.Branding.Theme" Value="light" />
174+ <Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="true" />
175+ <Property Id="Microsoft.VisualStudio.Services.Content.Pricing" Value="Free"/>
176+
177+
178+
179+ </Properties>%s%s
180+ </Metadata>
181+ <Installation>
182+ <InstallationTarget Id="Microsoft.VisualStudio.Code"/>
183+ </Installation>
184+ <Dependencies/>
185+ <Assets>
186+ <Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />%s%s%s
187+ </Assets>
188+ </PackageManifest>]] ,
189+ package_data .name , package_data .version , package_data .publisher , package_data .displayName , package_data .description ,
190+ tags , categories , package_data .engine ,
191+ license_path ~= " " and string.format (' \n\t\t\t <License>%s</License>' , license_path ) or " " ,
192+ icon_path ~= " " and string.format (' \n\t\t\t <Icon>%s</Icon>' , icon_path ) or " " ,
193+ (function () local ok , _ = shell .try_exec (' test -f "%s"' , ext_path .. " /README.md" ); return ok end )() and ' \n\t\t\t <Asset Type="Microsoft.VisualStudio.Services.Content.Details" Path="extension/README.md" Addressable="true" />' or " " ,
194+ (function () local ok , _ = shell .try_exec (' test -f "%s"' , ext_path .. " /CHANGELOG.md" ); return ok end )() and ' \n\t\t\t <Asset Type="Microsoft.VisualStudio.Services.Content.Changelog" Path="extension/CHANGELOG.md" Addressable="true" />' or " " ,
195+ license_path ~= " " and string.format (' \n\t\t\t <Asset Type="Microsoft.VisualStudio.Services.Content.License" Path="%s" Addressable="true" />' , license_path ) or " "
196+ )
197+
198+ -- Add icon asset if found
199+ if icon_path ~= " " then
200+ vsix_manifest = vsix_manifest :gsub (" </Assets>" , string.format (' \t\t\t <Asset Type="Microsoft.VisualStudio.Services.Icons.Default" Path="%s" Addressable="true" />\n\t\t </Assets>' , icon_path ))
201+ end
202+
203+ shell .exec (' cat > "%s/extension.vsixmanifest" << \' EOF\'\n %sEOF' , temp_dir , vsix_manifest )
65204end
66205
67206-- VSIX file creation and installation
@@ -71,9 +210,20 @@ function M.create_and_install_vsix(ext_id, nix_store_path, install_dir, tool_nam
71210 local vsix_name = (tool_name or ext_id ):gsub (" %." , " -" ) .. " .vsix"
72211 local vsix_path = install_dir .. " /" .. vsix_name
73212
74- -- Create VSIX file
213+ -- Create VSIX file with proper structure
75214 local zip_ok = pcall (function ()
76- shell .exec (' cd "%s" && zip -r "%s" * -x "*.DS_Store"' , ext_path , vsix_path )
215+ -- Create temp directory for proper VSIX structure
216+ local temp_dir = install_dir .. " /vsix_temp"
217+ shell .exec (' mkdir -p "%s/extension"' , temp_dir )
218+ shell .exec (' cp -r "%s"/. "%s/extension/"' , ext_path , temp_dir )
219+ -- Fix permissions on copied files so they can be deleted
220+ shell .exec (' chmod -R u+w "%s"' , temp_dir )
221+
222+ -- Create required VSIX manifest files
223+ M .create_vsix_manifest (temp_dir , ext_id , ext_path )
224+
225+ shell .exec (' cd "%s" && zip -r "%s" . -x "*.DS_Store"' , temp_dir , vsix_path )
226+ shell .exec (' rm -rf "%s"' , temp_dir )
77227 end )
78228
79229 if not zip_ok then
0 commit comments