Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,51 @@ Supply a custom path to the prettier module. This path should be to the module f

**Disabled on untrusted workspaces**

#### prettier.customExecutable

Use a custom executable to run Prettier. This is useful for Docker-centric workspaces where Prettier runs inside a container. Specify the full command including any arguments. Prettier CLI arguments (like `--stdin-filepath`, `--parser`, etc.) will be automatically appended to your command.

**How it works:**
- The extension runs your command with Prettier arguments appended
- Code is sent via stdin and formatted output is read from stdout
- Optionally use `${prettier}` as a placeholder for the Prettier path (defaults to the value in `prettier.prettierPath` or auto-detected path)

**Example for Docker:**

```json
{
"prettier.customExecutable": "docker compose exec -T app node_modules/.bin/prettier"
}
```

This will execute commands like:
```bash
docker compose exec -T app node_modules/.bin/prettier --stdin-filepath file.js --parser babel
```

**Example with a wrapper script:**

```json
{
"prettier.customExecutable": "./scripts/prettier-wrapper.sh"
}
```

**Example with placeholder:**

```json
{
"prettier.customExecutable": "docker exec my-container ${prettier}",
"prettier.prettierPath": "/app/node_modules/.bin/prettier"
}
```

When `prettier.customExecutable` is specified, it takes precedence over `prettier.prettierPath`. If you need to specify where Prettier is located, you can set `prettier.prettierPath` to provide the path for the `${prettier}` placeholder or for auto-detection.

**Note:** The custom executable must accept Prettier CLI arguments and behave like the Prettier CLI. Input is provided via stdin and output is expected on stdout.

**Disabled on untrusted workspaces**

#### prettier.resolveGlobalModules (default: `false`)

When enabled, this extension will attempt to use global npm or yarn modules if local modules cannot be resolved.
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"prettier.resolveGlobalModules",
"prettier.ignorePath",
"prettier.prettierPath",
"prettier.customExecutable",
"prettier.configPath",
"prettier.useEditorConfig",
"prettier.resolveGlobalModules",
Expand Down Expand Up @@ -214,6 +215,11 @@
"markdownDescription": "%ext.config.prettierPath%",
"scope": "resource"
},
"prettier.customExecutable": {
"type": "string",
"markdownDescription": "%ext.config.customExecutable%",
"scope": "resource"
},
"prettier.configPath": {
"type": "string",
"markdownDescription": "%ext.config.configPath%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"ext.config.parser": "Override the parser. You shouldn't have to change this setting.",
"ext.config.parserDeprecationMessage": "This setting is no longer supported. Use a prettier configuration file instead.",
"ext.config.prettierPath": "Path to the `prettier` module, eg: `./node_modules/prettier`.",
"ext.config.customExecutable": "Use a custom executable to run Prettier. Specify the full command including arguments. Prettier CLI arguments will be appended automatically. Optionally use `${prettier}` as a placeholder for the Prettier path. Example: `docker compose exec -T app node_modules/.bin/prettier`. When specified, takes precedence over `prettier.prettierPath`.",
"ext.config.printWidth": "Fit code within this line limit.",
"ext.config.proseWrap": "(Markdown) wrap prose over multiple lines.",
"ext.config.quoteProps": "Change when properties in objects are quoted.\nValid options:\n- `\"as-needed\"` - Only add quotes around object properties where required.\n- `\"consistent\"` - If at least one property in an object requires quotes, quote all properties.\n- `\"preserve\"` - Respect the input use of quotes in object properties.",
Expand Down
1 change: 1 addition & 0 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"ext.config.parser": "覆盖解析器。通常不需要更改此设置。",
"ext.config.parserDeprecationMessage": "此设置已不再支持。请改用 Prettier 配置文件。",
"ext.config.prettierPath": "`prettier` 包路径,如 `./node_modules/prettier`。",
"ext.config.customExecutable": "使用自定义可执行文件运行 Prettier。指定完整命令及其参数。Prettier CLI 参数将自动附加。可选使用 `${prettier}` 作为 Prettier 路径的占位符。例如:`docker compose exec -T app node_modules/.bin/prettier`。当指定时,优先于 `prettier.prettierPath`。",
"ext.config.printWidth": "每行代码的长度限制。",
"ext.config.proseWrap": "( Markdown ) 文本换行。",
"ext.config.quoteProps": "指定对象字面量中的属性名引号添加方式。\n可选项: \n- `as-needed` - 只在需要的情况下加引号。\n- `consistent` - 有一个需要引号就给其他都统一加上。\n - `preserve` - 保留用户输入的引号。",
Expand Down
1 change: 1 addition & 0 deletions package.nls.zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"ext.config.parser": "覆寫解析器。你不應變更這個設定。",
"ext.config.parserDeprecationMessage": "這個設定已不再支援。請改用 prettier 組態檔。",
"ext.config.prettierPath": "`prettier` 模組的路徑,如 `./node_modules/prettier`。",
"ext.config.customExecutable": "使用自訂可執行檔來執行 Prettier。指定完整命令及其參數。Prettier CLI 參數將自動附加。可選使用 `${prettier}` 作為 Prettier 路徑的佔位符。例如:`docker compose exec -T app node_modules/.bin/prettier`。當指定時,優先於 `prettier.prettierPath`。",
"ext.config.printWidth": "讓程式碼的每一列符合這個寬度限制。",
"ext.config.proseWrap": "( Markdown ) 把文句換行成多列。",
"ext.config.quoteProps": "變更物件屬性何時加上引號。\n可用的選項:\n- `\"as-needed\"` - 只在需要時加上引號。\n- `\"consistent\"` - 如果至少有一個屬性需要引號,則對所有屬性加上引號。\n- `\"preserve\"` - 保留輸入中屬性引號的使用方式。",
Expand Down
76 changes: 73 additions & 3 deletions src/ModuleResolverNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getWorkspaceRelativePath,
} from "./utils/workspace.js";
import { PrettierDynamicInstance } from "./PrettierDynamicInstance.js";
import { PrettierExecutableInstance } from "./PrettierExecutableInstance.js";

