Skip to content

Commit

Permalink
#788 - Safe setting update
Browse files Browse the repository at this point in the history
  • Loading branch information
estruyf committed May 23, 2024
1 parent 16453cb commit a70b431
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

- [#441](https://github.com/estruyf/vscode-front-matter/issues/441): Show input descriptions for snippet and data forms
- [#442](https://github.com/estruyf/vscode-front-matter/issues/442): Hide sidebar on data view when data file is selecte + show dropdown of data files
- [#788](https://github.com/estruyf/vscode-front-matter/issues/788): Show a warning on setting update when it exists in an extended configuration
- [#798](https://github.com/estruyf/vscode-front-matter/issues/798): Changed dialog to slide-over for the snippet forms
- [#799](https://github.com/estruyf/vscode-front-matter/issues/799): Added `frontMatter.logging` setting to define the logging output. Options are `info`, `warn`, `error`, and `verbose`. Default is `info`.
- [#800](https://github.com/estruyf/vscode-front-matter/issues/800): Add colors for the Front Matter CMS output
Expand Down
7 changes: 3 additions & 4 deletions src/commands/Folders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,8 @@ export class Folders {
path: Folders.relWsFolder(folder, wsFolder)
};

if (detail['$schema'] || detail.extended) {
return null;
}
delete detail['$schema'];
delete detail.extended;

if (detail.locale && detail.locale === detail.defaultLocale) {
// Check if the folder was on the original list
Expand All @@ -490,7 +489,7 @@ export class Folders {
})
.filter((folder) => folder !== null);

await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);

// Reinitialize the folder listeners
PagesListener.startWatchers();
Expand Down
2 changes: 1 addition & 1 deletion src/commands/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ categories: []
await Settings.createTeamSettings();

// Add the default content type
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);

if (sampleTemplate !== undefined) {
await Project.createSampleTemplate();
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/ContentType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export class ContentType {
contentTypes.push(newContentType);
}

await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);

const configPath = await Settings.projectConfigPath();
const notificationAction = await Notifications.info(
Expand Down Expand Up @@ -350,7 +350,7 @@ export class ContentType {
const index = contentTypes.findIndex((ct) => ct.name === contentType.name);
contentTypes[index].fields = updatedFields;

await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);

const configPath = await Settings.projectConfigPath();
const notificationAction = await Notifications.info(
Expand Down Expand Up @@ -804,7 +804,7 @@ export class ContentType {
fields: blockFields
};
fieldGroups.push(newFieldGroup);
await Settings.update(SETTING_TAXONOMY_FIELD_GROUPS, fieldGroups, true);
await Settings.safeUpdate(SETTING_TAXONOMY_FIELD_GROUPS, fieldGroups, true);

fields.push({
title: field,
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export class Extension {
}
);

await Settings.update(
await Settings.safeUpdate(
SETTING_TEMPLATES_ENABLED,
answer?.toLocaleLowerCase() === l10n.t(LocalizationKey.commonYes),
true
Expand Down Expand Up @@ -266,7 +266,7 @@ export class Extension {

return f;
});
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folders, true);
await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folders, true);
}

// The tags and categories settings need to be moved to the database
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/FrameworkDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class FrameworkDetector {
});
}

await Settings.update(SETTING_CONTENT_STATIC_FOLDER, assetFoler, true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, assetFoler, true);
} catch (e) {
Logger.error(
`Something failed while processing your Hexo configuration. ${(e as Error).message}`
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { format } from 'date-fns';
import { COMMAND_NAME, SETTING_LOGGING } from '../constants';
import { Settings } from '.';

export type LoggerLocation = 'VSCODE' | 'DASHBOARD' | 'PANEL';
export type LoggerLocation = 'VSCODE' | 'DASHBOARD' | 'PANEL' | 'SETTING';

export class Logger {
private static instance: Logger;
Expand Down
24 changes: 24 additions & 0 deletions src/helpers/Notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ export class Notifications {
return Promise.resolve(undefined);
}

/**
* Show a warning notification to the user with a link to the output channel
* @param message
* @param items
* @returns
*/
public static warningWithOutput(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'WARNING');

if (this.shouldShow('WARNING')) {
return window.showWarningMessage(
`${EXTENSION_NAME}: ${message} ${l10n.t(
LocalizationKey.notificationsOutputChannelDescription,
`[${l10n.t(LocalizationKey.notificationsOutputChannelLink)}](command:${
COMMAND_NAME.showOutputChannel
})`
)}`,
...items
);
}

return Promise.resolve(undefined);
}

/**
* Show an error notification to the user
* @param message
Expand Down
74 changes: 68 additions & 6 deletions src/helpers/SettingsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export class Settings {
private static readConfigPromise: Promise<void> | undefined = undefined;
private static project: Project | undefined = undefined;
private static configDebouncer = debounceCallback();
private static hasExtendedConfig: boolean = false;
private static extendedConfig: { extended: any[]; splitted: any[]; dynamic: boolean } = {} as any;

public static async registerCommands() {
const ext = Extension.getInstance();
Expand Down Expand Up @@ -374,12 +376,54 @@ export class Settings {
return setting;
}

/**
* Safely updates a setting value.
*
* @param name - The name of the setting.
* @param value - The new value for the setting.
* @param updateGlobal - Indicates whether to update the global setting or not. Default is `false`.
* @returns A promise that resolves when the setting is updated.
*/
public static async safeUpdate<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
if (Settings.hasExtendedConfig) {
const configKey = `${CONFIG_KEY}.${name}`;

if (
Settings.extendedConfig.extended.includes(configKey) ||
Settings.extendedConfig.splitted.includes(configKey) ||
Settings.extendedConfig.dynamic
) {
Notifications.warningWithOutput(
`Cannot update setting "${configKey}" because you've extended or split the Front Matter CMS configuration. Please manually add your changes. Check the output for the setting update.`
);

Logger.info(`Updating setting: ${configKey}`, 'SETTING');
Logger.info(
`New value:
${JSON.stringify(value, null, 2)}`,
'SETTING'
);
return;
}
}

await Settings.update<T>(name, value, updateGlobal);
}

/**
* String update config setting
* @param name
* @param value
*/
public static async update<T>(name: string, value: T, updateGlobal: boolean = false) {
public static async update<T>(
name: string,
value: T,
updateGlobal: boolean = false
): Promise<void> {
const fmConfig = await Settings.projectConfigPath();

if (updateGlobal) {
Expand Down Expand Up @@ -423,7 +467,7 @@ export class Settings {

const workspaceSettingValue = Settings.hasWorkspaceSettings<ContentType[]>(name);
if (workspaceSettingValue) {
await Settings.update(name, undefined);
await Settings.safeUpdate(name, undefined);
}

// Make sure to reload the whole config + all the data files
Expand Down Expand Up @@ -515,7 +559,7 @@ export class Settings {
customTaxonomies[taxIdx].options.push(option);
customTaxonomies[taxIdx].options = [...new Set(customTaxonomies[taxIdx].options)];
customTaxonomies[taxIdx].options = customTaxonomies[taxIdx].options.sort().filter((o) => !!o);
await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
}

/**
Expand All @@ -532,7 +576,7 @@ export class Settings {
customTaxonomies[taxIdx].options = options;
}

await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true);
}

/**
Expand All @@ -548,8 +592,8 @@ export class Settings {
const setting = Settings.config.inspect(settingName);

if (setting && typeof setting.workspaceValue !== 'undefined') {
await Settings.update(settingName, setting.workspaceValue, true);
await Settings.update(settingName, undefined);
await Settings.safeUpdate(settingName, setting.workspaceValue, true);
await Settings.safeUpdate(settingName, undefined);
}
}
}
Expand Down Expand Up @@ -652,6 +696,13 @@ export class Settings {
*/
private static async readConfig() {
try {
Settings.hasExtendedConfig = false;
Settings.extendedConfig = {
extended: [],
splitted: [],
dynamic: false
};

const fmConfig = await Settings.projectConfigPath();
if (fmConfig && (await existsAsync(fmConfig))) {
const localConfig = await readFileAsync(fmConfig, 'utf8');
Expand All @@ -671,6 +722,8 @@ export class Settings {
);
if (configFiles.length === 0) {
Logger.info(`No ".frontmatter/config" config files found.`);
} else {
Settings.hasExtendedConfig = true;
}

// Sort the files by fsPath
Expand Down Expand Up @@ -707,6 +760,8 @@ export class Settings {
);

if (dynamicConfig) {
Settings.hasExtendedConfig = true;
Settings.extendedConfig.dynamic = true;
Settings.globalConfig = dynamicConfig;
Logger.info(`Dynamic config file loaded`);
}
Expand Down Expand Up @@ -738,11 +793,17 @@ export class Settings {
return;
}

Settings.hasExtendedConfig = true;
const originalConfig = Object.assign({}, Settings.globalConfig);
const extendsConfig: string[] = Settings.globalConfig[extendsConfigName];
for (const externalConfig of extendsConfig) {
if (externalConfig.endsWith(`.json`)) {
const config = await Settings.getExternalConfig(externalConfig);

// Store the config keys
const keys = Object.keys(config);
Settings.extendedConfig.extended.push(...keys);

await Settings.extendConfig(config, originalConfig);
}
}
Expand Down Expand Up @@ -777,6 +838,7 @@ export class Settings {
Settings.globalConfig = {};
}

Settings.extendedConfig.splitted.push(`${CONFIG_KEY}.${relSettingName}`);
Settings.updateGlobalConfigSetting(relSettingName, configJson, configFilePath, filePath);
} catch (e) {
Logger.error(`Error reading config file: ${configFile.fsPath}`);
Expand Down
14 changes: 7 additions & 7 deletions src/listeners/dashboard/SettingsListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class SettingsListener extends BaseListener {
if (typeof setting.name !== 'undefined' && typeof setting.value !== 'undefined') {
const value = Settings.get(setting.name);
if (value !== setting.value) {
await Settings.update(setting.name, setting.value, true);
await Settings.safeUpdate(setting.name, setting.value, true);
}
}
}
Expand Down Expand Up @@ -190,7 +190,7 @@ export class SettingsListener extends BaseListener {
*/
private static async update(data: { name: string; value: any; global?: boolean }) {
if (data.name) {
await Settings.update(data.name, data.value, data.global);
await Settings.safeUpdate(data.name, data.value, data.global);
this.getSettings(true);
}
}
Expand All @@ -213,7 +213,7 @@ export class SettingsListener extends BaseListener {
* @param frameworkId
*/
public static async setFramework(frameworkId: string | null) {
await Settings.update(SETTING_FRAMEWORK_ID, frameworkId, true);
await Settings.safeUpdate(SETTING_FRAMEWORK_ID, frameworkId, true);

if (frameworkId) {
const allFrameworks = FrameworkDetector.getAll();
Expand All @@ -222,24 +222,24 @@ export class SettingsListener extends BaseListener {
);
if (framework) {
if (framework.static && typeof framework.static === 'string') {
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
}

if (framework.server) {
await Settings.update(SETTING_PREVIEW_HOST, framework.server, true);
await Settings.safeUpdate(SETTING_PREVIEW_HOST, framework.server, true);
}

await FrameworkDetector.checkDefaultSettings(framework);
} else {
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, '', true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, '', true);
}
}

SettingsListener.getSettings(true);
}

public static async addAssetsFolder(assetFolder: string | StaticFolder) {
await Settings.update(SETTING_CONTENT_STATIC_FOLDER, assetFolder, true);
await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, assetFolder, true);
SettingsListener.getSettings(true);
}

Expand Down
4 changes: 2 additions & 2 deletions src/listeners/dashboard/SnippetListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class SnippetListener extends BaseListener {

snippets[title] = snippetContent;

await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
await Settings.safeUpdate(SETTING_CONTENT_SNIPPETS, snippets, true);
SettingsListener.getSettings(true);
}

Expand All @@ -93,7 +93,7 @@ export class SnippetListener extends BaseListener {
return acc;
}, {} as Snippets);

await Settings.update(SETTING_CONTENT_SNIPPETS, snippetsToStore, true);
await Settings.safeUpdate(SETTING_CONTENT_SNIPPETS, snippetsToStore, true);
SettingsListener.getSettings(true);
}

Expand Down
2 changes: 1 addition & 1 deletion src/listeners/dashboard/SsgListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class SsgListener extends BaseListener {
}

contentTypes.push(contentType);
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);
await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true);

SsgListener.sendRequest(command as any, requestId, {});
SettingsListener.getSettings(true);
Expand Down
2 changes: 1 addition & 1 deletion src/listeners/panel/SettingsListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class SettingsListener extends BaseListener {
* @param value
*/
private static async updateSetting(setting: string, value: any) {
await Settings.update(setting, value);
await Settings.safeUpdate(setting, value);
this.getSettings();
}

Expand Down
4 changes: 2 additions & 2 deletions src/models/ContentFolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export interface ContentFolder {
filePrefix?: string;
contentTypes?: string[];
originalPath?: string;
$schema?: string;
extended?: boolean;
$schema?: string; // Extended config
extended?: boolean; // Extended config

locale?: string;
localeTitle?: string;
Expand Down

0 comments on commit a70b431

Please sign in to comment.