Skip to content

Commit 080a661

Browse files
authored
fix: path resolution problems in Windows (#69)
2 parents 289214d + 07f4763 commit 080a661

11 files changed

Lines changed: 147 additions & 67 deletions

File tree

.github/workflows/ci.yml

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ permissions:
1010

1111
jobs:
1212
check:
13-
name: Lint, Test & Typecheck
13+
name: Lint & Typecheck
1414
runs-on: ubuntu-latest
1515

1616
steps:
@@ -28,8 +28,28 @@ jobs:
2828
- name: Lint
2929
run: npm run lint
3030

31-
- name: Test
32-
run: npm test
33-
3431
- name: Typecheck
3532
run: npx tsc --noEmit
33+
34+
test:
35+
name: Test (${{ matrix.os }})
36+
runs-on: ${{ matrix.os }}
37+
strategy:
38+
fail-fast: false
39+
matrix:
40+
os: [ubuntu-latest, windows-latest]
41+
42+
steps:
43+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
44+
45+
- name: Setup Node.js
46+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
47+
with:
48+
node-version-file: package.json
49+
cache: npm
50+
51+
- name: Install dependencies
52+
run: npm ci
53+
54+
- name: Test
55+
run: npm test

package-lock.json

Lines changed: 1 addition & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bundler/esbuild.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as os from "os";
21
import path from "path";
32
import type { OnLoadArgs, OnResolveArgs, PluginBuild } from "esbuild";
43
import { build } from "esbuild";
@@ -7,21 +6,15 @@ import type { ICompilerResult } from "../definition";
76
import type { IBundledCompilerResult } from "../definition/ICompilerResult";
87
import type { AppsEngineValidator } from "../compiler/AppsEngineValidator";
98

10-
const isWin = os.platform() === "win32";
11-
129
function normalizeAppModulePath(modulePath: string, parentDir: string): string {
1310
const isRelative = /\.\.?\//.test(modulePath);
1411

1512
if (!isRelative) {
1613
return modulePath;
1714
}
1815

19-
const baseDir = path.dirname(parentDir);
20-
const resolvedPath = isWin
21-
? path.join(baseDir, modulePath)
22-
: path.resolve("/", baseDir, modulePath).substring(1);
23-
24-
return `${resolvedPath}.js`;
16+
const baseDir = path.posix.dirname(parentDir);
17+
return `${path.posix.join(baseDir, modulePath)}.js`;
2518
}
2619

2720
export async function bundleCompilation(
@@ -70,13 +63,10 @@ export async function bundleCompilation(
7063

7164
if (isRelative) {
7265
// normalize into the key you used in r.files
73-
let modulePath = normalizeAppModulePath(
66+
const modulePath = normalizeAppModulePath(
7467
args.path,
7568
args.importer,
7669
);
77-
modulePath = modulePath
78-
.replace(/^:\\/, "")
79-
.replace(/\\/g, "/");
8070
if (r.files[modulePath]) {
8171
return {
8272
namespace: "app-source",

src/compiler/AppsEngineValidator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export class AppsEngineValidator {
114114
filename: string,
115115
compilationResult: ICompilerResult,
116116
): any {
117+
filename = filename.replace(/\\/g, "/");
117118
const exports = {};
118119
const context = vm.createContext({
119120
require: (filepath: string) => {
@@ -135,8 +136,8 @@ export class AppsEngineValidator {
135136
return undefined;
136137
}
137138

138-
filepath = path.normalize(
139-
path.join(path.dirname(filename), filepath),
139+
filepath = path.posix.normalize(
140+
path.posix.join(path.posix.dirname(filename), filepath),
140141
);
141142

142143
// Handles import of other files in app's source

src/compiler/TscBasedCompiler.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ export class TscBasedCompiler {
3939
}: IAppSource): Promise<ICompilerResult> {
4040
const startTime = Date.now();
4141

42+
const posixClassFile = appInfo.classFile.replace(/\\/g, "/");
43+
4244
// Entry‐file must exist
43-
if (!appInfo.classFile || !sourceFiles[appInfo.classFile]) {
45+
if (!posixClassFile || !sourceFiles[posixClassFile]) {
4446
throw new Error(
4547
`Invalid App package. Could not find the classFile (${appInfo.classFile}).`,
4648
);
@@ -211,7 +213,7 @@ export class TscBasedCompiler {
211213
withFileTypes: true,
212214
})) {
213215
const full = path.join(dir, entry.name);
214-
const rel = path.join(base, entry.name);
216+
const rel = path.posix.join(base, entry.name);
215217
if (entry.isDirectory()) {
216218
await collect(full, rel);
217219
} else if (entry.isFile() && rel.endsWith(".js")) {
@@ -228,21 +230,21 @@ export class TscBasedCompiler {
228230
await collect(path.join(this.sourcePath, BUILD_DIR));
229231

230232
// Point at the main JS file
231-
const mainJs = appInfo.classFile.replace(/\.ts$/, ".js");
233+
const mainJs = posixClassFile.replace(/\.ts$/, ".js");
232234
result.mainFile = result.files[mainJs];
233235

234236
// Extract implemented interfaces from the TS AST
235237
const srcNode = TS.createSourceFile(
236-
appInfo.classFile,
237-
sourceFiles[appInfo.classFile].content,
238+
posixClassFile,
239+
sourceFiles[posixClassFile].content,
238240
TS.ScriptTarget.Latest,
239241
true,
240242
);
241243
result.implemented = this.extractInterfaces(srcNode);
242244

243245
// Run inheritance checks
244246
this.appValidator.checkInheritance(
245-
appInfo.classFile.replace(/\.ts$/, ""),
247+
posixClassFile.replace(/\.ts$/, ""),
246248
result,
247249
);
248250

src/compiler/TypescriptCompiler.ts

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { EventEmitter } from "events";
22
import fs from "fs";
33
import path from "path";
44

5-
import type { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata";
65
import type {
76
CompilerOptions,
87
EmitOutput,
@@ -59,10 +58,24 @@ export class TypescriptCompiler {
5958
appInfo,
6059
sourceFiles: files,
6160
}: IAppSource): ICompilerResult {
61+
// Normalize all file keys, file names, and classFile to POSIX separators
62+
// once up front so getScriptFileNames(), getScriptVersion(), getScriptSnapshot(),
63+
// and all subsequent lookups into result.files operate on consistent keys.
64+
const posixClassFile = appInfo.classFile.replace(/\\/g, "/");
65+
const normalizedFiles: IMapCompilerFile = {};
66+
for (const [key, file] of Object.entries(files)) {
67+
const posixKey = key.replace(/\\/g, "/");
68+
normalizedFiles[posixKey] = {
69+
...file,
70+
name: file.name.replace(/\\/g, "/"),
71+
};
72+
}
73+
files = normalizedFiles;
74+
6275
if (
63-
!appInfo.classFile ||
64-
!files[appInfo.classFile] ||
65-
!this.isValidFile(files[appInfo.classFile])
76+
!posixClassFile ||
77+
!files[posixClassFile] ||
78+
!this.isValidFile(files[posixClassFile])
6679
) {
6780
throw new Error(
6881
`Invalid App package. Could not find the classFile (${appInfo.classFile}) file.`,
@@ -91,7 +104,9 @@ export class TypescriptCompiler {
91104
throw new Error(`Invalid TypeScript file: "${key}".`);
92105
}
93106

94-
result.files[key].name = path.normalize(result.files[key].name);
107+
result.files[key].name = path.posix.normalize(
108+
result.files[key].name,
109+
);
95110
});
96111

97112
let hasExternalDependencies = false;
@@ -121,13 +136,13 @@ export class TypescriptCompiler {
121136
const host = {
122137
getScriptFileNames: () => Object.keys(result.files),
123138
getScriptVersion: (fileName) => {
124-
fileName = path.normalize(fileName);
139+
fileName = path.posix.normalize(fileName.replace(/\\/g, "/"));
125140
const file =
126141
result.files[fileName] || this.getLibraryFile(fileName);
127142
return file?.version?.toString();
128143
},
129144
getScriptSnapshot: (fileName) => {
130-
fileName = path.normalize(fileName);
145+
fileName = path.posix.normalize(fileName.replace(/\\/g, "/"));
131146
const file =
132147
result.files[fileName] || this.getLibraryFile(fileName);
133148

@@ -217,7 +232,7 @@ export class TypescriptCompiler {
217232

218233
result.implemented = this.getImplementedInterfaces(
219234
languageService,
220-
appInfo,
235+
posixClassFile,
221236
);
222237

223238
Object.defineProperty(result, "diagnostics", {
@@ -240,11 +255,10 @@ export class TypescriptCompiler {
240255
file.compiled = output.outputFiles[0].text;
241256
});
242257

243-
result.mainFile =
244-
result.files[appInfo.classFile.replace(/\.ts$/, ".js")];
258+
result.mainFile = result.files[posixClassFile.replace(/\.ts$/, ".js")];
245259

246260
this.appValidator.checkInheritance(
247-
appInfo.classFile.replace(/\.ts$/, ""),
261+
posixClassFile.replace(/\.ts$/, ""),
248262
result,
249263
);
250264

@@ -317,10 +331,20 @@ export class TypescriptCompiler {
317331
}
318332

319333
private resolvePath(containingFile: string, moduleName: string): string {
320-
const currentFolderPath = path
321-
.dirname(containingFile)
322-
.replace(this.sourcePath.replace(/\/$/, ""), "");
323-
const modulePath = path.join(currentFolderPath, moduleName);
334+
const posixSourcePath = this.sourcePath
335+
.replace(/\\/g, "/")
336+
.replace(/\/$/, "");
337+
const posixContainingDir = path.posix.dirname(
338+
containingFile.replace(/\\/g, "/"),
339+
);
340+
const currentFolderPath = posixContainingDir.replace(
341+
posixSourcePath,
342+
"",
343+
);
344+
const modulePath = path.posix.join(
345+
currentFolderPath || ".",
346+
moduleName,
347+
);
324348

325349
// Let's ensure we search for the App's modules first
326350
const transformedModule =
@@ -332,13 +356,11 @@ export class TypescriptCompiler {
332356

333357
private getImplementedInterfaces(
334358
languageService: LanguageService,
335-
appInfo: IAppInfo,
359+
classFile: string,
336360
): ICompilerResult["implemented"] {
337361
const result: ICompilerResult["implemented"] = [];
338362

339-
const src = languageService
340-
.getProgram()
341-
.getSourceFile(appInfo.classFile);
363+
const src = languageService.getProgram().getSourceFile(classFile);
342364

343365
this.ts.forEachChild(src, (n) => {
344366
if (!this.ts.isClassDeclaration(n)) {
@@ -370,7 +392,7 @@ export class TypescriptCompiler {
370392
return undefined;
371393
}
372394

373-
const norm = path.normalize(fileName);
395+
const norm = path.posix.normalize(fileName.replace(/\\/g, "/"));
374396

375397
if (this.libraryFiles[norm]) {
376398
return this.libraryFiles[norm];
@@ -394,10 +416,6 @@ export class TypescriptCompiler {
394416
return false;
395417
}
396418

397-
return (
398-
file.name.trim() !== "" &&
399-
path.normalize(file.name) &&
400-
file.content.trim() !== ""
401-
);
419+
return file.name.trim() !== "" && file.content.trim() !== "";
402420
}
403421
}

0 commit comments

Comments
 (0)