Skip to content

Commit 2eefffe

Browse files
committed
fix(config): never lose a config edit when prettier is unavailable
setValue fed writeFile with prettier's return value, so a failing or missing prettier silently discarded the transformed content. Formatting is now best-effort and the transformed content is always written. Prettier is also resolved from the project first and only falls back to the CLI's bundled copy, so the formatting options read from the project are applied by the version the project pins. Loading it lazily keeps a broken prettier from breaking CLI startup, since projectConfigService is a core service.
1 parent ddd39b8 commit 2eefffe

2 files changed

Lines changed: 208 additions & 47 deletions

File tree

lib/services/project-config-service.ts

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ import {
2323
import { IBasePluginData } from "../definitions/plugins";
2424
import { injector } from "../common/yok";
2525
import { EOL } from "os";
26-
import {
27-
format as prettierFormat,
28-
resolveConfig as resolvePrettierConfig,
29-
} from "prettier";
3026
import { cache, exported } from "../common/decorators";
3127
import { IOptions } from "../declarations";
3228
import * as semver from "semver/preload";
3329
import { ICleanupService } from "../definitions/cleanup-service";
3430

31+
type PrettierModule = typeof import("prettier");
32+
3533
export class ProjectConfigService implements IProjectConfigService {
3634
private forceUsingNewConfig: boolean = false;
3735
private forceUsingLegacyConfig: boolean = false;
@@ -273,27 +271,7 @@ export default {
273271
configContent,
274272
);
275273
const newContent = transformer.setValue(key, value);
276-
const prettierOptions = (await resolvePrettierConfig(
277-
this.projectHelper.projectDir,
278-
{ editorconfig: true },
279-
)) || {
280-
semi: false,
281-
singleQuote: true,
282-
};
283-
this.$logger.trace(
284-
"updating config, prettier options: ",
285-
prettierOptions,
286-
);
287-
this.$fs.writeFile(
288-
configFilePath,
289-
await prettierFormat(newContent, {
290-
...prettierOptions,
291-
parser: "typescript",
292-
// note: we don't use plugins here, since we are only formatting ts files, and they are supported by default
293-
// and this also causes issues with certain plugins, like prettier-plugin-tailwindcss.
294-
plugins: [],
295-
}),
296-
);
274+
this.$fs.writeFile(configFilePath, await this.formatConfig(newContent));
297275
} catch (error) {
298276
this.$logger.error(`Failed to update config.` + error);
299277
} finally {
@@ -318,6 +296,61 @@ export default {
318296
}
319297
}
320298

299+
/**
300+
* Resolved from the project first: the formatting options come from the
301+
* project's own prettier/editorconfig setup, so the project's prettier version
302+
* is the one that understands them. Required lazily - a broken or missing
303+
* prettier must not take down the CLI, since this is a core service.
304+
*/
305+
private loadPrettier(): PrettierModule {
306+
const projectDir = this.projectHelper.projectDir;
307+
308+
if (projectDir) {
309+
try {
310+
return require(require.resolve("prettier", { paths: [projectDir] }));
311+
} catch (error) {
312+
this.$logger.trace(
313+
"Could not load prettier from the project, using the bundled one.",
314+
error,
315+
);
316+
}
317+
}
318+
319+
return require("prettier");
320+
}
321+
322+
private async formatConfig(content: string): Promise<string> {
323+
try {
324+
const prettier = this.loadPrettier();
325+
const prettierOptions = (await prettier.resolveConfig(
326+
this.projectHelper.projectDir,
327+
{ editorconfig: true },
328+
)) || {
329+
semi: false,
330+
singleQuote: true,
331+
};
332+
this.$logger.trace(
333+
"updating config, prettier options: ",
334+
prettierOptions,
335+
);
336+
337+
// awaiting covers both prettier 2 (sync) and prettier 3 (async) format
338+
return await prettier.format(content, {
339+
...prettierOptions,
340+
parser: "typescript",
341+
// note: we don't use plugins here, since we are only formatting ts files, and they are supported by default
342+
// and this also causes issues with certain plugins, like prettier-plugin-tailwindcss.
343+
plugins: [],
344+
});
345+
} catch (error) {
346+
this.$logger.warn(
347+
"Could not format the config with prettier - it will be written unformatted.",
348+
);
349+
this.$logger.trace("prettier failed with: ", error);
350+
return content;
351+
}
352+
}
353+
321354
public writeDefaultConfig(projectDir: string, appId?: string) {
322355
const TSConfigPath = path.resolve(projectDir, CONFIG_FILE_NAME_TS);
323356

0 commit comments

Comments
 (0)