Declared `packageDirectory` reached through a symlink silently contributes zero components to a deploy
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 68/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- node.js, typescript
Hướng nghiên cứu
Start in src/shared/local/localShadowRepo.ts, especially the packageDirectories mapping and git.statusMatrix call, then run the supplied repro.mjs with isomorphic-git 1.38.9 and 1.41.7 to confirm the walker behavior. Trace the fs wrapper and relevant source-tracking tests or entry points before choosing an approach; done means files under a symlinked packageDirectory are included in status and deploy processing rather than silently omitted.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
A packageDirectory declared in sfdx-project.json and reached through a symlink contributes no components to a deploy. No error, no warning, and the deploy reports Succeeded. The metadata never arrives and the failure surfaces later as something unrelated.
@salesforce/source-tracking passes declared packageDirectories to isomorphic-git's working-directory walker and relies on that walker descending symlinks. isomorphic-git stopped descending them in 1.38.10. The dependency is declared ^1.34.2, so this arrives on a fresh install with no CLI version change.
Reproduction
This script will generate a simple dx project setup and then call git.statusMatrix to show how different versions of isomorphic-git handle symlinks. Save as repro.mjs in an empty directory:
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import git from 'isomorphic-git';
// A throwaway project: one package dir reached through a symlink to an ordinary
// sibling directory, and one ordinary package dir as a control.
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sf-symlink-repro-'));
const linkedTarget = path.join(root, 'shared-lib', 'force-app', 'main', 'default', 'classes');
fs.mkdirSync(linkedTarget, { recursive: true });
fs.writeFileSync(path.join(linkedTarget, 'Linked.cls'), 'public class Linked {}');
fs.mkdirSync(path.join(root, 'links'));
fs.symlinkSync(path.join('..', 'shared-lib'), path.join(root, 'links', 'shared-lib'), 'dir');
const controlDir = path.join(root, 'force-app', 'main', 'default', 'classes');
fs.mkdirSync(controlDir, { recursive: true });
fs.writeFileSync(path.join(controlDir, 'Control.cls'), 'public class Control {}');
// Mirror what ShadowRepo does: init a shadow repo rooted at the project, then ask
// for the status of each declared packageDirectory.
const gitdir = path.join(root, '.shadow');
await git.init({ fs, dir: root, gitdir, defaultBranch: 'main' });
const status = async (filepath) =>
(await git.statusMatrix({ fs, dir: root, gitdir, filepaths: [filepath] })).map((row) => row[0]);
console.log(`isomorphic-git ${git.version()}`);
console.log(` symlinked packageDirectory 'links/shared-lib/force-app':`);
console.log(` ${(await status('links/shared-lib/force-app')).join('\n ') || '(nothing)'}`);
console.log(` control packageDirectory 'force-app':`);
console.log(` ${(await status('force-app')).join('\n ') || '(nothing)'}`);
fs.rmSync(root, { recursive: true, force: true });
npm i [email protected] && node repro.mjs
npm i [email protected] && node repro.mjs
Output
isomorphic-git 1.38.9
symlinked packageDirectory 'links/shared-lib/force-app':
links/shared-lib
links/shared-lib/force-app/main/default/classes/Linked.cls
control packageDirectory 'force-app':
force-app/main/default/classes/Control.cls
isomorphic-git 1.41.7
symlinked packageDirectory 'links/shared-lib/force-app':
links/shared-lib
control packageDirectory 'force-app':
force-app/main/default/classes/Control.cls
Under 1.41.7 the only entry for the symlinked package directory is the symlink itself, typed as a blob. Linked.cls is gone.
1.38.9 is the last good version. 1.38.10, 1.39.x, 1.40.0 and 1.41.7 all reproduce.
I can put together a full sfdx-project reproduction if the script above isn't enough.
Cause
ShadowRepo in @salesforce/source-tracking passes packageDirs to isomorphic-git, relying on its working directory walker to descend into subfolders and find all the files to report on. The folders passed may contain symlinks that are not resolved to real paths. And so is relying on how isomorphic-git handles symlinks. This behavior changed in 1.38.10 and so how Salesforce-cli handles symlinks is now at the mercy of whatever version of isomorphic-git you get installed. (The declared range includes both good and bad versions.)
ShadowRepo (src/shared/local/localShadowRepo.ts) rebases each packageDirectory to a project-relative path and relies on the working-directory walker to enumerate files:
this.packageDirs = options.packageDirs.map(packageDirToRelativePosixPath(options.projectPath));
// ...
this.status = await git.statusMatrix({
fs,
dir: this.projectPath,
gitdir: this.gitDir,
filepaths: this.packageDirs,
filter: fileFilter(this.packageDirs),
});
In isomorphic-git's GitWalkerFs, entry types come from lstat, which describes the link rather than its target, so a symlinked directory types as blob (a regular directory has type tree).
1.38.10 added a guard making readdir respect that type:
async readdir(entry) {
if ((await entry.type()) !== 'tree') return null // added in 1.38.10
const filepath = entry._fullpath;
const names = await fs.readdir(join(dir, filepath));
fs.readdir does follow symlinks, so before 1.38.10 the walker descended symlinked directories regardless of type. But git does not follow symlinks. 1.38.10 closed isomorphic-git#1215, which is a legit discrepancy between the behavior of git and isomorphic-git.
The shadow repo is using git as a change detector over directories the user explicitly declared. A symlinked package directory is a pointer to files the user asked to have tracked. That distinction has to be made in source-tracking.
This is not an isomorphic-git bug.
Where it showed up
From a failed deploy log:
CustomField Budget__c.Eligible_for_Billing__c
Field $Label.common_label_yes does not exist. Check spelling. (648:13)
The label exists and is spelled correctly. It lives in a labels file in a symlinked package directory, so the label is never deployed and a field referencing it in a later package directory failed. Nothing in the output pointed at the package directory with the symlink and the label, which is where the problem actually occurred.
sfdx-project.json declares:
{ "path": "sf-lib-links/psenterprise-lib/force-app", "default": false }
where sf-lib-links/psenterprise-lib is a symlink to a sibling package in the same repo. This is an ordinary layout for a monorepo with shared library code.
Possible fixes
As we pass fs into isomorphic-git we have the opportunity to wrap it. We could wrap it so that it sees symlinks as type tree (isDirectory() === true) and the walker will descend into them. This uses isomorphic-git's own extension point to get the behavior we want. I have validated this approach is viable.
(We can't resolve declared packageDirectories to their real paths before walking them. Looking at the parameters we pass to statusMatrix, filepaths is relative to dir- a resolved symlink frequently points outside dir, so there's no relative path you could pass.)
Other ideas:
- We could pin the isomorphic-git dependency. The range
^1.34.2allowed a transitive behavioral change to alter source-tracking behavior with no release involved. However it doesn't give a long-term fix for sharing code via symlinks in dx projects. - Warn when a declared
packageDirectorycontributes zero components. This would make the investigation easier, as the error above is pointing in completely the wrong place.
System information
{
"architecture": "linux-x64",
"cliVersion": "@salesforce/cli/2.150.6",
"nodeVersion": "node-v24.19.0",
"osVersion": "Linux 6.1.182",
"rootPath": "/home/jenkins/workspace/9-investigate-sf-2.150.6-upgrade/common/temp/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected]/node_modules/@salesforce/cli",
"shell": "sh",
"pluginVersions": [
"@oclif/plugin-autocomplete 3.3.0 (core)",
"@oclif/plugin-commands 4.2.0 (core)",
"@oclif/plugin-help 6.3.0 (core)",
"@oclif/plugin-not-found 3.3.0 (core)",
"@oclif/plugin-plugins 5.5.1 (core)",
"@oclif/plugin-search 1.3.0 (core)",
"@oclif/plugin-update 4.8.0 (core)",
"@oclif/plugin-version 2.3.0 (core)",
"@oclif/plugin-warn-if-update-available 3.2.0 (core)",
"@oclif/plugin-which 3.3.0 (core)",
"@salesforce/cli 2.150.6 (core)",
"agent 2.0.5 (core)",
"apex 4.1.1 (core)",
"api 2.0.9 (core)",
"auth 5.0.6 (core)",
"data 5.1.7 (core)",
"deploy-retrieve 4.1.2 (core)",
"info 4.0.9 (core)",
"limits 4.0.4 (core)",
"marketplace 2.0.5 (core)",
"org 6.0.11 (core)",
"packaging 3.0.6 (core)",
"schema 4.0.6 (core)",
"settings 3.0.6 (core)",
"sobject 2.0.5 (core)",
"telemetry 4.0.6 (core)",
"templates 57.0.11 (core)",
"trust 4.0.10 (core)",
"user 5.0.2 (core)"
]
}
Dependency chain: @salesforce/[email protected] → @salesforce/[email protected] →
isomorphic-git: "^1.34.2" → resolves to 1.41.7 on a fresh install.
- Ngôn ngữ chính
- Không có dữ liệu ngôn ngữ
- Star
- 571
- Fork
- 80
- Merge trung bình
- 2 ngày 21 giờ
- Pull request đã merge (30 ngày)
- 3
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của forcedotcom/cli
-
investigating validated
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
forcedotcom/cli#3657 · 2 bình luận ·
-
`sf agent preview` fails with `AgentApiNotFound` against staging (aws-stage1) orgs — `stage.api.salesforce.com` missing from endpoint fallbackCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mởarea:afdx owned by another team
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
forcedotcom/cli#3645 · 2 bình luận ·
-
bug investigating validated
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
forcedotcom/cli#3644 · 6 bình luận ·
-
sf agent mcp asset replace --assets null throws raw TypeError instead of InvalidShapeCó thể đã có người làm @konkonrong-lgtm đã nhận 54 ngày trước. Đang mởarea:afdx bug investigating owned by another team validated
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
forcedotcom/cli#3625 · 4 bình luận ·
-
area:afdx bug owned by another team
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
forcedotcom/cli#3608 · 2 bình luận ·
Tất cả issue của forcedotcom/cli
Issue tương tự
-
Benchmark runner exits 0 after failed compiler runsCó thể đã có người làm @ArkashJ đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
-
Date converter silently normalizes invalid calendar datesCó thể đã có người làm @PHJ2000 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 80/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
nunomaduro/collision#371 ·
-
[BUG] `mo clean` deletes DiagnosticReports directory and breaks crash reportingCó thể đã có người làm @md786-dotcom đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày