wixgem 0.39.0 → 0.43.0
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.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/{spec → lib}/WindowsInstaller.rb +45 -38
- data/lib/WindowsInstaller_hold.rb +156 -0
- data/lib/wixgem.rb +18 -22
- metadata +5 -31
- data/spec/COM_spec.rb +0 -56
- data/spec/command_spec.rb +0 -18
- data/spec/installation_spec.rb +0 -59
- data/spec/mergemodule_spec.rb +0 -76
- data/spec/multiple_product_installation_spec.rb +0 -67
- data/spec/test_files_exist.rb +0 -35
- data/spec/test_install.rb +0 -66
- data/spec/wixpath.rb +0 -3
- data/test_files/32145.txt +0 -1
- data/test_files/8.0/File With Space.txt +0 -1
- data/test_files/9.0/File With Space.txt +0 -1
- data/test_files/9.0/test_files/a_file.txt +0 -1
- data/test_files/File-With-Hyphen.txt +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53d5561b7f79f736e50eecf763a51183da7c0997
|
4
|
+
data.tar.gz: 8d79cd0de6f4a946b455e655f23ba5e7a7026e71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1db621649fc003d17e08cdc79d981019cc91a3b6821abc51a0f88acd939fed00edb7f8dc5f316a6dca21a42f92c93c2e71c21e311ffa013e75f9e3db57e5802
|
7
|
+
data.tar.gz: 0cc002163c565419c6c0f5b28b87e2809b29848bcace63ffb84ba77d9e637f230ec03505ee4de6576273677a4122102410a9cf21136ff95d2c764a4b553f4fd3
|
data/README.md
CHANGED
@@ -14,6 +14,7 @@ The [WiX Toolset](http://wixtoolset.org) must be installed.
|
|
14
14
|
require 'wixgem'
|
15
15
|
|
16
16
|
WIX_TOOLSET_ROOT='path to root of Wix toolset'
|
17
|
+
Wixgem::Wix.install_path = WIX_TOOLSET_ROOT
|
17
18
|
Wixgen::Wix.make_installation('Product.msi', ['rakefile.rb']])
|
18
19
|
|
19
20
|
Wixgen::Wix.make_installation('Product.msi', {product_name: 'productname',
|
@@ -30,6 +31,7 @@ Wixgen::Wix.make_installation('Product.msi', {modify_file_paths: {/\Atest_files\
|
|
30
31
|
require 'wixgem'
|
31
32
|
|
32
33
|
WIX_TOOLSET_ROOT='path to root of Wix toolset'
|
34
|
+
Wixgem::Wix.install_path = WIX_TOOLSET_ROOT
|
33
35
|
Wixgen::Wix.make_mergemodule('Product.msi', ['rakefile.rb']])
|
34
36
|
|
35
37
|
```
|
@@ -56,7 +58,9 @@ small set of optional arguments allowing the developer to customize the generate
|
|
56
58
|
* **all_users**: String value perUser or perMachine. The default is perUser.
|
57
59
|
* **debug**: Boolean value. If debug is true the Product's wxs file and a log file are copied
|
58
60
|
to the same directory as the output msi file. This can help trouble shoot the
|
59
|
-
installation.
|
61
|
+
installation.
|
62
|
+
* **suppress_registry_harvesting** Suppress registry harvesting. Can fix the Runtime Error E6034.
|
63
|
+
* **suppress_COM_elements** Suppress COM elements.
|
60
64
|
|
61
65
|
## License
|
62
66
|
Copyright 2013-2014 Kevin Marshall
|
@@ -1,46 +1,52 @@
|
|
1
1
|
require 'win32ole'
|
2
|
-
require '
|
2
|
+
require File.dirname(__FILE__) + '/command.rb'
|
3
3
|
|
4
|
+
module Wixgem
|
4
5
|
class WindowsInstaller
|
5
|
-
def self.
|
6
|
-
|
6
|
+
def self.install(msi_file)
|
7
|
+
raise "#{msi_file} does not exist!" unless(File.exists?(msi_file))
|
8
|
+
msi_file = msi_file.gsub(/\//, '\\')
|
9
|
+
raise "#{msi_file} is already installed" if(WindowsInstaller.msi_installed?(msi_file))
|
10
|
+
execute("msiexec.exe /i #{msi_file}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.uninstall(msi_file)
|
14
|
+
raise "#{msi_file} does not exist!" unless(File.exists?(msi_file))
|
7
15
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
value = records['ProductCode']
|
12
|
-
end
|
16
|
+
info = msi_records(msi_file)
|
17
|
+
uninstall_product_code(info['ProductCode'])
|
18
|
+
end
|
13
19
|
|
20
|
+
def self.uninstall_product_name(product_name)
|
21
|
+
raise "#{product_name} is not installed" unless(product_name_installed?(product_name))
|
22
|
+
uninstall_product_code(product_code_from_product_name(product_name))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.uninstall_product_code(product_code)
|
26
|
+
raise "#{product_code} is not installed" unless(product_code_installed?(product_code))
|
27
|
+
execute("msiexec.exe /quiet /x #{product_code}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.msi_installed?(msi_file)
|
31
|
+
info = msi_records(msi_file)
|
32
|
+
result = product_code_installed?(info['ProductCode'])
|
33
|
+
return result
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.product_name_installed?(product_name)
|
37
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
14
38
|
installer.Products.each { |prod_code|
|
15
39
|
name = installer.ProductInfo(prod_code, "ProductName")
|
16
|
-
return true if (
|
17
|
-
return true if (value == prod_code)
|
40
|
+
return true if (product_name == name)
|
18
41
|
}
|
19
42
|
return false
|
20
43
|
end
|
21
44
|
|
22
|
-
def self.
|
23
|
-
msi = msi.gsub(/\//, '\\')
|
24
|
-
raise "#{msi} is already installed" if(WindowsInstaller.installed?(msi))
|
25
|
-
execute("msiexec.exe /i #{msi}")
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.uninstall(msi_product_code_or_product_name)
|
29
|
-
value = msi_product_code_or_product_name
|
30
|
-
if(File.exists?(value))
|
31
|
-
value = value.gsub(/\//, '\\')
|
32
|
-
execute("msiexec.exe /quiet /x #{value}")
|
33
|
-
return
|
34
|
-
end
|
35
|
-
|
45
|
+
def self.product_code_installed?(product_code)
|
36
46
|
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
37
|
-
installer.Products.each { |
|
38
|
-
|
39
|
-
|
40
|
-
}
|
41
|
-
|
42
|
-
execute("msiexec.exe /quiet /x #{value}")
|
43
|
-
end
|
47
|
+
installer.Products.each { |installed_product_code| return true if (product_code == installed_product_code) }
|
48
|
+
return false
|
49
|
+
end
|
44
50
|
|
45
51
|
def self.product_code_installed?(product_code)
|
46
52
|
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
@@ -48,13 +54,13 @@ class WindowsInstaller
|
|
48
54
|
return false
|
49
55
|
end
|
50
56
|
|
51
|
-
def self.
|
57
|
+
def self.version_from_product_name(product_name)
|
52
58
|
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
53
|
-
info = product_info(installer,
|
59
|
+
info = product_info(installer, product_code_from_product_name(product_name, installer))
|
54
60
|
return info['VersionString']
|
55
61
|
end
|
56
62
|
|
57
|
-
def self.
|
63
|
+
def self.product_code_from_product_name(product_name, installer = nil)
|
58
64
|
installer = WIN32OLE.new('WindowsInstaller.Installer') if(installer.nil?)
|
59
65
|
installer.Products.each { |prod_code|
|
60
66
|
name = installer.ProductInfo(prod_code, "ProductName")
|
@@ -93,17 +99,17 @@ class WindowsInstaller
|
|
93
99
|
public
|
94
100
|
def self.dump_info(product_name)
|
95
101
|
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
96
|
-
properties = product_info(installer,
|
102
|
+
properties = product_info(installer, product_code_from_product_name(product_name, installer))
|
97
103
|
properties.each { |id, value| puts "#{id}: #{value}" }
|
98
104
|
end
|
99
105
|
|
100
|
-
def self.msi_records(
|
106
|
+
def self.msi_records(msi_file)
|
101
107
|
records = {}
|
102
108
|
|
103
109
|
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
104
110
|
sql_query = "SELECT * FROM `Property`"
|
105
111
|
|
106
|
-
db = installer.OpenDatabase(
|
112
|
+
db = installer.OpenDatabase(msi_file, 0)
|
107
113
|
|
108
114
|
view = db.OpenView(sql_query)
|
109
115
|
view.Execute(nil)
|
@@ -159,7 +165,8 @@ class WindowsInstaller
|
|
159
165
|
|
160
166
|
def self.execute(cmd)
|
161
167
|
command = Wixgem::Command.new(cmd)
|
168
|
+
#command[:debug] = true
|
162
169
|
command.execute
|
163
170
|
end
|
164
171
|
end
|
165
|
-
|
172
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'win32ole'
|
2
|
+
require 'dev_tasks'
|
3
|
+
|
4
|
+
module Wixgem
|
5
|
+
|
6
|
+
class WindowsInstaller
|
7
|
+
def self.install(msi_file)
|
8
|
+
msi_file = msi_file.gsub(/\//, '\\')
|
9
|
+
raise "#{msi_file} is already installed" if(WindowsInstaller.installed?(msi_file))
|
10
|
+
execute("msiexec.exe /i #{msi_file}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.uninstall(msi_file)
|
14
|
+
raise "#{msi_file} does not exist!" unless(File.exists?(msi_file))
|
15
|
+
|
16
|
+
info = msi_properties(installer, msi_file)
|
17
|
+
uninstall_product_code(info['ProductCode'])
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.uninstall_product_name(product_name)
|
21
|
+
raise "#{product_name} is not installed" unless(product_name_installed?(product_name))
|
22
|
+
uninstall_product_code(product_code_from_product_name(product_name))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.uninstall_product_code(product_code)
|
26
|
+
raise "#{product_code} is not installed" unless(product_code_installed?(product_code))
|
27
|
+
execute("msiexec.exe /quiet /x #{product_code}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.msi_installed?(msi_file)
|
31
|
+
info = msi_properties(msi_file)
|
32
|
+
return product_code_installed?(info['ProductCode'])
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.product_name_installed?(product_name)
|
36
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
37
|
+
installer.Products.each { |prod_code|
|
38
|
+
name = installer.ProductInfo(prod_code, "ProductName")
|
39
|
+
return true if (product_name == name)
|
40
|
+
}
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.product_code_installed?(product_code)
|
45
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
46
|
+
installer.Products.each { |installed_product_code| return true if (product_code == installed_product_code) }
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.product_name_installed_version(product_name)
|
51
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
52
|
+
info = product_info(installer, product_code_from_product_name(product_name, installer))
|
53
|
+
return info['VersionString']
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def self.product_code_from_msi(msi_file, installer = nil)
|
58
|
+
msi_info = msi_properties(msi_file)
|
59
|
+
return msi_info['ProductCode']
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.product_code_from_product_name(product_name, installer = nil)
|
63
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer') if(installer.nil?)
|
64
|
+
installer.Products.each { |prod_code|
|
65
|
+
name = installer.ProductInfo(prod_code, "ProductName")
|
66
|
+
return prod_code if (product_name == name)
|
67
|
+
}
|
68
|
+
raise "Failed to find product code for product: #{product_name}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.product_code_properties(product_code, installer = nil)
|
72
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer') if(installer.nil?)
|
73
|
+
hash = Hash.new
|
74
|
+
# known product keywords found on internet. Would be nice to generate.
|
75
|
+
%w[Language PackageCode Transforms AssignmentType PackageName InstalledProductName VersionString RegCompany
|
76
|
+
RegOwner ProductID ProductIcon InstallLocation InstallSource InstallDate Publisher LocalPackage HelpLink
|
77
|
+
HelpTelephone URLInfoAbout URLUpdateInfo InstanceType].sort.each do |prop|
|
78
|
+
value = installer.ProductInfo(product_code, prop)
|
79
|
+
hash[prop] = value unless(value.nil? || value == '')
|
80
|
+
end
|
81
|
+
return hash
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.print_properties(product_name)
|
85
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
86
|
+
properties = product_info(installer, product_code(product_name, installer))
|
87
|
+
properties.each { |id, value| puts "#{id}: #{value}" }
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.msi_records(msi_file)
|
91
|
+
records = {}
|
92
|
+
|
93
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
94
|
+
sql_query = "SELECT * FROM `Property`"
|
95
|
+
|
96
|
+
db = installer.OpenDatabase(msi_file, 0)
|
97
|
+
|
98
|
+
view = db.OpenView(sql_query)
|
99
|
+
view.Execute(nil)
|
100
|
+
|
101
|
+
record = view.Fetch()
|
102
|
+
return '' if(record == nil)
|
103
|
+
|
104
|
+
while(!record.nil?)
|
105
|
+
records[record.StringData(1)] = record.StringData(2)
|
106
|
+
record = view.Fetch()
|
107
|
+
end
|
108
|
+
db.ole_free
|
109
|
+
db = nil
|
110
|
+
installer.ole_free
|
111
|
+
installer = nil
|
112
|
+
|
113
|
+
return records
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.print_msi_records(msi_file)
|
117
|
+
records = msi_records(msi_file)
|
118
|
+
|
119
|
+
puts "#{msi_file} Properties:"
|
120
|
+
records.each do |key,value|
|
121
|
+
puts "#{key}: #{value}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.print_product_name(product_name)
|
126
|
+
installer = WIN32OLE.new('WindowsInstaller.Installer')
|
127
|
+
# only one session per process!
|
128
|
+
session = installer.OpenProduct(product_code_from_product_name?(product_name, installer))
|
129
|
+
db = session.Database
|
130
|
+
|
131
|
+
sql_query = "SELECT * FROM `Property`"
|
132
|
+
view = db.OpenView(sql_query)
|
133
|
+
view.Execute(nil)
|
134
|
+
|
135
|
+
record = view.Fetch()
|
136
|
+
return '' if(record == nil)
|
137
|
+
|
138
|
+
puts "Session Properties:"
|
139
|
+
while(!record.nil?)
|
140
|
+
puts "#{record.StringData(1)}: #{record.StringData(2)}"
|
141
|
+
record = view.Fetch()
|
142
|
+
end
|
143
|
+
db.ole_free
|
144
|
+
db = nil
|
145
|
+
installer.ole_free
|
146
|
+
installer = nil
|
147
|
+
puts ''
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.execute(cmd)
|
151
|
+
command = Wixgem::Command.new(cmd)
|
152
|
+
command.execute
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
data/lib/wixgem.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'SecureRandom'
|
3
|
-
require 'logging'
|
4
|
-
require 'tempfile'
|
5
3
|
require 'tmpdir.rb'
|
6
4
|
require 'rexml/document'
|
7
5
|
require "#{File.dirname(__FILE__)}/command.rb"
|
@@ -41,15 +39,11 @@ class Wix
|
|
41
39
|
|
42
40
|
private
|
43
41
|
def self.start_logger
|
44
|
-
@logger =
|
45
|
-
@log_file = Tempfile.new('wixgem')
|
46
|
-
@logger.add_appenders(Logging.appenders.file(@log_file.path))
|
47
|
-
@logger.level = :debug
|
42
|
+
@logger = []
|
48
43
|
end
|
49
44
|
|
50
45
|
def self.end_logger
|
51
46
|
@logger = nil
|
52
|
-
@log_file = nil
|
53
47
|
end
|
54
48
|
|
55
49
|
def self.manage_upgrade(xml_doc, input)
|
@@ -150,18 +144,18 @@ class Wix
|
|
150
144
|
max_path = files.max { |a, b| a.length <=> b.length }
|
151
145
|
columen_size = max_path.length + 10
|
152
146
|
|
153
|
-
@logger
|
154
|
-
@logger
|
147
|
+
@logger << "------------------------------------ Installation Paths -----------------------------------" unless(@logger.nil?)
|
148
|
+
@logger << "%-#{columen_size}s %s\n" % ['File path', 'Installation Path'] unless(@logger.nil?)
|
155
149
|
files.each do |file|
|
156
150
|
if(File.file?(file))
|
157
151
|
install_path = file
|
158
152
|
if(input.has_key?(:modify_file_paths))
|
159
153
|
input[:modify_file_paths].each { |regex, replacement_string| install_path = install_path.gsub(regex, replacement_string) }
|
160
154
|
end
|
161
|
-
@logger
|
155
|
+
@logger << "%-#{columen_size}s %s\n" % [file, install_path] unless(@logger.nil?)
|
162
156
|
end
|
163
157
|
end
|
164
|
-
@logger
|
158
|
+
@logger << "-------------------------------------------------------------------------------------------" unless(@logger.nil?)
|
165
159
|
end
|
166
160
|
|
167
161
|
if(missing_files.length > 0)
|
@@ -182,15 +176,17 @@ class Wix
|
|
182
176
|
template_option = "-template module" unless(ext == ".msi")
|
183
177
|
|
184
178
|
cmd = "\"#{install_path}/bin/heat.exe\" dir . #{template_option} -cg InstallionFiles -gg -nologo -srd -o \"#{wxs_file}\""
|
185
|
-
cmd = cmd.gsub(/-srd/, '-svb6 -srd') if(input.has_key?(:has_vb6_files))
|
179
|
+
cmd = cmd.gsub(/-srd/, '-svb6 -srd') if(input.has_key?(:has_vb6_files) && input[:has_vb6_files])
|
180
|
+
cmd = cmd.gsub(/-srd/, '-sreg -srd') if(input.has_key?(:suppress_registry_harvesting) && input[:suppress_registry_harvesting])
|
181
|
+
cmd = cmd.gsub(/-srd/, '-scom -srd') if(input.has_key?(:suppress_COM_elements) && input[:suppress_COM_elements])
|
186
182
|
|
187
183
|
heat_cmd = Command.new(cmd)
|
188
|
-
@logger
|
184
|
+
@logger << "command: #{heat_cmd[:command]}" if(@debug && !@logger.nil?)
|
189
185
|
|
190
186
|
heat_cmd.execute
|
191
187
|
if(@debug && !heat_cmd[:output].empty?)
|
192
|
-
@logger
|
193
|
-
@logger
|
188
|
+
@logger << "--------------------------- Heat output -----------------------------------" unless(@logger.nil?)
|
189
|
+
@logger << heat_cmd[:output] unless(@logger.nil?)
|
194
190
|
end
|
195
191
|
|
196
192
|
product_name = File.basename(wxs_file, '.wxs')
|
@@ -240,21 +236,21 @@ class Wix
|
|
240
236
|
wixobj_file = "#{File.basename(wxs_file,'.wxs')}.wixobj"
|
241
237
|
|
242
238
|
candle_cmd = Command.new("\"#{install_path}/bin/candle.exe\" -out \"#{wixobj_file}\" \"#{wxs_file}\"")
|
243
|
-
@logger
|
239
|
+
@logger << "command: #{candle_cmd[:command]}" if(@debug && !@logger.nil?)
|
244
240
|
|
245
241
|
candle_cmd.execute
|
246
242
|
if(@debug && !candle_cmd[:output].empty?)
|
247
|
-
@logger
|
248
|
-
@logger
|
243
|
+
@logger << "--------------------------- Candle output -----------------------------------" unless(@logger.nil?)
|
244
|
+
@logger << candle_cmd[:output] unless(@logger.nil?)
|
249
245
|
end
|
250
246
|
|
251
247
|
light_cmd = Command.new("\"#{install_path}/bin/light.exe\" -nologo -out \"#{output}\" \"#{wixobj_file}\"")
|
252
|
-
@logger
|
248
|
+
@logger << "command: #{light_cmd[:command]}" if(@debug && !@logger.nil?)
|
253
249
|
|
254
250
|
light_cmd.execute
|
255
251
|
if(@debug && !light_cmd[:output].empty?)
|
256
|
-
@logger
|
257
|
-
@logger
|
252
|
+
@logger << "--------------------------- Light output -----------------------------------" unless(@logger.nil?)
|
253
|
+
@logger << light_cmd[:output] unless(@logger.nil?)
|
258
254
|
end
|
259
255
|
end
|
260
256
|
|
@@ -285,7 +281,7 @@ class Wix
|
|
285
281
|
raise "Wixgem exception: #{e}"
|
286
282
|
ensure
|
287
283
|
FileUtils.cp(wxs_file, "#{output_absolute_path}.wxs") if(File.exists?(wxs_file) && @debug)
|
288
|
-
|
284
|
+
File.open("#{output_absolute_path}.log", 'w') { |f| f.puts(@logger) } if(@debug &!@logger.nil?)
|
289
285
|
end
|
290
286
|
end
|
291
287
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wixgem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.43.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Marshall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -59,21 +59,7 @@ dependencies:
|
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
type: :
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: logging
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :runtime
|
62
|
+
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
@@ -94,22 +80,10 @@ files:
|
|
94
80
|
- example/install_files/directory/file2.txt
|
95
81
|
- example/install_files/file1.txt
|
96
82
|
- example/rakefile.rb
|
83
|
+
- lib/WindowsInstaller.rb
|
84
|
+
- lib/WindowsInstaller_hold.rb
|
97
85
|
- lib/command.rb
|
98
86
|
- lib/wixgem.rb
|
99
|
-
- spec/COM_spec.rb
|
100
|
-
- spec/WindowsInstaller.rb
|
101
|
-
- spec/command_spec.rb
|
102
|
-
- spec/installation_spec.rb
|
103
|
-
- spec/mergemodule_spec.rb
|
104
|
-
- spec/multiple_product_installation_spec.rb
|
105
|
-
- spec/test_files_exist.rb
|
106
|
-
- spec/test_install.rb
|
107
|
-
- spec/wixpath.rb
|
108
|
-
- test_files/32145.txt
|
109
|
-
- test_files/8.0/File With Space.txt
|
110
|
-
- test_files/9.0/File With Space.txt
|
111
|
-
- test_files/9.0/test_files/a_file.txt
|
112
|
-
- test_files/File-With-Hyphen.txt
|
113
87
|
homepage: http://rubygems.org/gems/wixgem
|
114
88
|
licenses:
|
115
89
|
- Apache 2.0
|
data/spec/COM_spec.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require './lib/wixgem.rb'
|
3
|
-
require './spec/wixpath.rb'
|
4
|
-
require './spec/WindowsInstaller.rb'
|
5
|
-
require './spec/test_files_exist.rb'
|
6
|
-
require 'win32ole'
|
7
|
-
require './admin.rb'
|
8
|
-
|
9
|
-
if(admin?)
|
10
|
-
describe 'Wixgem' do
|
11
|
-
describe 'Installation of a COM object' do
|
12
|
-
it 'should not be able to instance a COM object' do
|
13
|
-
expect { WIN32OLE.new('COMObject.ComClassExample') }.to raise_error
|
14
|
-
end
|
15
|
-
|
16
|
-
installation_file = 'test\\wixgem_com_test.msi'
|
17
|
-
File.delete(installation_file) if(File.exists?(installation_file))
|
18
|
-
WindowsInstaller.uninstall(installation_file) if(WindowsInstaller.installed?(installation_file))
|
19
|
-
|
20
|
-
installation_hash = { debug: true, all_users: 'perMachine', files: ['COMObject/bin/Release/COMObject.dll']}
|
21
|
-
it "should create an installation file using: #{installation_file}" do
|
22
|
-
Wixgem::Wix.make_installation(installation_file, installation_hash)
|
23
|
-
expect(File.exists?(installation_file)).to be(true)
|
24
|
-
end
|
25
|
-
|
26
|
-
# it 'should install' do
|
27
|
-
# WindowsInstaller.install(installation_file)
|
28
|
-
# expect(WindowsInstaller.installed?(installation_file)).to be(true)
|
29
|
-
# end
|
30
|
-
|
31
|
-
# it 'should have installed the COMObject.dll' do
|
32
|
-
# test_files_exist(installation_file, installation_hash)
|
33
|
-
# end
|
34
|
-
|
35
|
-
# it 'should be able to instance a COM object with a GUID' do
|
36
|
-
# object = WIN32OLE.new('{863AEADA-EE73-4f4a-ABC0-3FB384CB41AA}')
|
37
|
-
# expect(object.nil?).to eq(false)
|
38
|
-
# expect(object.GetText).to eq('Hello World')
|
39
|
-
# end
|
40
|
-
|
41
|
-
# it 'should be able to instance a COM object with a Program Id' do
|
42
|
-
# object = WIN32OLE.new('COMObject.ComClassExample')
|
43
|
-
# expect(object.nil?).to eq(false)
|
44
|
-
# expect(object.GetText).to eq('Hello World')
|
45
|
-
# end
|
46
|
-
|
47
|
-
it 'should uninstall' do
|
48
|
-
WindowsInstaller.uninstall(installation_file) if(WindowsInstaller.installed?(installation_file))
|
49
|
-
expect(WindowsInstaller.installed?(installation_file)).to be(false)
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'should cleanup the generaged msi file' do
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/spec/command_spec.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require './lib/command.rb'
|
3
|
-
|
4
|
-
describe 'Command' do
|
5
|
-
it 'should be able to execute: dir' do
|
6
|
-
cmd = Wixgem::Command.new('dir')
|
7
|
-
cmd.execute
|
8
|
-
expect(cmd[:output].empty?).to eq(false)
|
9
|
-
expect(cmd[:output].include?('Directory')).to eq(true)
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'should fail executing: isnotacommand' do
|
13
|
-
cmd = Wixgem::Command.new('isnotacommand')
|
14
|
-
expect { cmd.execute }.to raise_error
|
15
|
-
expect(cmd[:error].include?('No such file or directory')).to eq(true)
|
16
|
-
expect(cmd[:exit_status]).to_not eq(0)
|
17
|
-
end
|
18
|
-
end
|
data/spec/installation_spec.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require './lib/wixgem.rb'
|
3
|
-
require './spec/wixpath.rb'
|
4
|
-
require './spec/test_install.rb'
|
5
|
-
require './spec/test_files_exist.rb'
|
6
|
-
|
7
|
-
describe 'Wixgem' do
|
8
|
-
#Wixgem::Wix.debug = true
|
9
|
-
describe 'Installation' do
|
10
|
-
test_arguments = {
|
11
|
-
test0: ['wixgem_install_test1.msi', ['rakefile.rb']],
|
12
|
-
test1: ['test/wixgem_install_test1.msi', ['rakefile.rb']],
|
13
|
-
test2: ['test/wixgem_install_test2.msi', {manufacturer: 'musco', files: ['Gemfile']}],
|
14
|
-
test3: ['test/wixgem_install_test3.msi', ['rakefile.rb', 'Gemfile']],
|
15
|
-
test4: ['test/wixgem_install_test4.msi', {version: '1.1.2.3', files: ['Gemfile']}],
|
16
|
-
test5: ['test/wixgem_install_test5.msi', {product_code: '{4528ae5a-c7fa-40a6-a70e-ac8135f1114c}', files: ['Gemfile']}],
|
17
|
-
test6: ['test/wixgem_install_test6.msi', {upgrade_code: '{1d5df00a-c18d-4897-95e6-8c936dd19647}', files: ['Gemfile']}],
|
18
|
-
test7: ['test/wixgem_install_test7.msi', {product_name: 'test_productname', files: ['Gemfile']}],
|
19
|
-
test8: ['test/wixgem_install_test8.msi', {modify_file_paths: {/\Atest_files\// => ''}, files: Dir.glob("test_files/**/*")}]
|
20
|
-
}
|
21
|
-
|
22
|
-
test_arguments.each { |key, value|
|
23
|
-
File.delete(value[0]) if(File.exists?(value[0]))
|
24
|
-
|
25
|
-
it "should create an installation file using: #{value[0]}" do
|
26
|
-
Wixgem::Wix.make_installation(value[0], value[1])
|
27
|
-
expect(File.exists?(value[0])).to be(true)
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should install and uninstall: #{value[0]}" do
|
31
|
-
execute = "test_files_exist('#{value[0]}', #{value[1]})"
|
32
|
-
execute = value[2] if(value.length == 3)
|
33
|
-
test_install(key, value[0], value[1], execute)
|
34
|
-
end
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
describe 'Packaging excptions' do
|
39
|
-
exception_test_arguments = {
|
40
|
-
test1: ['test/wixgem_install_test1.msi', nil],
|
41
|
-
test2: ['test/wixgem_install_test1.msi', []],
|
42
|
-
test3: ['test/wixgem_install_test1.msi', ['does_not_exist.txt']]
|
43
|
-
}
|
44
|
-
|
45
|
-
exception_test_arguments.each { |key, value|
|
46
|
-
it "#{key} should raise an exception" do
|
47
|
-
expect { Wixgem::Wix.make_installation(value[0], value[1]) }.to raise_error
|
48
|
-
end
|
49
|
-
}
|
50
|
-
end
|
51
|
-
|
52
|
-
describe 'including vb6 files' do
|
53
|
-
it "the wix's heat command should contain the -svb6 flag" do
|
54
|
-
Wixgem::Wix.make_installation('test/wixgem_install_vb6_files.msi', {debug: true, manufacturer: 'musco', has_vb6_files: true, files: ['rakefile.rb'], debug: true})
|
55
|
-
wix_cmd_text = File.read('test/wixgem_install_vb6_files.msi.log')
|
56
|
-
expect(wix_cmd_text.include?('-svb6')).to eq(true)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/spec/mergemodule_spec.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require './lib/wixgem.rb'
|
3
|
-
require './spec/wixpath.rb'
|
4
|
-
require './spec/test_install.rb'
|
5
|
-
require './spec/WindowsInstaller.rb'
|
6
|
-
require './admin.rb'
|
7
|
-
|
8
|
-
describe 'Wixgem' do
|
9
|
-
describe 'Merge Module' do
|
10
|
-
test_arguments = {
|
11
|
-
test1: ['test/wixgem_merge_test1.msm', ['rakefile.rb']],
|
12
|
-
test2: ['test/wixgem_merge_test2.msm', {files: ['Gemfile']}],
|
13
|
-
test3: ['test/wixgem_merge_test3.msm', ['rakefile.rb', 'Gemfile']],
|
14
|
-
test4: ['test/wixgem_merge_test4.msm', Dir.glob("test_files/**/*")],
|
15
|
-
test5: ['test/wixgem_merge_test5.msm', {debug: true, files: Dir.glob("test_files/**/*")}]
|
16
|
-
}
|
17
|
-
|
18
|
-
test_arguments.each { |key, value|
|
19
|
-
File.delete(value[0]) if(File.exists?(value[0]))
|
20
|
-
|
21
|
-
it "should create merge module: #{value[0]}" do
|
22
|
-
Wixgem::Wix.make_mergemodule(value[0], value[1])
|
23
|
-
raise "#{key}: #{value[0]} does not exist" unless(File.exists?(value[0]))
|
24
|
-
end
|
25
|
-
|
26
|
-
install_file = value[0].gsub(/msm/) { |s| s = 'msi' }
|
27
|
-
it "should be able to create an installation file using: #{value[0]}" do
|
28
|
-
Wixgem::Wix.make_installation(install_file, ["#{value[0]}"])
|
29
|
-
|
30
|
-
expect(File.exists?(install_file)).to be(true)
|
31
|
-
end
|
32
|
-
|
33
|
-
it "should install and uninstall: #{install_file}" do
|
34
|
-
test_install(key, install_file, value[1])
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should produce the debug files" do
|
38
|
-
if(key == :test5)
|
39
|
-
expect(File.exists?("#{value[0]}.wxs")).to be(true)
|
40
|
-
expect(File.exists?("#{value[0]}.log")).to be(true)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
if(admin?)
|
47
|
-
describe 'Multiple merge Module' do
|
48
|
-
msi_file='test\\wixgem_multiple_merge_test1.msi'
|
49
|
-
merge1='test\\wixgem_multiple_merge_test1.msm'
|
50
|
-
merge2='test\\wixgem_multiple_merge_test2.msm'
|
51
|
-
it "should be able to create two merge modules" do
|
52
|
-
Wixgem::Wix.make_mergemodule(merge1, ['rakefile.rb'])
|
53
|
-
expect(File.exists?(merge1)).to be(true)
|
54
|
-
Wixgem::Wix.make_mergemodule(merge2, ['Gemfile'])
|
55
|
-
expect(File.exists?(merge2)).to be(true)
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should be able to create an installation file using: #{msi_file}" do
|
59
|
-
Wixgem::Wix.make_installation(msi_file, [merge1, merge2])
|
60
|
-
expect(File.exists?(msi_file)).to be(true)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should install contents of merge module" do
|
64
|
-
begin
|
65
|
-
WindowsInstaller.install(msi_file)
|
66
|
-
|
67
|
-
install_dir = "C:/Program Files (x86)/#{File.basename(msi_file, '.msi')}"
|
68
|
-
expect(File.exists?("#{install_dir}/rakefile.rb")).to be(true)
|
69
|
-
expect(File.exists?("#{install_dir}/Gemfile")).to be(true)
|
70
|
-
ensure
|
71
|
-
WindowsInstaller.uninstall(msi_file)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require './lib/wixgem.rb'
|
3
|
-
require './spec/wixpath.rb'
|
4
|
-
require './spec/WindowsInstaller.rb'
|
5
|
-
require './admin.rb'
|
6
|
-
|
7
|
-
if(admin?)
|
8
|
-
describe 'Wixgem' do
|
9
|
-
describe 'Side by side installations' do
|
10
|
-
product1='wixgem_multiple 1.0'
|
11
|
-
product2='wixgem_multiple 1.1'
|
12
|
-
|
13
|
-
Wixgem::Wix.make_installation("test/wixgem_multiple.1.0.0.msi", {version: '1.0.0.0', product_name: product1, upgrade_code: '{face46ab-74ce-44eb-a2b7-81a8cfad5bab}', files: ['Gemfile']})
|
14
|
-
Wixgem::Wix.make_installation("test/wixgem_multiple.1.1.0.msi", {version: '1.1.0.0', product_name: product2, upgrade_code: '{face46ab-74ce-44eb-a2b7-81a8cfad5bab}', files: ['rakefile.rb']})
|
15
|
-
|
16
|
-
it "should install version 1.0.0" do
|
17
|
-
WindowsInstaller.install('test\\wixgem_multiple.1.0.0.msi')
|
18
|
-
expect(WindowsInstaller.installed?(product1)).to be(true)
|
19
|
-
expect(WindowsInstaller.version(product1)).to eq('1.0.0.0')
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should install version 1.1.0" do
|
23
|
-
WindowsInstaller.install('test\\wixgem_multiple.1.1.0.msi')
|
24
|
-
expect(WindowsInstaller.installed?(product2)).to be(true)
|
25
|
-
expect(WindowsInstaller.version(product2)).to eq('1.1.0.0')
|
26
|
-
end
|
27
|
-
|
28
|
-
it "version 1.0.0 should still be installed" do
|
29
|
-
expect(WindowsInstaller.installed?(product1)).to be(true)
|
30
|
-
end
|
31
|
-
|
32
|
-
it "product codes for version 1.0.0 and 1.0.0 should be different" do
|
33
|
-
expect(WindowsInstaller.product_code(product1)).not_to eq(WindowsInstaller.product_code(product2))
|
34
|
-
end
|
35
|
-
|
36
|
-
it "Should be able to uninstall both products" do
|
37
|
-
WindowsInstaller.uninstall(product1)
|
38
|
-
WindowsInstaller.uninstall(product2)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe 'remove previous version' do
|
43
|
-
product1='wixgem_install 1.0'
|
44
|
-
product2='wixgem_install 1.1'
|
45
|
-
Wixgem::Wix.make_installation("test/wixgem_install.1.0.0.msi", {version: '1.0.0.0', product_name: product1, upgrade_code: '{face46ab-74ce-44eb-a2b7-81a8cfad5bab}', files: ['Gemfile']})
|
46
|
-
Wixgem::Wix.make_installation("test/wixgem_install.1.1.0.msi", {version: '1.1.0.0', product_name: product2, remove_existing_products: true, upgrade_code: '{face46ab-74ce-44eb-a2b7-81a8cfad5bab}', files: ['rakefile.rb']})
|
47
|
-
|
48
|
-
it "should install version 1.0.0" do
|
49
|
-
WindowsInstaller.install('test\\wixgem_install.1.0.0.msi')
|
50
|
-
expect(WindowsInstaller.installed?(product1)).to be(true)
|
51
|
-
end
|
52
|
-
|
53
|
-
it "should install version 1.1.0" do
|
54
|
-
WindowsInstaller.install('test\\wixgem_install.1.1.0.msi')
|
55
|
-
expect(WindowsInstaller.installed?(product2)).to be(true)
|
56
|
-
end
|
57
|
-
|
58
|
-
it "the version 1.0.0 should have been uninstalled" do
|
59
|
-
expect(WindowsInstaller.installed?(product1)).to be(false)
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should be able to uninstall #{product2}" do
|
63
|
-
WindowsInstaller.uninstall(product2)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
data/spec/test_files_exist.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
def files(data)
|
2
|
-
files = data
|
3
|
-
if(data.kind_of?(Hash))
|
4
|
-
files = data[:files]
|
5
|
-
|
6
|
-
if(data.has_key?(:modify_file_paths))
|
7
|
-
modify_paths = data[:modify_file_paths]
|
8
|
-
files.each_index do |index|
|
9
|
-
file_path = files[index]
|
10
|
-
modify_paths.each { |regex, replacement_string| file_path = file_path.gsub(regex, replacement_string) }
|
11
|
-
files[index] = file_path
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
return files
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_files_exist(msi_file, data)
|
19
|
-
files = files(data)
|
20
|
-
|
21
|
-
product_name = File.basename(msi_file, File.extname(msi_file))
|
22
|
-
product_name = data[:product_name] if(data.kind_of?(Hash) && data.has_key?(:product_name))
|
23
|
-
|
24
|
-
manufacturer = ''
|
25
|
-
manufacturer = data[:manufacturer] if(data.kind_of?(Hash) && data.has_key?(:manufacturer))
|
26
|
-
|
27
|
-
relative_install_dir = product_name
|
28
|
-
raise "#{name}: relative_install_dir should be set to the product name" if(relative_install_dir.length == 0)
|
29
|
-
relative_install_dir = "#{manufacturer}/#{relative_install_dir}" if(manufacturer.length > 0)
|
30
|
-
|
31
|
-
files.each { |file|
|
32
|
-
full_path = "C:/Program Files (x86)/#{relative_install_dir}/#{file}"
|
33
|
-
raise "#{full_path} not installed." unless(File.exists?(full_path))
|
34
|
-
}
|
35
|
-
end
|
data/spec/test_install.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
require './spec/WindowsInstaller.rb'
|
2
|
-
|
3
|
-
require './admin.rb'
|
4
|
-
require './lib/command.rb'
|
5
|
-
|
6
|
-
def get_product_name(msi_file, arg2)
|
7
|
-
product_name = File.basename(msi_file, File.extname(msi_file))
|
8
|
-
product_name = arg2[:product_name] if(arg2.has_key?(:product_name))
|
9
|
-
|
10
|
-
return product_name
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_msi(msi_file, arg2)
|
14
|
-
product_name = get_product_name(msi_file, arg2)
|
15
|
-
|
16
|
-
msi_info = WindowsInstaller.msi_records(msi_file)
|
17
|
-
#puts msi_info.to_s
|
18
|
-
|
19
|
-
if(arg2.has_key?(:product_name))
|
20
|
-
raise "ProductName is #{msi_info['ProductName']} expected #{product_name}" unless(product_name == msi_info['ProductName'])
|
21
|
-
end
|
22
|
-
|
23
|
-
if(arg2.has_key?(:product_code))
|
24
|
-
expected = arg2[:product_code].upcase
|
25
|
-
raise "ProductCode is #{msi_info['ProductCode']} expected #{expected}" unless(expected == msi_info['ProductCode'])
|
26
|
-
end
|
27
|
-
|
28
|
-
if(arg2.has_key?(:upgrade_code))
|
29
|
-
expected = arg2[:upgrade_code].upcase
|
30
|
-
raise "UpgradeCode is #{msi_info['UpgradeCode']} expected #{expected}" unless(expected == msi_info['UpgradeCode'])
|
31
|
-
end
|
32
|
-
|
33
|
-
expected_product_version = '1.0.0.0'
|
34
|
-
expected_product_version = arg2[:version] if(arg2.has_key?(:version))
|
35
|
-
raise "Invalid product version #{msi_info['ProductVersion']}" if(msi_info['ProductVersion'] != expected_product_version)
|
36
|
-
|
37
|
-
expected_manufacturer = 'Not Set'
|
38
|
-
expected_manufacturer = arg2[:manufacturer] if(arg2.has_key?(:manufacturer))
|
39
|
-
raise "Invalid Manufacturer #{msi_info['Manufacturer']}" if(msi_info['Manufacturer'] != expected_manufacturer)
|
40
|
-
end
|
41
|
-
|
42
|
-
def test_install(name, msi_file, arg2, callback=nil)
|
43
|
-
arg2 = { files: arg2} unless(arg2.kind_of?(Hash))
|
44
|
-
msi_file = msi_file.gsub(/\//) { |s| s = '\\' }
|
45
|
-
test_msi(msi_file, arg2)
|
46
|
-
|
47
|
-
msi_info = WindowsInstaller.msi_records(msi_file)
|
48
|
-
product_name = msi_info['ProductName']
|
49
|
-
|
50
|
-
if(admin?)
|
51
|
-
while(WindowsInstaller.installed?(product_name))
|
52
|
-
WindowsInstaller.uninstall(product_name)
|
53
|
-
end
|
54
|
-
raise "#{name}: Uninstall #{product_name} before running tests" if(WindowsInstaller.installed?(product_name))
|
55
|
-
|
56
|
-
begin
|
57
|
-
WindowsInstaller.install(msi_file)
|
58
|
-
raise "#{name}: Product name #{product_name} is not installed" unless(WindowsInstaller.installed?(product_name))
|
59
|
-
|
60
|
-
eval callback unless(callback == nil)
|
61
|
-
ensure
|
62
|
-
WindowsInstaller.uninstall(msi_file) if(WindowsInstaller.installed?(product_name))
|
63
|
-
raise "Failed to uninstall product #{product_name}" if(WindowsInstaller.installed?(product_name))
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
data/spec/wixpath.rb
DELETED
data/test_files/32145.txt
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Number File
|
@@ -1 +0,0 @@
|
|
1
|
-
Test File
|
@@ -1 +0,0 @@
|
|
1
|
-
Hello World
|
@@ -1 +0,0 @@
|
|
1
|
-
Test file
|
@@ -1 +0,0 @@
|
|
1
|
-
Test it
|