const minPrettierVersion = "1.13.0";

Expand Down Expand Up @@ -100,9 +101,17 @@ export class ModuleResolver implements ModuleResolverInterface {
return getBundledPrettier();
}

const { prettierPath, resolveGlobalModules } = getWorkspaceConfig(
Uri.file(fileName),
);
const { prettierPath, customExecutable, resolveGlobalModules } =
getWorkspaceConfig(Uri.file(fileName));

// If custom executable is specified, use it with highest priority
if (customExecutable) {
return this.getCustomExecutableInstance(
fileName,
customExecutable,
prettierPath,
);
}

// Look for local module
let modulePath: string | undefined;
Expand Down Expand Up @@ -160,6 +169,67 @@ export class ModuleResolver implements ModuleResolverInterface {
return prettierInstance;
}

private async getCustomExecutableInstance(
fileName: string,
customExecutable: string,
prettierPath: string | undefined,
): Promise<PrettierInstance | undefined> {
// Determine the prettier path for the custom executable
let resolvedPrettierPath: string;

if (prettierPath) {
// Use the explicitly provided prettierPath
const absolutePath = path.isAbsolute(prettierPath)
? prettierPath
: path.join(
workspace.getWorkspaceFolder(Uri.file(fileName))?.uri.fsPath ?? "",
prettierPath,
);
resolvedPrettierPath = absolutePath;
} else {
// Try to find prettier module automatically
const foundPath = await this.findPrettierModule(fileName);
if (foundPath) {
resolvedPrettierPath = foundPath;
} else {
// Fall back to just "prettier" and let the custom executable resolve it
resolvedPrettierPath = "prettier";
}
}

const cacheKey = `custom:${customExecutable}:${resolvedPrettierPath}`;

// Check cache
let prettierInstance = this.path2Module.get(cacheKey);
if (prettierInstance) {
return prettierInstance;
}

// Create new instance using PrettierExecutableInstance
prettierInstance = new PrettierExecutableInstance(
customExecutable,
resolvedPrettierPath,
);

// Import/validate the executable
try {
await prettierInstance.import();
this.loggingService.logInfo(
`Using custom executable: ${customExecutable} with prettier at ${resolvedPrettierPath}`,
);
} catch (error) {
this.loggingService.logError(
`Failed to initialize custom executable: ${customExecutable}`,
error,
);
return undefined;
}

this.path2Module.set(cacheKey, prettierInstance);

return prettierInstance;
}

private async getModuleFromPrettierPath(
fileName: string,
prettierPath: string,
Expand Down
Loading