Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sparkly-crews-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webiny/data-transfer": patch
---

Strip `#0001` revision suffix from `inheritedFrom` in FLP permission records during transfer.
119 changes: 117 additions & 2 deletions __tests__/transformers/folders/updateFlpIds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import { describe, it, expect } from "vitest";
import { updateFlpIds } from "~/transformers/folders/updateFlpIds.js";
import { makeFakeBaseContext } from "../fakeContext.ts";

interface FlpPermission {
inheritedFrom?: string;
level: string;
target: string;
}

interface FlpData {
id: string;
parentId: string;
permissions?: FlpPermission[];
}

const getData = (ctx: { record: unknown }): FlpData => (ctx.record as { data: FlpData }).data;

describe("updateFlpIds", () => {
it("strips #0001 revision suffix from data.id and data.parentId", () => {
const ctx = makeFakeBaseContext({
Expand All @@ -21,7 +30,7 @@ describe("updateFlpIds", () => {

updateFlpIds(ctx);

const data = (ctx.record as unknown as { data: FlpData }).data;
const data = getData(ctx);
expect(data.id).toBe("abc123");
expect(data.parentId).toBe("def456");
});
Expand All @@ -39,9 +48,115 @@ describe("updateFlpIds", () => {

updateFlpIds(ctx);

const data = (ctx.record as unknown as { data: FlpData }).data;
const data = getData(ctx);
expect(data.id).toBe("abc123");
expect(data.parentId).toBe("def456");
});

it("strips revision suffix from inheritedFrom in permissions", () => {
const ctx = makeFakeBaseContext({
PK: "T#root#FLP#a1b2c3d4e5f6#0001",
SK: "A",
TYPE: "flp",
data: {
id: "a1b2c3d4e5f6#0001",
parentId: "f6e5d4c3b2a1#0001",
permissions: [
{
inheritedFrom: "parent:f6e5d4c3b2a1#0001",
level: "editor",
target: "team:design-editors"
},
{
inheritedFrom: "parent:f6e5d4c3b2a1#0001",
level: "viewer",
target: "team:marketing-viewers"
}
]
}
});

updateFlpIds(ctx);

const data = getData(ctx);
expect(data.id).toBe("a1b2c3d4e5f6");
expect(data.parentId).toBe("f6e5d4c3b2a1");
expect(data.permissions?.[0]?.inheritedFrom).toBe("parent:f6e5d4c3b2a1");
expect(data.permissions?.[1]?.inheritedFrom).toBe("parent:f6e5d4c3b2a1");
});

it("handles any 4-digit revision suffix, not just #0001", () => {
const ctx = makeFakeBaseContext({
PK: "T#root#FLP",
SK: "A",
TYPE: "flp",
data: {
id: "abc123#0010",
parentId: "def456#0099",
permissions: [
{
inheritedFrom: "parent:def456#0042",
level: "editor",
target: "team:ops-admins"
}
]
}
});

updateFlpIds(ctx);

const data = getData(ctx);
expect(data.id).toBe("abc123");
expect(data.parentId).toBe("def456");
expect(data.permissions?.[0]?.inheritedFrom).toBe("parent:def456");
});

it("skips permissions without inheritedFrom", () => {
const ctx = makeFakeBaseContext({
PK: "T#root#FLP",
SK: "A",
TYPE: "flp",
data: {
id: "abc123#0001",
parentId: "def456#0001",
permissions: [
{
level: "editor",
target: "team:content-editors"
},
{
inheritedFrom: "parent:def456#0001",
level: "viewer",
target: "team:content-viewers"
}
]
}
});

updateFlpIds(ctx);

const data = getData(ctx);
expect(data.permissions?.[0]?.inheritedFrom).toBeUndefined();
expect(data.permissions?.[1]?.inheritedFrom).toBe("parent:def456");
});

it("handles record with no permissions array", () => {
const ctx = makeFakeBaseContext({
PK: "T#root#FLP",
SK: "A",
TYPE: "flp",
data: {
id: "abc123#0001",
parentId: "def456#0001"
}
});

expect(() => updateFlpIds(ctx)).not.toThrow();

const data = getData(ctx);
expect(data.id).toBe("abc123");
expect(data.parentId).toBe("def456");
expect(data.permissions).toBeUndefined();
});

it("is a no-op when record.data is missing", () => {
Expand Down
34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
"author": "Webiny",
"license": "MIT",
"dependencies": {
"@aws-sdk/credential-providers": "^3.1101.0",
"@inquirer/core": "^11.2.1",
"@inquirer/prompts": "^8.5.2",
"@aws-sdk/credential-providers": "^3.1116.0",
"@inquirer/core": "^12.0.0",
"@inquirer/prompts": "^8.6.0",
"@modelcontextprotocol/sdk": "^1.30.0",
"@opensearch-project/opensearch": "3.6.0",
"@types/node": "^24.13.3",
Expand All @@ -67,34 +67,34 @@
"@webiny/utils": "^6.6.0-alpha.0",
"dotenv": "^17.4.2",
"execa": "^10.0.1",
"exifreader": "^4.41.3",
"exifreader": "^4.44.0",
"front-matter": "^4.0.2",
"jsdom": "^30.0.1",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"sharp": "^0.35.3",
"tsx": "^4.23.5",
"tsx": "^4.23.12",
"typescript": "^7.0.2",
"yargs": "^18.1.0",
"zod": "^4.4.3"
},
"devDependencies": {
"@aws-sdk/client-dynamodb": "^3.1101.0",
"@aws-sdk/client-s3": "^3.1101.0",
"@aws-sdk/lib-dynamodb": "^3.1101.0",
"@changesets/cli": "^2.31.1",
"@faker-js/faker": "^10.5.0",
"@smithy/util-stream": "^4.7.16",
"@types/jsdom": "^28.0.3",
"@aws-sdk/client-dynamodb": "^3.1116.0",
"@aws-sdk/client-s3": "^3.1116.0",
"@aws-sdk/lib-dynamodb": "^3.1116.0",
"@changesets/cli": "^3.0.1",
"@faker-js/faker": "^10.6.0",
"@smithy/util-stream": "^4.8.2",
"@types/jsdom": "^30.0.0",
"@types/yargs": "^17.0.35",
"@vitest/coverage-v8": "^4.1.10",
"@vitest/coverage-v8": "^4.1.11",
"adio": "^3.0.1",
"aws-sdk-client-mock": "^4.1.0",
"dynalite": "^4.0.0",
"oxfmt": "^0.61.0",
"oxlint": "^1.76.0",
"verdaccio": "^6.9.2",
"vitest": "^4.1.10"
"oxfmt": "^0.64.0",
"oxlint": "^1.79.0",
"verdaccio": "^6.10.0",
"vitest": "^4.1.11"
},
"engines": {
"node": ">=24.0.0"
Expand Down
39 changes: 27 additions & 12 deletions src/transformers/folders/updateFlpIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,42 @@ import { createTransformer } from "~/transformers/createTransformer.js";
import type { BaseTransformContext } from "~/features/TransformContext/abstractions/BaseTransformContext.js";
import type { BaseRecord } from "~/domain/transform/types/records.js";

/**
* Removes #0001 revision from data.id and data.parentId in FLP records.
* Note: FLP records already have data attribute, so wrapInData doesn't wrap them again
*/
const stripRevision = (value: string) => value.replace(/#\d{4}$/, "");

export const updateFlpIds = createTransformer<BaseTransformContext.Interface<BaseRecord>>(
"updateFlpIds",
ctx => {
const { record } = ctx;

if (record.data && typeof record.data === "object") {
const data = record.data as Record<string, unknown>;
if (!record.data || typeof record.data !== "object") {
return;
}
const data = record.data as Record<string, unknown>;

if (typeof data.id === "string") {
data.id = stripRevision(data.id);
}

if (typeof data.parentId === "string") {
data.parentId = stripRevision(data.parentId);
}

if (!Array.isArray(data.permissions)) {
return;
}

// Remove #0001 from id
if (typeof data.id === "string") {
data.id = data.id.replace(/#0001$/, "");
for (const permission of data.permissions) {
if (!permission || typeof permission !== "object") {
continue;
}

// Remove #0001 from parentId
if (typeof data.parentId === "string") {
data.parentId = data.parentId.replace(/#0001$/, "");
const perm = permission as Record<string, unknown>;

if (typeof perm.inheritedFrom !== "string") {
continue;
}

perm.inheritedFrom = stripRevision(perm.inheritedFrom);
}
}
);
Loading
Loading