-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Desktop: Install default plugins on first-app-start #6585
Merged
Merged
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
bd677c4
initial commit
mak2002 eb67977
don't load default plugins immediately after installing
mak2002 302b380
Merge branch 'dev' into install-default-plugins
laurent22 d8d86f2
fix installed state of default plugins
mak2002 75d2601
Merge branch 'install-default-plugins' of https://github.com/mak2002/…
mak2002 4491cf0
organize the code for installing default plugins
mak2002 84c02c9
improve types of functions
mak2002 8d61611
add function for default Plugins settings
mak2002 3c14807
add _built_in field in plugins manifest
mak2002 191c8a5
added test
mak2002 32c003b
implement initial settings for plugins
mak2002 3536393
removed code for mkdir command
mak2002 4aa26bc
remove unnecessary files
mak2002 78c2ac7
added tests for initial settings for default plugins
mak2002 46007c5
remove async keyword from app.ts
mak2002 ebde62b
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 4d05817
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 360de71
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 25820d3
Merge branch 'dev' into install-default-plugins
mak2002 b80c487
refactor code
mak2002 228aa96
small fix
mak2002 aa3a5fa
Merge branch 'install-default-plugins' of https://github.com/mak2002/…
mak2002 343c4de
remove old plugins
mak2002 b68fd07
refactor code
mak2002 fbc1793
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 d1db3ec
move default plugins functions into separate folder | update tests
mak2002 53ddafb
remove test plugins
mak2002 4cc80d1
small fix
mak2002 90dfd0d
add comment for checkArrayAndUpdate method
mak2002 a4fcf8e
make function for initial settings to support profileDir
mak2002 6ef7c3d
refactor plugins info object
mak2002 5f6671d
fix import
mak2002 d90dee1
update import in ConfigScreen
mak2002 263d33b
Merge branch 'laurent22:dev' into install-default-plugins
mak2002 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
packages/app-cli/tests/services/plugins/defaultPluginsUtils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import { installDefaultPlugins, getDefaultPluginsInstallState, setSettingsForDefaultPlugins, checkPreInstalledDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils'; | ||
import PluginRunner from '../../../app/services/plugins/PluginRunner'; | ||
import * as fs from 'fs-extra'; | ||
import { setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils'; | ||
import PluginService, { defaultPluginSetting, InitialSettings, PluginSettings } from '@joplin/lib/services/plugins/PluginService'; | ||
import Setting from '@joplin/lib/models/Setting'; | ||
|
||
const testPluginDir = `${supportDir}/plugins`; | ||
|
||
function newPluginService(appVersion: string = '1.4') { | ||
const runner = new PluginRunner(); | ||
const service = new PluginService(); | ||
service.initialize( | ||
appVersion, | ||
{ | ||
joplin: {}, | ||
}, | ||
runner, | ||
{ | ||
dispatch: () => {}, | ||
getState: () => {}, | ||
} | ||
); | ||
return service; | ||
} | ||
|
||
describe('defaultPluginsUtils', function() { | ||
|
||
const pluginsId = ['joplin.plugin.ambrt.backlinksToNote', 'org.joplinapp.plugins.ToggleSidebars']; | ||
|
||
beforeEach(async (done) => { | ||
await setupDatabaseAndSynchronizer(1); | ||
await switchClient(1); | ||
done(); | ||
}); | ||
|
||
it('should install default plugins with no previous default plugins installed', (async () => { | ||
const testPluginDir = `${supportDir}/pluginRepo/plugins`; | ||
Setting.setValue('installedDefaultPlugins', []); | ||
|
||
const service = newPluginService('2.1'); | ||
|
||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states')); | ||
|
||
const newPluginsSettings = await installDefaultPlugins(service, testPluginDir, pluginsId, pluginSettings); | ||
|
||
const installedPluginPath1 = `${Setting.value('pluginDir')}/${pluginsId[0]}.jpl`; | ||
const installedPluginPath2 = `${Setting.value('pluginDir')}/${pluginsId[1]}.jpl`; | ||
|
||
expect(await fs.pathExists(installedPluginPath1)).toBe(true); | ||
expect(await fs.pathExists(installedPluginPath2)).toBe(true); | ||
|
||
expect(newPluginsSettings[pluginsId[0]]).toMatchObject(defaultPluginSetting()); | ||
expect(newPluginsSettings[pluginsId[1]]).toMatchObject(defaultPluginSetting()); | ||
|
||
})); | ||
|
||
it('should install default plugins with previous default plugins installed', (async () => { | ||
|
||
const testPluginDir = `${supportDir}/pluginRepo/plugins`; | ||
Setting.setValue('installedDefaultPlugins', ['org.joplinapp.plugins.ToggleSidebars']); | ||
|
||
const service = newPluginService('2.1'); | ||
|
||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states')); | ||
|
||
const newPluginsSettings = await installDefaultPlugins(service, testPluginDir, pluginsId, pluginSettings); | ||
|
||
const installedPluginPath1 = `${Setting.value('pluginDir')}/${pluginsId[0]}.jpl`; | ||
const installedPluginPath2 = `${Setting.value('pluginDir')}/${pluginsId[1]}.jpl`; | ||
|
||
expect(await fs.pathExists(installedPluginPath1)).toBe(true); | ||
expect(await fs.pathExists(installedPluginPath2)).toBe(false); | ||
|
||
expect(newPluginsSettings[pluginsId[0]]).toMatchObject(defaultPluginSetting()); | ||
expect(newPluginsSettings[pluginsId[1]]).toBeUndefined(); | ||
})); | ||
|
||
it('should get default plugins install state', (async () => { | ||
const testCases = [ | ||
{ | ||
'installedDefaultPlugins': [''], | ||
'loadingPlugins': [`${testPluginDir}/simple`, `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`], | ||
'plugin1DefaultState': defaultPluginSetting(), | ||
'plugin2DefaultState': defaultPluginSetting(), | ||
'installedDefaultPlugins1': true, | ||
'installedDefaultPlugins2': true, | ||
}, | ||
{ | ||
'installedDefaultPlugins': [''], | ||
'loadingPlugins': [`${testPluginDir}/simple`], | ||
'plugin1DefaultState': defaultPluginSetting(), | ||
'plugin2DefaultState': undefined, | ||
'installedDefaultPlugins1': true, | ||
'installedDefaultPlugins2': false, | ||
}, | ||
{ | ||
'installedDefaultPlugins': ['org.joplinapp.plugins.Simple'], | ||
'loadingPlugins': [`${testPluginDir}/simple`, `${testPluginDir}/jpl_test/org.joplinapp.FirstJplPlugin.jpl`], | ||
'plugin1DefaultState': undefined, | ||
'plugin2DefaultState': defaultPluginSetting(), | ||
'installedDefaultPlugins1': true, | ||
'installedDefaultPlugins2': true, | ||
}, | ||
{ | ||
'installedDefaultPlugins': ['org.joplinapp.plugins.Simple'], | ||
'loadingPlugins': [`${testPluginDir}/simple`], | ||
'plugin1DefaultState': undefined, | ||
'plugin2DefaultState': undefined, | ||
'installedDefaultPlugins1': true, | ||
'installedDefaultPlugins2': false, | ||
}, | ||
]; | ||
|
||
for (const testCase of testCases) { | ||
const service = newPluginService(); | ||
const pluginsId = ['org.joplinapp.plugins.Simple', 'org.joplinapp.FirstJplPlugin']; | ||
|
||
Setting.setValue('installedDefaultPlugins', testCase.installedDefaultPlugins); | ||
await service.loadAndRunPlugins(testCase.loadingPlugins, {}); | ||
|
||
// setting installedDefaultPlugins state | ||
const defaultInstallStates: PluginSettings = getDefaultPluginsInstallState(service, pluginsId); | ||
|
||
expect(defaultInstallStates[pluginsId[0]]).toStrictEqual(testCase.plugin1DefaultState); | ||
expect(defaultInstallStates[pluginsId[1]]).toStrictEqual(testCase.plugin2DefaultState); | ||
|
||
|
||
const installedDefaultPlugins = Setting.value('installedDefaultPlugins'); | ||
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(testCase.installedDefaultPlugins1); | ||
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(testCase.installedDefaultPlugins2); | ||
|
||
} | ||
|
||
})); | ||
|
||
it('should check pre-installed default plugins', (async () => { | ||
// with previous pre-installed default plugins | ||
Setting.setValue('installedDefaultPlugins', ['']); | ||
let pluginSettings, installedDefaultPlugins; | ||
|
||
pluginSettings = { [pluginsId[0]]: defaultPluginSetting() }; | ||
checkPreInstalledDefaultPlugins(pluginsId, pluginSettings); | ||
|
||
installedDefaultPlugins = Setting.value('installedDefaultPlugins'); | ||
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(true); | ||
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(false); | ||
|
||
|
||
// with no previous pre-installed default plugins | ||
Setting.setValue('installedDefaultPlugins', ['not-a-default-plugin']); | ||
pluginSettings = {}; | ||
checkPreInstalledDefaultPlugins(pluginsId, pluginSettings); | ||
|
||
installedDefaultPlugins = Setting.value('installedDefaultPlugins'); | ||
expect(installedDefaultPlugins.includes(pluginsId[0])).toBe(false); | ||
expect(installedDefaultPlugins.includes(pluginsId[1])).toBe(false); | ||
|
||
})); | ||
|
||
it('should set initial settings for default plugins', async () => { | ||
const service = newPluginService(); | ||
|
||
const pluginScript = ` | ||
/* joplin-manifest: | ||
{ | ||
"id": "io.github.jackgruber.backup", | ||
"manifest_version": 1, | ||
"app_min_version": "1.4", | ||
"name": "JS Bundle test", | ||
"version": "1.0.0" | ||
} | ||
*/ | ||
joplin.plugins.register({ | ||
onStart: async function() { | ||
await joplin.settings.registerSettings({ | ||
path: { | ||
value: "initial-path", | ||
type: 2, | ||
section: "backupSection", | ||
public: true, | ||
label: "Backup path", | ||
}, | ||
}) | ||
}, | ||
});`; | ||
|
||
const plugin = await service.loadPluginFromJsBundle('', pluginScript); | ||
await service.runPlugin(plugin); | ||
|
||
const initialSettings: InitialSettings = { | ||
'io.github.jackgruber.backup': { | ||
'path': `${Setting.value('profileDir')}/testBackup`, | ||
}, | ||
}; | ||
|
||
// with pre-installed default plugin | ||
Setting.setValue('installedDefaultPlugins', ['io.github.jackgruber.backup']); | ||
setSettingsForDefaultPlugins(initialSettings); | ||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe('initial-path'); | ||
await service.destroy(); | ||
|
||
// with no pre-installed default plugin | ||
Setting.setValue('installedDefaultPlugins', ['']); | ||
setSettingsForDefaultPlugins(initialSettings); | ||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe(`${Setting.value('profileDir')}/testBackup`); | ||
await service.destroy(); | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+11 MB
packages/app-desktop/build/defaultPlugins/io.github.jackgruber.backup/plugin.jpl
Binary file not shown.
Binary file added
BIN
+2.28 MB
packages/app-desktop/build/defaultPlugins/plugin.calebjohn.rich-markdown/plugin.jpl
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1546,6 +1546,13 @@ class Setting extends BaseModel { | |
public: false, | ||
}, | ||
|
||
installedDefaultPlugins: { | ||
value: [], | ||
type: SettingItemType.Array, | ||
public: false, | ||
storage: SettingStorage.File, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be database here |
||
}, | ||
|
||
// 'featureFlag.syncAccurateTimestamps': { | ||
// value: false, | ||
// type: SettingItemType.Bool, | ||
|
@@ -1914,6 +1921,17 @@ class Setting extends BaseModel { | |
return this.setValue(key, !this.value(key)); | ||
} | ||
|
||
// this method checks if the 'value' passed is present in the Setting "Array" | ||
// If yes, then it just returns 'true'. If its not present then, it will | ||
// update it and return 'false' | ||
static checkArrayAndUpdate(settingName: string, value: string): boolean { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe call it |
||
const settingValue: Array<any> = this.value(settingName); | ||
if (settingValue.includes(value)) return true; | ||
settingValue.push(value); | ||
this.setValue(settingName, settingValue); | ||
return false; | ||
} | ||
|
||
static objectValue(settingKey: string, objectKey: string, defaultValue: any = null) { | ||
const o = this.value(settingKey); | ||
if (!o || !(objectKey in o)) return defaultValue; | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't import
*
. Only import the functions that you need