Skip to content

Commit

Permalink
fix: mapped fs with aboslute paths
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelstroschein committed Sep 7, 2024
1 parent b5419e0 commit 988aa28
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 12 deletions.
115 changes: 114 additions & 1 deletion inlang/source-code/sdk2/src/project/loadProjectFromDirectory.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { expect, test } from "vitest";
import { expect, test, vi } from "vitest";
import { ProjectSettings } from "../json-schema/settings.js";
import { Volume } from "memfs";
import {
Expand Down Expand Up @@ -268,3 +269,115 @@ test("it should provide plugins from disk for backwards compatibility but warn t
// else roundtrips would not work
expect(settings.modules?.[0]).toBe("../local-plugins/mock-plugin.js");
});

// https://github.com/opral/inlang-sdk/issues/174
test("plugin calls that use fs should be intercepted to use an absolute path", async () => {
process.cwd = () => "/";

const mockRepo = {
"/inlang/development-projects/inlang-nextjs/app/i18n/locales/en.json":
JSON.stringify({
key1: "value1",
key2: "value2",
}),
"/inlang/development-projects/inlang-nextjs/other-folder/backend.inlang/settings.json":
JSON.stringify({
baseLocale: "en",
locales: ["en", "de"],
"plugin.mock-plugin": {
pathPattern: "./../../app/i18n/locales/{locale}.json",
},
} satisfies ProjectSettings),
};

const mockPlugin: InlangPlugin = {
key: "mock-plugin",
loadMessages: async ({ nodeishFs, settings }) => {
const pathPattern = settings["plugin.mock-plugin"]?.pathPattern.replace(
"{locale}",
"en"
) as string;
const file = await nodeishFs.readFile(pathPattern);
// reading the file should be possible without an error
expect(file.toString()).toBe(
JSON.stringify({
key1: "value1",
key2: "value2",
})
);
return [];
},
saveMessages: async ({ nodeishFs, settings }) => {
const pathPattern = settings["plugin.mock-plugin"]?.pathPattern.replace(
"{locale}",
"en"
) as string;
const file = new TextEncoder().encode(
JSON.stringify({
key1: "value1",
key2: "value2",
key3: "value3",
})
);
await nodeishFs.writeFile(pathPattern, file);
},
toBeImportedFiles: async ({ settings, nodeFs }) => {
const pathPattern = settings["plugin.mock-plugin"]?.pathPattern.replace(
"{locale}",
"en"
) as string;
const file = await nodeFs.readFile(pathPattern);
// reading the file should be possible without an error
expect(file.toString()).toBe(
JSON.stringify({
key1: "value1",
key2: "value2",
})
);
return [];
},
};

const fs = Volume.fromJSON(mockRepo).promises;

const loadMessagesSpy = vi.spyOn(mockPlugin, "loadMessages");
const saveMessagesSpy = vi.spyOn(mockPlugin, "saveMessages");
const toBeImportedFilesSpy = vi.spyOn(mockPlugin, "toBeImportedFiles");
const fsReadFileSpy = vi.spyOn(fs, "readFile");
const fsWriteFileSpy = vi.spyOn(fs, "writeFile");

const project = await loadProjectFromDirectoryInMemory({
fs: fs as any,
path: "/inlang/development-projects/inlang-nextjs/other-folder/backend.inlang",
providePlugins: [mockPlugin],
});

expect(loadMessagesSpy).toHaveBeenCalled();
expect(fsReadFileSpy).toHaveBeenCalledWith("/messages/en.json");

// todo test that saveMessages works too.
// await project.db.insertInto("bundle").defaultValues().execute();

// const translationFile = await fs.readFile("/messages/en.json", "utf-8");

// expect(translationFile).toBe(
// JSON.stringify({
// key1: "value1",
// key2: "value2",
// key3: "value3",
// })
// );

// expect(fsWriteFileSpy).toHaveBeenCalledWith(
// "/messages/en.json",
// JSON.stringify({
// key1: "value1",
// key2: "value2",
// key3: "value3",
// }),
// "utf-8"
// );

// expect(saveMessagesSpy).toHaveBeenCalled();
// expect(toBeImportedFilesSpy).toHaveBeenCalled();
});
78 changes: 67 additions & 11 deletions inlang/source-code/sdk2/src/project/loadProjectFromDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { uuidv4, type Lix } from "@lix-js/sdk";
import type fs from "node:fs/promises";
// eslint-disable-next-line no-restricted-imports
import nodePath from "node:path";
import type { InlangPlugin } from "../plugin/schema.js";
import type {
InlangPlugin,
NodeFsPromisesSubsetLegacy,
} from "../plugin/schema.js";
import { insertBundleNested } from "../query-utilities/insertBundleNested.js";
import { fromMessageV1 } from "../json-schema/old-v1-message/fromMessageV1.js";
import type { ProjectSettings } from "../json-schema/settings.js";
Expand Down Expand Up @@ -102,7 +105,7 @@ export async function loadProjectFromDirectoryInMemory(

await project.importFiles({
pluginKey: importer.key,
files,
files: files.map((file) => ({ ...file, pluginKey: importer.key })),
});

// TODO check user id and description (where will this one appear?)
Expand All @@ -116,6 +119,7 @@ export async function loadProjectFromDirectoryInMemory(
if (chosenLegacyPlugin) {
await loadLegacyMessages({
project,
projectPath: args.path,
fs: args.fs,
pluginKey: chosenLegacyPlugin.key ?? chosenLegacyPlugin.id,
loadMessagesFn: chosenLegacyPlugin.loadMessages,
Expand Down Expand Up @@ -154,11 +158,13 @@ async function loadLegacyMessages(args: {
project: Awaited<ReturnType<typeof loadProjectInMemory>>;
pluginKey: NonNullable<InlangPlugin["key"] | InlangPlugin["id"]>;
loadMessagesFn: Required<InlangPlugin>["loadMessages"];
projectPath: string;
fs: typeof fs;
}) {
const loadedLegacyMessages = await args.loadMessagesFn({
settings: await args.project.settings.get(),
nodeishFs: args.fs,
// @ts-ignore
nodeishFs: withAbsolutePaths(args.fs, args.projectPath),
});
const insertQueries = [];

Expand Down Expand Up @@ -283,13 +289,7 @@ async function importLocalPlugins(args: {
await args.fs.readFile(settingsPath, "utf8")
) as ProjectSettings;
for (const module of settings.modules ?? []) {
// need to remove the project path from the module path for legacy reasons
// "/project.inlang/local-plugins/mock-plugin.js" -> "/local-plugins/mock-plugin.js"
const pathWithoutProject = args.path
.split(nodePath.sep)
.slice(0, -1)
.join(nodePath.sep);
const modulePath = nodePath.join(pathWithoutProject, module);
const modulePath = absolutePathFromProject(args.path, module);
try {
let moduleAsText = await args.fs.readFile(modulePath, "utf8");
if (moduleAsText.includes("messageLintRule")) {
Expand Down Expand Up @@ -344,4 +344,60 @@ export class WarningDeprecatedLintRule extends Error {
);
this.name = "WarningDeprecatedLintRule";
}
}
}

/**
* Resolving absolute paths for fs functions.
*
* This mapping is required for backwards compatibility.
* Relative paths in the project.inlang/settings.json
* file are resolved to absolute paths with `*.inlang`
* being pruned.
*
* @example
* "/website/project.inlang"
* "./local-plugins/mock-plugin.js"
* -> "/website/local-plugins/mock-plugin.js"
*
*/
function withAbsolutePaths(
fs: NodeFsPromisesSubsetLegacy,
projectPath: string
): NodeFsPromisesSubsetLegacy {
return {
// @ts-expect-error
readFile: (path, options) => {
return fs.readFile(absolutePathFromProject(projectPath, path), options);
},
writeFile: (path, data) => {
return fs.writeFile(absolutePathFromProject(projectPath, path), data);
},
mkdir: (path) => {
return fs.mkdir(absolutePathFromProject(projectPath, path));
},
readdir: (path) => {
return fs.readdir(absolutePathFromProject(projectPath, path));
},
};
}

/**
* Joins a path from a project path.
*
* @example
* joinPathFromProject("/project.inlang", "./local-plugins/mock-plugin.js") -> "/local-plugins/mock-plugin.js"
*
* joinPathFromProject("/website/project.inlang", "./mock-plugin.js") -> "/website/mock-plugin.js"
*/
function absolutePathFromProject(projectPath: string, path: string) {
// need to remove the project path from the module path for legacy reasons
// "/project.inlang/local-plugins/mock-plugin.js" -> "/local-plugins/mock-plugin.js"
const pathWithoutProject = projectPath
.split(nodePath.sep)
.slice(0, -1)
.join(nodePath.sep);

const resolvedPath = nodePath.resolve(pathWithoutProject, path);

return resolvedPath;
}

0 comments on commit 988aa28

Please sign in to comment.