From 099a176f58c9e2348471c2c6bbfd644539c98c4c Mon Sep 17 00:00:00 2001 From: ashwani123p Date: Fri, 12 Jun 2026 11:54:47 +0530 Subject: [PATCH 1/2] Adds a third route to the extension's URI handler so external tools (e.g., HTML reports generated by Claude Code skills) can deep-link into PP-VSCode and trigger the existing "Import Metadata Diff" command on a specific .json file path without prompting the user with the file picker. The route accepts vscode://microsoft-IsvExpTools.powerplatform-vscode/ metadataDiffImport?filePath=. The supplied path is validated (must exist, must be absolute, must be .json, must not contain '..' segments) before being passed to importMetadataDiff() as an optional vscode.Uri argument. The command-palette flow remains unchanged when no argument is supplied. --- .../actions-hub/ActionsHubTreeDataProvider.ts | 4 +- .../power-pages/actions-hub/Constants.ts | 10 +++ .../ImportMetadataDiffHandler.ts | 58 ++++++++++++----- .../ImportMetadataDiffHandler.test.ts | 62 +++++++++++++++++++ src/client/test/unit/uriHandler.test.ts | 45 ++++++++++++++ .../uriHandler/constants/uriConstants.ts | 7 ++- src/client/uriHandler/constants/uriStrings.ts | 5 +- .../telemetry/uriHandlerTelemetryEvents.ts | 3 +- src/client/uriHandler/uriHandler.ts | 54 +++++++++++++++- .../utils/metadataDiffImportValidation.ts | 45 ++++++++++++++ 10 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 src/client/uriHandler/utils/metadataDiffImportValidation.ts diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 02e8c3ac1..d0da31bad 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -386,7 +386,9 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider importMetadataDiff()), vscode.commands.registerCommand(Constants.Commands.METADATA_DIFF_RESYNC, resyncMetadataDiff(this._pacTerminal, this._context)), MetadataDiffDecorationProvider.getInstance().register(), ReadOnlyContentProvider.getInstance().register() diff --git a/src/client/power-pages/actions-hub/Constants.ts b/src/client/power-pages/actions-hub/Constants.ts index dfbb59ecc..1f9e0c64c 100644 --- a/src/client/power-pages/actions-hub/Constants.ts +++ b/src/client/power-pages/actions-hub/Constants.ts @@ -268,6 +268,7 @@ export const Constants = { METADATA_DIFF_EXPORT_TITLE: vscode.l10n.t("Export Site Comparison"), METADATA_DIFF_IMPORT_PROGRESS: vscode.l10n.t("Importing comparison..."), METADATA_DIFF_IMPORT_TITLE: vscode.l10n.t("Import Site Comparison"), + METADATA_DIFF_IMPORT_NOT_JSON: vscode.l10n.t("Metadata diff file must be a .json file."), METADATA_DIFF_EXPORT_INVALID_FILE: vscode.l10n.t("Invalid file format. The file does not contain valid metadata diff data."), METADATA_DIFF_EXPORT_UNSUPPORTED_VERSION: vscode.l10n.t("Unsupported version. This file was created with a newer version of the extension."), METADATA_DIFF_EXPORT_NEWER_EXTENSION_VERSION: vscode.l10n.t("This file was exported with a newer version of the extension. Please update your extension to import this file."), @@ -519,6 +520,15 @@ export const Constants = { args: [errorMessage], comment: ["Error message when import fails. {0} is the error message."] }), + /** + * Returns the error message when a pre-supplied import file does not exist + */ + METADATA_DIFF_IMPORT_FILE_NOT_FOUND: (filePath: string) => + vscode.l10n.t({ + message: "Metadata diff file not found: {0}", + args: [filePath], + comment: ["Error message when the supplied import file is missing. {0} is the file path."] + }), /** * Returns the progress message when resyncing a site comparison */ diff --git a/src/client/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.ts b/src/client/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.ts index 51666bd31..52f9bd42d 100644 --- a/src/client/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.ts +++ b/src/client/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.ts @@ -112,26 +112,56 @@ function validateImportData(data: unknown): string | undefined { /** * Imports a metadata diff from a JSON file */ -export async function importMetadataDiff(): Promise { +export async function importMetadataDiff(presuppliedFileUri?: vscode.Uri): Promise { traceInfo(Constants.EventNames.ACTIONS_HUB_METADATA_DIFF_IMPORT_CALLED, { - methodName: importMetadataDiff.name + methodName: importMetadataDiff.name, + source: presuppliedFileUri ? "uri_handler" : "command_palette" }); try { - // Show open dialog first (before progress) - const openUris = await vscode.window.showOpenDialog({ - canSelectMany: false, - filters: { - [Constants.Strings.METADATA_DIFF_EXPORT_FILTER_NAME]: ["json"] - }, - title: Constants.Strings.METADATA_DIFF_IMPORT_TITLE - }); + let fileUri: vscode.Uri; - if (!openUris || openUris.length === 0) { - return; // User cancelled - } + if (presuppliedFileUri) { + // URI-handler path: validate the supplied file before proceeding. + if (!fs.existsSync(presuppliedFileUri.fsPath)) { + vscode.window.showErrorMessage( + Constants.StringFunctions.METADATA_DIFF_IMPORT_FILE_NOT_FOUND(presuppliedFileUri.fsPath) + ); + traceError( + Constants.EventNames.ACTIONS_HUB_METADATA_DIFF_IMPORT_FAILED, + new Error("Pre-supplied file path does not exist"), + { methodName: importMetadataDiff.name, reason: "file_not_found" } + ); + return; + } + if (!presuppliedFileUri.fsPath.toLowerCase().endsWith(".json")) { + vscode.window.showErrorMessage( + Constants.Strings.METADATA_DIFF_IMPORT_NOT_JSON + ); + traceError( + Constants.EventNames.ACTIONS_HUB_METADATA_DIFF_IMPORT_FAILED, + new Error("Pre-supplied file is not .json"), + { methodName: importMetadataDiff.name, reason: "wrong_extension" } + ); + return; + } + fileUri = presuppliedFileUri; + } else { + // Command path: existing file-picker flow (unchanged). + const openUris = await vscode.window.showOpenDialog({ + canSelectMany: false, + filters: { + [Constants.Strings.METADATA_DIFF_EXPORT_FILTER_NAME]: ["json"] + }, + title: Constants.Strings.METADATA_DIFF_IMPORT_TITLE + }); + + if (!openUris || openUris.length === 0) { + return; // User cancelled + } - const fileUri = openUris[0]; + fileUri = openUris[0]; + } // Read and parse the file first to validate before showing progress let importData: IMetadataDiffExport; diff --git a/src/client/test/integration/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.test.ts b/src/client/test/integration/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.test.ts index a7e769926..dbee87e95 100644 --- a/src/client/test/integration/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.test.ts +++ b/src/client/test/integration/power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler.test.ts @@ -4,6 +4,7 @@ */ import * as vscode from "vscode"; +import * as fs from "fs"; import { expect } from "chai"; import sinon from "sinon"; import * as TelemetryHelper from "../../../../../../power-pages/actions-hub/TelemetryHelper"; @@ -105,6 +106,67 @@ describe("ImportMetadataDiffHandler", () => { }); }); + describe("importMetadataDiff with a pre-supplied URI", () => { + it("should skip the open dialog when a URI is supplied", async () => { + const existsSyncStub = sandbox.stub(fs, "existsSync").returns(true); + // Short-circuit the read so we don't run the full import flow. + sandbox.stub(fs, "readFileSync").throws(new Error("stop after dialog check")); + + const { importMetadataDiff } = await import("../../../../../../power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler"); + + await importMetadataDiff(vscode.Uri.file("/tmp/diff.json")); + + expect(showOpenDialogStub.called).to.be.false; + expect(existsSyncStub.called).to.be.true; + }); + + it("should show an error when the supplied file does not exist", async () => { + sandbox.stub(fs, "existsSync").returns(false); + + const { importMetadataDiff } = await import("../../../../../../power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler"); + + await importMetadataDiff(vscode.Uri.file("/tmp/missing.json")); + + expect(showOpenDialogStub.called).to.be.false; + expect(showErrorMessageStub.calledOnce).to.be.true; + expect(showErrorMessageStub.firstCall.args[0]).to.match(/not found/i); + }); + + it("should show an error when the supplied file is not a .json file", async () => { + sandbox.stub(fs, "existsSync").returns(true); + + const { importMetadataDiff } = await import("../../../../../../power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler"); + + await importMetadataDiff(vscode.Uri.file("/tmp/diff.txt")); + + expect(showOpenDialogStub.called).to.be.false; + expect(showErrorMessageStub.calledOnce).to.be.true; + expect(showErrorMessageStub.firstCall.args[0]).to.match(/\.json/i); + }); + + it("should record the URI handler source in telemetry", async () => { + const traceInfoStub = TelemetryHelper.traceInfo as sinon.SinonStub; + sandbox.stub(fs, "existsSync").returns(false); + + const { importMetadataDiff } = await import("../../../../../../power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler"); + + await importMetadataDiff(vscode.Uri.file("/tmp/missing.json")); + + expect(traceInfoStub.firstCall.args[0]).to.equal("ActionsHubMetadataDiffImportCalled"); + expect(traceInfoStub.firstCall.args[1]).to.include({ source: "uri_handler" }); + }); + + it("should still open the dialog when no URI is supplied", async () => { + showOpenDialogStub.resolves(undefined); + + const { importMetadataDiff } = await import("../../../../../../power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler"); + + await importMetadataDiff(); + + expect(showOpenDialogStub.calledOnce).to.be.true; + }); + }); + describe("IMetadataDiffExport format", () => { it("should support new format with localWebsiteId and remoteWebsiteId", () => { // This test verifies that the interface supports the new field names diff --git a/src/client/test/unit/uriHandler.test.ts b/src/client/test/unit/uriHandler.test.ts index 1fbadef5c..bbb021613 100644 --- a/src/client/test/unit/uriHandler.test.ts +++ b/src/client/test/unit/uriHandler.test.ts @@ -4,6 +4,7 @@ */ import { expect } from "chai"; +import { resolveMetadataDiffImportFilePath } from "../../uriHandler/utils/metadataDiffImportValidation"; // Test URI handling functionality describe('UriHandler Schema Parameter Tests', () => { @@ -211,3 +212,47 @@ describe('UriHandler Schema Parameter Tests', () => { }); }); }); + +describe('Metadata Diff Import filePath validation', () => { + it('reports "missing" when filePath is null', () => { + const result = resolveMetadataDiffImportFilePath(null); + expect(result.ok).to.be.false; + expect(result).to.deep.equal({ ok: false, reason: "missing" }); + }); + + it('reports "missing" when filePath is undefined', () => { + const result = resolveMetadataDiffImportFilePath(undefined); + expect(result).to.deep.equal({ ok: false, reason: "missing" }); + }); + + it('reports "missing" when filePath is an empty string', () => { + const result = resolveMetadataDiffImportFilePath(""); + expect(result).to.deep.equal({ ok: false, reason: "missing" }); + }); + + it('reports "invalid" for a relative path', () => { + const result = resolveMetadataDiffImportFilePath("relative/path/diff.json"); + expect(result).to.deep.equal({ ok: false, reason: "invalid" }); + }); + + it('reports "invalid" for a path containing ".." segments', () => { + const result = resolveMetadataDiffImportFilePath("/Users/test/../../etc/diff.json"); + expect(result).to.deep.equal({ ok: false, reason: "invalid" }); + }); + + it('reports "invalid" when ".." is hidden behind URL-encoding', () => { + // %2E%2E decodes to "..", so traversal attempts must be caught after decoding. + const result = resolveMetadataDiffImportFilePath("%2F..%2Fetc%2Fdiff.json"); + expect(result).to.deep.equal({ ok: false, reason: "invalid" }); + }); + + it('accepts an absolute path and returns the decoded path', () => { + const result = resolveMetadataDiffImportFilePath("/Users/test/diff.json"); + expect(result).to.deep.equal({ ok: true, filePath: "/Users/test/diff.json" }); + }); + + it('decodes percent-encoded absolute paths', () => { + const result = resolveMetadataDiffImportFilePath("%2FUsers%2Ftest%20user%2Fdiff.json"); + expect(result).to.deep.equal({ ok: true, filePath: "/Users/test user/diff.json" }); + }); +}); diff --git a/src/client/uriHandler/constants/uriConstants.ts b/src/client/uriHandler/constants/uriConstants.ts index 00330e7d8..9f6ccf33a 100644 --- a/src/client/uriHandler/constants/uriConstants.ts +++ b/src/client/uriHandler/constants/uriConstants.ts @@ -10,7 +10,8 @@ export const URI_CONSTANTS = { EXTENSION_ID: 'microsoft-IsvExpTools.powerplatform-vscode', PATHS: { PCF_INIT: '/pcfInit', - OPEN: '/open' + OPEN: '/open', + METADATA_DIFF_IMPORT: '/metadataDiffImport' }, PARAMETERS: { WEBSITE_ID: 'websiteid', @@ -20,7 +21,8 @@ export const URI_CONSTANTS = { SITE_NAME: 'sitename', WEBSITE_NAME: 'websitename', SITE_URL: 'siteurl', - WEBSITE_PREVIEW_URL: 'websitepreviewurl' + WEBSITE_PREVIEW_URL: 'websitepreviewurl', + FILE_PATH: 'filePath' }, SCHEMA_VALUES: { PORTAL_SCHEMA_V2: 'portalschemav2' @@ -40,4 +42,5 @@ export const URI_CONSTANTS = { export const enum UriPath { PcfInit = '/pcfInit', Open = '/open', + MetadataDiffImport = '/metadataDiffImport', } diff --git a/src/client/uriHandler/constants/uriStrings.ts b/src/client/uriHandler/constants/uriStrings.ts index e83b88254..9e70e2ff6 100644 --- a/src/client/uriHandler/constants/uriStrings.ts +++ b/src/client/uriHandler/constants/uriStrings.ts @@ -19,7 +19,10 @@ export const URI_HANDLER_STRINGS = { ENV_SWITCH_FAILED: vscode.l10n.t("Failed to switch to the required environment. Please sign in with an account that has access to the target environment using 'pac auth create' command."), USER_CANCELLED_ENV_SWITCH: vscode.l10n.t("User cancelled environment switch"), USER_CANCELLED_FOLDER_SELECTION: vscode.l10n.t("User cancelled folder selection"), - DOWNLOAD_FAILED: vscode.l10n.t("Download failed: {0}") + DOWNLOAD_FAILED: vscode.l10n.t("Download failed: {0}"), + METADATA_DIFF_IMPORT_MISSING_FILE_PATH: vscode.l10n.t("Metadata diff import URI is missing the required 'filePath' query parameter."), + METADATA_DIFF_IMPORT_INVALID_FILE_PATH: vscode.l10n.t("Metadata diff import URI must reference an absolute file path without '..' segments."), + METADATA_DIFF_IMPORT_FAILED: vscode.l10n.t("Metadata diff import failed: {0}") }, INFO: { DOWNLOAD_CANCELLED_AUTH: vscode.l10n.t("Site download cancelled. Authentication is required to proceed."), diff --git a/src/client/uriHandler/telemetry/uriHandlerTelemetryEvents.ts b/src/client/uriHandler/telemetry/uriHandlerTelemetryEvents.ts index 40339b871..2404cf066 100644 --- a/src/client/uriHandler/telemetry/uriHandlerTelemetryEvents.ts +++ b/src/client/uriHandler/telemetry/uriHandlerTelemetryEvents.ts @@ -16,5 +16,6 @@ export enum uriHandlerTelemetryEventNames { URI_HANDLER_DOWNLOAD_STARTED = "UriHandlerDownloadStarted", URI_HANDLER_DOWNLOAD_COMPLETED = "UriHandlerDownloadCompleted", URI_HANDLER_FOLDER_OPENED = "UriHandlerFolderOpened", - URI_HANDLER_PCF_INIT_TRIGGERED = "UriHandlerPcfInitTriggered" + URI_HANDLER_PCF_INIT_TRIGGERED = "UriHandlerPcfInitTriggered", + URI_HANDLER_METADATA_DIFF_IMPORT_TRIGGERED = "UriHandlerMetadataDiffImportTriggered" } diff --git a/src/client/uriHandler/uriHandler.ts b/src/client/uriHandler/uriHandler.ts index 96bd60b8f..62ffc8c64 100644 --- a/src/client/uriHandler/uriHandler.ts +++ b/src/client/uriHandler/uriHandler.ts @@ -6,10 +6,12 @@ import * as vscode from "vscode"; import { PacWrapper } from "../pac/PacWrapper"; import { oneDSLoggerWrapper } from "../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; -import { UriPath } from "./constants/uriConstants"; +import { UriPath, URI_CONSTANTS } from "./constants/uriConstants"; import { URI_HANDLER_STRINGS } from "./constants/uriStrings"; import { uriHandlerTelemetryEventNames } from "./telemetry/uriHandlerTelemetryEvents"; import { UriHandlerUtils, UriParameters } from "./utils/uriHandlerUtils"; +import { resolveMetadataDiffImportFilePath } from "./utils/metadataDiffImportValidation"; +import { importMetadataDiff } from "../power-pages/actions-hub/handlers/metadata-diff/ImportMetadataDiffHandler"; export function RegisterUriHandler(pacWrapper: PacWrapper): vscode.Disposable { const uriHandler = new UriHandler(pacWrapper); @@ -32,6 +34,56 @@ class UriHandler implements vscode.UriHandler { return this.pcfInit(); } else if (uri.path === UriPath.Open) { return this.handleOpenPowerPages(uri); + } else if (uri.path === UriPath.MetadataDiffImport) { + return this.handleMetadataDiffImport(uri); + } + } + + // vscode://microsoft-IsvExpTools.powerplatform-vscode/metadataDiffImport?filePath= + async handleMetadataDiffImport(uri: vscode.Uri): Promise { + const startTime = Date.now(); + try { + const params = new URLSearchParams(uri.query); + const rawFilePath = params.get(URI_CONSTANTS.PARAMETERS.FILE_PATH); + + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_METADATA_DIFF_IMPORT_TRIGGERED, + { timestamp: startTime.toString(), hasFilePath: rawFilePath ? "true" : "false" } + ); + + const pathResult = resolveMetadataDiffImportFilePath(rawFilePath); + if (!pathResult.ok) { + const message = pathResult.reason === "missing" + ? URI_HANDLER_STRINGS.ERRORS.METADATA_DIFF_IMPORT_MISSING_FILE_PATH + : URI_HANDLER_STRINGS.ERRORS.METADATA_DIFF_IMPORT_INVALID_FILE_PATH; + vscode.window.showErrorMessage(message); + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_METADATA_DIFF_IMPORT_TRIGGERED, + { success: "false", reason: pathResult.reason, duration: (Date.now() - startTime).toString() } + ); + return; + } + + const fileUri = vscode.Uri.file(pathResult.filePath); + await importMetadataDiff(fileUri); + + oneDSLoggerWrapper.getLogger().traceInfo( + uriHandlerTelemetryEventNames.URI_HANDLER_METADATA_DIFF_IMPORT_TRIGGERED, + { success: "true", duration: (Date.now() - startTime).toString() } + ); + } catch (error) { + oneDSLoggerWrapper.getLogger().traceError( + uriHandlerTelemetryEventNames.URI_HANDLER_METADATA_DIFF_IMPORT_TRIGGERED, + "Metadata diff import via URI failed", + error instanceof Error ? error : new Error(String(error)), + { duration: (Date.now() - startTime).toString() } + ); + vscode.window.showErrorMessage( + URI_HANDLER_STRINGS.ERRORS.METADATA_DIFF_IMPORT_FAILED.replace( + "{0}", + error instanceof Error ? error.message : String(error) + ) + ); } } diff --git a/src/client/uriHandler/utils/metadataDiffImportValidation.ts b/src/client/uriHandler/utils/metadataDiffImportValidation.ts new file mode 100644 index 000000000..27fa92b73 --- /dev/null +++ b/src/client/uriHandler/utils/metadataDiffImportValidation.ts @@ -0,0 +1,45 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as path from "path"; + +/** + * Result of validating the `filePath` query parameter of a + * `/metadataDiffImport` URI. + * + * Kept free of any `vscode` dependency so the validation logic can be + * exercised by the plain-mocha unit test runner (which cannot load the + * `vscode` runtime module). + */ +export type MetadataDiffImportPathResult = + | { ok: true; filePath: string } + | { ok: false; reason: "missing" | "invalid" }; + +/** + * Validates and decodes the `filePath` supplied on a metadata diff import URI. + * + * Defense-in-depth: only absolute paths without `..` segments are accepted. + * Relative paths and path-traversal attempts are rejected. The user is + * implicitly trusting whoever generated the URL — but we shouldn't make path + * tricks easy. + * + * @param rawFilePath The raw, still-encoded value of the `filePath` query parameter. + * @returns `{ ok: true, filePath }` with the decoded absolute path, or + * `{ ok: false, reason }` describing why it was rejected. + */ +export function resolveMetadataDiffImportFilePath( + rawFilePath: string | null | undefined +): MetadataDiffImportPathResult { + if (!rawFilePath) { + return { ok: false, reason: "missing" }; + } + + const decoded = decodeURIComponent(rawFilePath); + if (!path.isAbsolute(decoded) || decoded.includes("..")) { + return { ok: false, reason: "invalid" }; + } + + return { ok: true, filePath: decoded }; +} From ea552943ed6f3926566fbd5e6ae5787861e1b185 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Jun 2026 06:29:50 +0000 Subject: [PATCH 2/2] Automated translations export from CI --- l10n/bundle.l10n.json | 10 ++++++++++ loc/translations-export/vscode-powerplatform.xlf | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 37ed942b7..6b27e37e0 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -133,6 +133,9 @@ "User cancelled environment switch": "User cancelled environment switch", "User cancelled folder selection": "User cancelled folder selection", "Download failed: {0}": "Download failed: {0}", + "Metadata diff import URI is missing the required 'filePath' query parameter.": "Metadata diff import URI is missing the required 'filePath' query parameter.", + "Metadata diff import URI must reference an absolute file path without '..' segments.": "Metadata diff import URI must reference an absolute file path without '..' segments.", + "Metadata diff import failed: {0}": "Metadata diff import failed: {0}", "Site download cancelled. Authentication is required to proceed.": "Site download cancelled. Authentication is required to proceed.", "Site download cancelled. Correct environment connection is required.": "Site download cancelled. Correct environment connection is required.", "Site download cancelled. No folder selected.": "Site download cancelled. No folder selected.", @@ -435,6 +438,7 @@ "Export Site Comparison": "Export Site Comparison", "Importing comparison...": "Importing comparison...", "Import Site Comparison": "Import Site Comparison", + "Metadata diff file must be a .json file.": "Metadata diff file must be a .json file.", "Invalid file format. The file does not contain valid metadata diff data.": "Invalid file format. The file does not contain valid metadata diff data.", "Unsupported version. This file was created with a newer version of the extension.": "Unsupported version. This file was created with a newer version of the extension.", "This file was exported with a newer version of the extension. Please update your extension to import this file.": "This file was exported with a newer version of the extension. Please update your extension to import this file.", @@ -613,6 +617,12 @@ "Error message when import fails. {0} is the error message." ] }, + "Metadata diff file not found: {0}/Error message when the supplied import file is missing. {0} is the file path.": { + "message": "Metadata diff file not found: {0}", + "comment": [ + "Error message when the supplied import file is missing. {0} is the file path." + ] + }, "Refreshing comparison for {0} ([details](command:microsoft.powerplatform.pages.actionsHub.showOutputChannel \"Show download output\")).../This is a markdown formatting which must persist across translations.": { "message": "Refreshing comparison for {0} ([details](command:microsoft.powerplatform.pages.actionsHub.showOutputChannel \"Show download output\"))...", "comment": [ diff --git a/loc/translations-export/vscode-powerplatform.xlf b/loc/translations-export/vscode-powerplatform.xlf index be35d03ac..c88c12add 100644 --- a/loc/translations-export/vscode-powerplatform.xlf +++ b/loc/translations-export/vscode-powerplatform.xlf @@ -822,6 +822,22 @@ Return to this chat and @powerpages can help you write and edit your website cod Maximum 30 characters allowed + + Metadata diff file must be a .json file. + + + Metadata diff file not found: {0} + Error message when the supplied import file is missing. {0} is the file path. + + + Metadata diff import URI is missing the required 'filePath' query parameter. + + + Metadata diff import URI must reference an absolute file path without '..' segments. + + + Metadata diff import failed: {0} + Microsoft wants your feedback