@messagevisor/core 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -181,9 +181,7 @@ describe("createPlugin", function () {
181
181
  expect(summary.requestedKeys).toEqual(["auth.signin", "auth.signout"]);
182
182
  expect(summary.createdKeys).toEqual(["auth.signout"]);
183
183
  expect(summary.skippedKeys).toEqual(["auth.signin"]);
184
- expect(summary.createdFilePaths).toEqual([
185
- path.relative(process.cwd(), path.join(root, "messages/auth/signout.yml")),
186
- ]);
184
+ expect(summary.createdFilePaths).toEqual([path.join("messages", "auth", "signout.yml")]);
187
185
  logSpy.mockRestore();
188
186
  });
189
187
 
@@ -4,6 +4,7 @@ import type { Attribute, Locale, Message, Target, Segment } from "@messagevisor/
4
4
 
5
5
  import type { Plugin } from "../cli";
6
6
  import { MessagevisorCLIError, printMessagevisorCLIError } from "../error";
7
+ import { formatProjectPath } from "../path";
7
8
  import { getProjectSetExecutions } from "../sets";
8
9
 
9
10
  type CreateEntityType = "messages" | "locales" | "targets" | "attributes" | "segments";
@@ -224,7 +225,7 @@ async function createDefinitions(
224
225
  await writeEntity(datasource, entityType, key);
225
226
  createdKeys.push(key);
226
227
  createdFilePaths.push(
227
- path.relative(process.cwd(), getEntityFilePath(projectConfig, entityType, key)),
228
+ formatProjectPath(projectConfig, getEntityFilePath(projectConfig, entityType, key)),
228
229
  );
229
230
  existingKeys.add(key);
230
231
  }
@@ -7,6 +7,7 @@ import type { Locale, Message, Override, Translation } from "@messagevisor/types
7
7
  import type { ProjectConfig } from "../config";
8
8
  import type { Datasource } from "../datasource";
9
9
  import { MessagevisorCLIError, printMessagevisorCLIError } from "../error";
10
+ import { formatProjectPath } from "../path";
10
11
  import { getProjectSetExecutions } from "../sets";
11
12
  import {
12
13
  CLI_FORMAT_BOLD,
@@ -1091,12 +1092,12 @@ export async function importProjectSets(
1091
1092
  );
1092
1093
  }
1093
1094
 
