Skip to content

Commit f2f76a3

Browse files
committed
Modules: Improved install command based on testing
- Updated output to be clearer - Added warning and confirmation to local install flow - Adjusted module folder name creation
1 parent ec3dd85 commit f2f76a3

File tree

3 files changed

+22
-5
lines changed

3 files changed

+22
-5
lines changed

app/Console/Commands/InstallModuleCommand.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ protected function getPathToZip(string $location): string|null
268268
if ($isRemote) {
269269
// Warning about fetching from source
270270
$host = parse_url($location, PHP_URL_HOST);
271-
$this->warn("This will download a module from {$host}. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.");
271+
$this->warn("\nThis will download a module from: {$host}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.");
272272
$trustHost = $this->confirm('Are you sure you trust this source?');
273273
if (!$trustHost) {
274274
return null;
@@ -286,13 +286,20 @@ protected function getPathToZip(string $location): string|null
286286
return $this->downloadModuleFile($location);
287287
}
288288

289-
// Validate file and get full location
289+
// Validate the file and get the full location
290290
$zipPath = realpath($location);
291+
291292
if (!$zipPath || !is_file($zipPath)) {
292293
$this->error("ERROR: Module file not found at {$location}");
293294
return null;
294295
}
295296

297+
$this->warn("\nThis will install a module from: {$zipPath}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.");
298+
$trustHost = $this->confirm('Are you sure you want to install this module?');
299+
if (!$trustHost) {
300+
return null;
301+
}
302+
296303
return $zipPath;
297304
}
298305

app/Theming/ThemeModuleManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function deleteModuleFolder(string $moduleFolderName): void
4444
*/
4545
public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule
4646
{
47-
$baseFolderName = Str::limit(Str::slug($name), 20);
47+
$baseFolderName = Str::limit(Str::slug($name), 40, '');
4848
$folderName = $baseFolderName;
4949
while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) {
5050
$folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4);

tests/Commands/InstallModuleCommandTest.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public function test_local_module_install_with_active_theme()
1515
$zip = $this->getModuleZipPath();
1616
$expectedInstallPath = theme_path('modules/test-module');
1717
$this->artisan('bookstack:install-module', ['location' => $zip])
18+
->expectsOutput("\nThis will install a module from: {$zip}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
19+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
1820
->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!')
1921
->expectsOutput("Install location: {$expectedInstallPath}")
2022
->assertExitCode(0);
@@ -35,7 +37,7 @@ public function test_remote_module_install_with_active_theme()
3537
$expectedInstallPath = theme_path('modules/test-module');
3638

3739
$this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip'])
38-
->expectsOutput("This will download a module from example.com. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
40+
->expectsOutput("\nThis will download a module from: example.com\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
3941
->expectsConfirmation('Are you sure you trust this source?', 'yes')
4042
->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!')
4143
->expectsOutput("Install location: {$expectedInstallPath}")
@@ -61,7 +63,7 @@ public function test_remote_http_module_warns_and_prompts_users()
6163
$expectedInstallPath = theme_path('modules/test-module');
6264

6365
$this->artisan('bookstack:install-module', ['location' => 'http://example.com/test-module.zip'])
64-
->expectsOutput("This will download a module from example.com. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
66+
->expectsOutput("\nThis will download a module from: example.com\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
6567
->expectsConfirmation('Are you sure you trust this source?', 'yes')
6668
->expectsOutput("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks.")
6769
->expectsConfirmation('Are you sure you want to continue without HTTPS?', 'yes')
@@ -142,6 +144,7 @@ public function test_run_with_invalid_zip_has_early_exit()
142144
file_put_contents($zip, 'invalid zip');
143145

144146
$this->artisan('bookstack:install-module', ['location' => $zip])
147+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
145148
->expectsOutput("ERROR: Cannot open ZIP file at {$zip}")
146149
->assertExitCode(1);
147150
}
@@ -153,6 +156,7 @@ public function test_run_with_large_zip_has_early_exit()
153156
]);
154157

155158
$this->artisan('bookstack:install-module', ['location' => $zip])
159+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
156160
->expectsOutput("ERROR: Module ZIP file contents are too large. Maximum size is 50MB")
157161
->assertExitCode(1);
158162
}
@@ -166,6 +170,7 @@ public function test_run_with_invalid_module_data_has_early_exit()
166170
]);
167171

168172
$this->artisan('bookstack:install-module', ['location' => $zip])
173+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
169174
->expectsOutput("ERROR: Failed to read module metadata with error: Module in folder \"_temp\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'")
170175
->assertExitCode(1);
171176
}
@@ -177,6 +182,7 @@ public function test_local_module_install_without_active_theme_can_setup_theme_f
177182
File::deleteDirectory($expectedThemePath);
178183

179184
$this->artisan('bookstack:install-module', ['location' => $zip])
185+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
180186
->expectsConfirmation('No active theme folder found, would you like to create one?', 'yes')
181187
->expectsOutput("Created theme folder at {$expectedThemePath}")
182188
->expectsOutput("You will need to set APP_THEME=custom in your BookStack env configuration to enable this theme!")
@@ -195,6 +201,7 @@ public function test_local_module_install_with_active_theme_and_conflicting_modu
195201
File::put(theme_path('modules'), '{}');
196202

197203
$this->artisan('bookstack:install-module', ['location' => $zip])
204+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
198205
->expectsOutput("ERROR: Cannot create a modules folder, file already exists at " . theme_path('modules'))
199206
->assertExitCode(1);
200207
});
@@ -207,6 +214,7 @@ public function test_single_existing_module_with_same_name_replace()
207214
$new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']);
208215

209216
$this->artisan('bookstack:install-module', ['location' => $new])
217+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
210218
->expectsOutput('The following modules already exist with the same name:')
211219
->expectsOutput('Test Module (test-module:v1.0.0) - cat')
212220
->expectsChoice('What would you like to do?', 'Replace existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module'])
@@ -226,6 +234,7 @@ public function test_single_existing_module_with_same_name_cancel()
226234
$new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']);
227235

228236
$this->artisan('bookstack:install-module', ['location' => $new])
237+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
229238
->expectsOutput('The following modules already exist with the same name:')
230239
->expectsOutput('Test Module (test-module:v1.0.0) - cat')
231240
->expectsChoice('What would you like to do?', 'Cancel module install', ['Cancel module install', 'Add alongside existing module', 'Replace existing module'])
@@ -244,6 +253,7 @@ public function test_single_existing_module_with_same_name_add()
244253
$new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']);
245254

246255
$this->artisan('bookstack:install-module', ['location' => $new])
256+
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
247257
->expectsOutput('The following modules already exist with the same name:')
248258
->expectsOutput('Test Module (test-module:v1.0.0) - cat')
249259
->expectsChoice('What would you like to do?', 'Add alongside existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module'])

0 commit comments

Comments
 (0)