Skip to content

Commit 93b17f7

Browse files
committed
feat(local-actions): enhance destination path handling and cleanup logic
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent 7c2f03b commit 93b17f7

4 files changed

Lines changed: 50 additions & 8 deletions

File tree

actions/local-actions/LocalActionsManager.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ export class LocalActionsManager {
1515
}
1616

1717
await mkdir(path.dirname(destinationPath), { recursive: true });
18-
await symlink(sourceDirectory, destinationPath, this.#getSymlinkType());
18+
const symlinkTarget = this.#resolveSymlinkTarget(
19+
sourceDirectory,
20+
destinationPath,
21+
);
22+
await symlink(symlinkTarget, destinationPath, this.#getSymlinkType());
1923

2024
return {
2125
created: true,
@@ -29,6 +33,7 @@ export class LocalActionsManager {
2933
}
3034

3135
await rm(destinationPath, { force: true, recursive: true });
36+
3237
return true;
3338
}
3439

@@ -37,8 +42,7 @@ export class LocalActionsManager {
3742
throw new Error("Workspace path is required.");
3843
}
3944

40-
const normalizedWorkspacePath = path.resolve(workspacePath);
41-
return path.resolve(normalizedWorkspacePath, "../self-actions");
45+
return path.resolve(workspacePath, "../self-actions");
4246
}
4347

4448
async resolveSourceDirectory({ sourcePath }) {
@@ -74,4 +78,18 @@ export class LocalActionsManager {
7478
#getSymlinkType() {
7579
return process.platform === "win32" ? "junction" : "dir";
7680
}
81+
82+
/**
83+
* Computes the symlink target. Uses a relative path on non-Windows
84+
* platforms so the symlink resolves correctly from both the container
85+
* filesystem and the host filesystem (bind-mount path differs).
86+
* Windows junctions require absolute targets.
87+
*/
88+
#resolveSymlinkTarget(sourceDirectory, destinationPath) {
89+
if (this.#getSymlinkType() === "junction") {
90+
return sourceDirectory;
91+
}
92+
93+
return path.relative(path.dirname(destinationPath), sourceDirectory);
94+
}
7795
}

actions/local-actions/LocalActionsManager.test.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
mkdtempSync,
55
mkdirSync,
66
readFileSync,
7+
readlinkSync,
78
realpathSync,
89
rmSync,
910
writeFileSync,
@@ -53,7 +54,7 @@ const createFixture = () => {
5354
};
5455
};
5556

56-
test("prepare creates a symlink to sibling actions in the destination", async () => {
57+
test("prepare creates a relative symlink to sibling actions in the destination", async () => {
5758
const fixture = createFixture();
5859
const manager = new LocalActionsManager();
5960

@@ -66,6 +67,16 @@ test("prepare creates a symlink to sibling actions in the destination", async ()
6667
assert.equal(result.created, true);
6768
assert.equal(result.destinationPath, fixture.selfActionsPath);
6869
assert.equal(lstatSync(fixture.selfActionsPath).isSymbolicLink(), true);
70+
71+
// Verify the symlink uses a relative target
72+
const linkTarget = readlinkSync(fixture.selfActionsPath);
73+
assert.equal(
74+
path.isAbsolute(linkTarget),
75+
false,
76+
`Expected relative symlink target, got: ${linkTarget}`,
77+
);
78+
79+
// Verify it resolves to the correct directory
6980
assert.equal(
7081
realpathSync(fixture.selfActionsPath),
7182
fixture.actionsDirectory,
@@ -141,6 +152,8 @@ test("cleanup removes the destination only when it was created by the action", a
141152
}),
142153
true,
143154
);
155+
assert.equal(existsSync(fixture.selfActionsPath), false);
156+
144157
assert.equal(
145158
await manager.cleanup({
146159
created: false,

actions/local-actions/cleanup.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ const manager = new LocalActionsManager();
66

77
try {
88
const destinationPath = runtime.getState("local_actions_destination_path");
9+
const created = runtime.getState("local_actions_created") === "true";
910
const cleaned = await manager.cleanup({
10-
created: runtime.getState("local_actions_created") === "true",
11+
created,
1112
destinationPath,
1213
});
1314

actions/local-actions/index.test.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
mkdtempSync,
55
mkdirSync,
66
readFileSync,
7+
readlinkSync,
78
realpathSync,
89
rmSync,
910
writeFileSync,
@@ -70,7 +71,7 @@ const runNodeScript = (scriptPath, env) =>
7071
},
7172
});
7273

73-
test("index.js writes outputs and creates the local actions symlink", () => {
74+
test("index.js writes outputs, creates a relative symlink, and saves state", () => {
7475
const fixture = createFixture();
7576

7677
try {
@@ -95,6 +96,15 @@ test("index.js writes outputs and creates the local actions symlink", () => {
9596
),
9697
);
9798
assert.equal(lstatSync(fixture.selfActionsPath).isSymbolicLink(), true);
99+
100+
// Verify relative symlink
101+
const linkTarget = readlinkSync(fixture.selfActionsPath);
102+
assert.equal(
103+
path.isAbsolute(linkTarget),
104+
false,
105+
`Expected relative symlink target, got: ${linkTarget}`,
106+
);
107+
98108
assert.equal(
99109
realpathSync(fixture.selfActionsPath),
100110
fixture.actionsDirectory,
@@ -111,11 +121,11 @@ test("index.js writes outputs and creates the local actions symlink", () => {
111121
);
112122
assert.match(
113123
readFileSync(fixture.stateFile, "utf8"),
114-
/^local_actions_created<<.+\ntrue\n.+\n/s,
124+
/local_actions_created<<.+\ntrue\n.+\n/s,
115125
);
116126
assert.match(
117127
readFileSync(fixture.stateFile, "utf8"),
118-
/^local_actions_created<<.+\ntrue\n.+\nlocal_actions_destination_path<<.+\n.*self-actions\n.+\n$/s,
128+
/local_actions_destination_path<<.+\n.*self-actions\n.+\n/s,
119129
);
120130
} finally {
121131
fixture.teardown();

0 commit comments

Comments
 (0)