1094
- function printImportResult(result: ImportProjectResult) {
1095
+ function printImportResult(projectConfig: ProjectConfig, result: ImportProjectResult) {
1095
1096
  console.log("");
1096
1097
  console.log(CLI_FORMAT_BOLD, "Importing Messagevisor translations");
1097
1098
  const inputLabel = isHttpUrl(result.inputFilePath)
1098
1099
  ? result.inputFilePath
1099
- : path.relative(process.cwd(), result.inputFilePath);
1100
+ : formatProjectPath(projectConfig, result.inputFilePath);
1100
1101
  console.log(` Input: ${colorize(inputLabel, 36)}`);
1101
1102
  console.log(` Mode: ${result.apply ? "apply" : "preview"}`);
1102
1103
  if (result.summary.sets.length > 0) {
@@ -1152,7 +1153,7 @@ export const importPlugin = {
1152
1153
  parsed,
1153
1154
  );
1154
1155
 
1155
- printImportResult(result);
1156
+ printImportResult(projectConfig, result);
1156
1157
  } catch (error) {
1157
1158
  if (printMessagevisorCLIError(error)) {
1158
1159
  return false;
package/src/index.ts CHANGED
@@ -15,5 +15,6 @@ export * from "./info";
15
15
  export * from "./importer";
16
16
  export * from "./init";
17
17
  export * from "./linter";
18
+ export * from "./path";
18
19
  export * from "./promoter";
19
20
  export * from "./tester";
@@ -52,6 +52,7 @@ describe("lintProject", function () {
52
52
  result.errors.some(
53
53
  (error) =>
54
54
  error.entityType === "locale" &&
55
+ error.filePath === path.join("locales", "en.yml") &&
55
56
  error.code === "unrecognized_keys" &&
56
57
  error.message.includes("unknown"),
57
58
  ),
@@ -21,6 +21,7 @@ import {
21
21
  getProjectSetExecutions,
22
22
  getProjectSetRelativeFilePath,
23
23
  } from "../sets";
24
+ import { formatProjectPath, getProjectRootDirectoryPath } from "../path";
24
25
  import { CLI_FORMAT_BOLD, CLI_FORMAT_GREEN, CLI_FORMAT_RED, colorize } from "../tester/cliFormat";
25
26
  import { prettyDuration } from "../tester/prettyDuration";
26
27
 
@@ -102,7 +103,7 @@ function getFullPathFromKey(projectConfig: ProjectConfig, entityType: LintEntity
102
103
  : path.join(projectConfig.testsDirectoryPath, fileName);
103
104
  }
104
105
 
105
- return path.join(process.cwd(), "messagevisor.config.js");
106
+ return path.join(getProjectRootDirectoryPath(projectConfig), "messagevisor.config.js");
106
107
  }
107
108
 
108
109
  async function readAll<T>(
@@ -147,7 +148,7 @@ export async function lintProject(
147
148
  ) {
148
149
  for (const issue of getLintIssuesFromZodError(error)) {
149
150
  recordError({
150
- filePath: path.relative(process.cwd(), fullPath),
151
+ filePath: formatProjectPath(projectConfig, fullPath),
151
152
  entityType,
152
153
  entityKey: key,
153
154
  message: issue.message,
@@ -168,7 +169,7 @@ export async function lintProject(
168
169
 
169
170
  if (!isValidEntityKey(projectConfig, key)) {
170
171
  recordError({
171
- filePath: path.relative(process.cwd(), fullPath),
172
+ filePath: formatProjectPath(projectConfig, fullPath),
172
173
  entityType,
173
174
  entityKey: key,
174
175
  message: `Invalid name: "${key}". ${ENTITY_NAME_REGEX_ERROR}`,
@@ -186,7 +187,7 @@ export async function lintProject(
186
187
  }
187
188
  } catch (error) {
188
189
  recordError({
189
- filePath: path.relative(process.cwd(), fullPath),
190
+ filePath: formatProjectPath(projectConfig, fullPath),
190
191
  entityType,
191
192
  entityKey: key,
192
193
  message: error instanceof Error ? error.message : String(error),
@@ -252,7 +253,7 @@ export async function lintProject(
252
253
  const fullPath = getFullPathFromKey(projectConfig, "locale", key);
253
254
 
254
255
  recordError({
255
- filePath: path.relative(process.cwd(), fullPath),
256
+ filePath: formatProjectPath(projectConfig, fullPath),
256
257
  entityType: "locale",
257
258
  entityKey: key,
258
259
  message: `Circular locale dependency detected for ${field}: ${circularDependency.cycle.join(" -> ")}`,
@@ -289,7 +290,8 @@ export async function lintProject(
289
290
  ...lintMessageIcuFormatStyles(
290
291
  Object.fromEntries(Object.entries(messagesByKey).filter(([key]) => shouldLintKey(key))),
291
292
  localesByKey,
292
- (key) => path.relative(process.cwd(), getFullPathFromKey(projectConfig, "message", key)),
293
+ (key) =>
294
+ formatProjectPath(projectConfig, getFullPathFromKey(projectConfig, "message", key)),
293
295
  { icuSkeleton: projectConfig.icuSkeleton },
294
296
  ),
295
297
  );
@@ -0,0 +1,20 @@
1
+ import * as path from "path";
2
+
3
+ import { formatRootRelativePath } from "./path";
4
+
5
+ describe("path formatting", function () {
6
+ it("formats paths relative to the selected project root", function () {
7
+ const root = path.join(path.sep, "tmp", "messagevisor-project");
8
+
9
+ expect(formatRootRelativePath(root, path.join(root, "messages/auth/signin.yml"))).toEqual(
10
+ path.join("messages", "auth", "signin.yml"),
11
+ );
12
+ });
13
+
14
+ it("keeps paths outside the selected project root absolute", function () {
15
+ const root = path.join(path.sep, "tmp", "messagevisor-project");
16
+ const outside = path.join(path.sep, "tmp", "other", "translations.csv");
17
+
18
+ expect(formatRootRelativePath(root, outside)).toEqual(outside);
19
+ });
20
+ });
package/src/path.ts ADDED
@@ -0,0 +1,31 @@
1
+ import * as path from "path";
2
+
3
+ import type { ProjectConfig } from "./config";
4
+
5
+ export function getProjectRootDirectoryPath(projectConfig: ProjectConfig) {
6
+ return path.dirname(projectConfig.setsDirectoryPath);
7
+ }
8
+
9
+ export function formatRootRelativePath(rootDirectoryPath: string, filePath: string) {
10
+ const absoluteRootDirectoryPath = path.resolve(rootDirectoryPath);
11
+ const absoluteFilePath = path.resolve(filePath);
12
+ const relativePath = path.relative(absoluteRootDirectoryPath, absoluteFilePath);
13
+
14
+ if (!relativePath) {
15
+ return ".";
16
+ }
17
+
18
+ if (
19
+ relativePath === ".." ||
20
+ relativePath.startsWith(`..${path.sep}`) ||
21
+ path.isAbsolute(relativePath)
22
+ ) {
23
+ return absoluteFilePath;
24
+ }
25
+
26
+ return relativePath;
27
+ }
28
+
29
+ export function formatProjectPath(projectConfig: ProjectConfig, filePath: string) {
30
+ return formatRootRelativePath(getProjectRootDirectoryPath(projectConfig), filePath);
31
+ }
@@ -488,10 +488,7 @@ describe("promoteProjectSets", function () {
488
488
  ".messagevisor/promotions/20260419T102030-dev-to-staging-1.md",
489
489
  );
490
490
 
491
- const audit = await fs.promises.readFile(
492
- path.resolve(process.cwd(), first.auditFilePath),
493
- "utf8",
494
- );
491
+ const audit = await fs.promises.readFile(path.resolve(root, first.auditFilePath), "utf8");
495
492
  expect(audit).toContain("# Messagevisor Promotion");
496
493
  expect(audit).toContain("- Mode: apply");
497
494
  expect(audit).toContain("messages/product/price.yml");
@@ -516,7 +513,7 @@ describe("promoteProjectSets", function () {
516
513
  expect(result.auditFilePath).toContain(".json");
517
514
 
518
515
  const audit = JSON.parse(
519
- await fs.promises.readFile(path.resolve(process.cwd(), result.auditFilePath!), "utf8"),
516
+ await fs.promises.readFile(path.resolve(root, result.auditFilePath!), "utf8"),
520
517
  );
521
518
 
522
519
  expect(audit.apply).toEqual(true);
@@ -16,6 +16,7 @@ import type { ProjectConfig } from "../config";
16
16
  import type { Datasource } from "../datasource";
17
17
  import { MessagevisorCLIError, printMessagevisorCLIError } from "../error";
18
18
  import { lintProject, type LintError } from "../linter";
19
+ import { formatProjectPath } from "../path";
19
20
  import { CLI_FORMAT_BOLD, CLI_FORMAT_GREEN, colorize } from "../tester/cliFormat";
20
21
  import { prettyDuration } from "../tester/prettyDuration";
21
22
 
@@ -1008,7 +1009,7 @@ async function writePromotionAudit(
1008
1009
 
1009
1010
  await fs.promises.writeFile(filePath, content);
1010
1011
 
1011
- return path.relative(process.cwd(), filePath);
1012
+ return formatProjectPath(projectConfig, filePath);
1012
1013
  }
1013
1014
 
1014
1015
  export async function promoteProjectSets(
@@ -1075,24 +1076,24 @@ export async function promoteProjectSets(
1075
1076
  const created = plans
1076
1077
  .filter((plan) => !plan.destination)
1077
1078
  .map((plan) =>
1078
- path.relative(
1079
- process.cwd(),
1079
+ formatProjectPath(
1080
+ projectConfig,
1080
1081
  getEntityFilePath(destinationDatasource.getConfig(), plan.type, plan.key),
1081
1082
  ),
1082
1083
  );
1083
1084
  const updated = plans
1084
1085
  .filter((plan) => plan.destination && !deepEqual(plan.destination, plan.merged))
1085
1086
  .map((plan) =>
1086
- path.relative(
1087
- process.cwd(),
1087
+ formatProjectPath(
1088
+ projectConfig,
1088
1089
  getEntityFilePath(destinationDatasource.getConfig(), plan.type, plan.key),
1089
1090
  ),
1090
1091
  );
1091
1092
  const unchanged = plans
1092
1093
  .filter((plan) => plan.destination && deepEqual(plan.destination, plan.merged))
1093
1094
  .map((plan) =>
1094
- path.relative(
1095
- process.cwd(),
1095
+ formatProjectPath(
1096
+ projectConfig,
1096
1097
  getEntityFilePath(destinationDatasource.getConfig(), plan.type, plan.key),
1097
1098
  ),
1098
1099
  );
@@ -13,6 +13,7 @@ import type {
13
13
  import type { ProjectConfig } from "../config";
14
14
  import type { Datasource } from "../datasource";
15
15
  import { mergeFormatPresets } from "../formats";
16
+ import { formatProjectPath } from "../path";
16
17
  import { getProjectSetExecutions } from "../sets";
17
18
  import { CLI_FORMAT_BOLD, CLI_FORMAT_GREEN } from "../tester/cliFormat";
18
19
 
@@ -100,8 +101,8 @@ function getEntityFilePath(
100
101
  ) {
101
102
  const parser = projectConfig.parser as CustomParser;
102
103
 
103
- return path.relative(
104
- process.cwd(),
104
+ return formatProjectPath(
105
+ projectConfig,
105
106
  path.join(directoryPath, ...key.split(projectConfig.namespaceCharacter)) +
106
107
  `${suffix}.${parser.extension}`,
107
108
  );
package/src/sets.ts CHANGED
@@ -3,6 +3,7 @@ import * as path from "path";
3
3
  import type { ProjectConfig } from "./config";
4
4
  import type { Datasource } from "./datasource";
5
5
  import { MessagevisorCLIError } from "./error";
6
+ import { formatProjectPath } from "./path";
6
7
 
7
8
  export interface ProjectSetExecution {
8
9
  set: string;
@@ -61,8 +62,8 @@ export function getProjectSetRelativeFilePath(
61
62
  set: string,
62
63
  filePath: string,
63
64
  ) {
64
- const setDirectoryPath = path.relative(
65
- process.cwd(),
65
+ const setDirectoryPath = formatProjectPath(
66
+ projectConfig,
66
67
  path.join(projectConfig.setsDirectoryPath, set),
67
68
  );
68
69
 
@@ -7,6 +7,7 @@ import type { ProjectConfig } from "../config";
7
7
  import type { Datasource } from "../datasource";
8
8
  import { buildDatafile, resolveFormats } from "../builder";
9
9
  import { evaluateSegment } from "../evaluate";
10
+ import { formatProjectPath } from "../path";
10
11
  import {
11
12
  assertProjectSetJsonSelection,
12
13
  getProjectSetExecutions,
@@ -200,7 +201,7 @@ function getTestFilePath(projectConfig: ProjectConfig, testKey: string) {
200
201
  const specPath = `${basePath}.spec.${extension}`;
201
202
  const legacyPath = `${basePath}.${extension}`;
202
203
 
203
- return path.relative(process.cwd(), fs.existsSync(specPath) ? specPath : legacyPath);
204
+ return formatProjectPath(projectConfig, fs.existsSync(specPath) ? specPath : legacyPath);
204
205
  }
205
206
 
206
207
  async function runTest(