Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Metadata Generation with "Used By" Tracking #774

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
107 changes: 95 additions & 12 deletions engine/decofile/fsFolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ import type {
} from "./provider.ts";
import type { VersionedDecofile } from "./realtime.ts";

interface BaseMetadata {
blockType: string;
__resolveType: string;
usedBy: string[];
}

interface PageMetadata extends BaseMetadata {
name: string;
path: string;
}

type Metadata = BaseMetadata | PageMetadata;

export const parseBlockId = (filename: string) =>
decodeURIComponent(filename.slice(0, filename.length - ".json".length));

Expand All @@ -26,7 +39,10 @@ const inferBlockType = (resolveType: string, knownBlockTypes: Set<string>) => {
return blockType;
};

const inferMetadata = (content: unknown, knownBlockTypes: Set<string>) => {
const inferMetadata = (
content: unknown,
knownBlockTypes: Set<string>,
): Metadata | null => {
try {
const { __resolveType, name, path } = content as Record<string, string>;
const blockType = inferBlockType(__resolveType, knownBlockTypes);
Expand All @@ -41,24 +57,76 @@ const inferMetadata = (content: unknown, knownBlockTypes: Set<string>) => {
path: path,
blockType,
__resolveType,
usedBy: [],
};
}

return {
blockType,
__resolveType,
usedBy: [],
};
// TODO @gimenes: when the json is wrong, we should somehow resolve to a standard block that talks well to the admin so the user can fix it somehow
} catch {
return null;
}
};

/**
* Recursively maps block references in an object and updates the `usedBy` property of the corresponding blocks.
*
* @param obj - The object to map block references in.
* @param blocks - A map of block paths to block metadata.
* @param currentPath - The current path of the object being processed.
* @param isRoot - Indicates whether the current object is the root object.
*/
/**
* Recursively maps block references in an object and updates the `usedBy` property of the corresponding blocks.
*
* @param obj - The object to inspect for block references.
* @param blockMetadata - A map of block paths to block metadata.
* @param blockPathByName - A map of block names to their corresponding file paths.
* @param currentPath - The path of the current block being processed.
* @param isRoot - Indicates whether the current object is the root object.
*/
const mapBlockReferences = (
obj: unknown,
blockMetadata: Map<string, Metadata>,
blockPathByName: Map<string, string>,
currentPath: string,
isRoot = true
): void => {
if (typeof obj !== 'object' || obj === null) return;

if (Array.isArray(obj)) {
for (const item of obj) {
mapBlockReferences(item, blockMetadata, blockPathByName, currentPath, false);
}
return;
}

for (const [key, value] of Object.entries(obj)) {
if (key === '__resolveType' && !isRoot) {
const blockName = value.split('/').pop();
if (blockName) {
const referencedBlockPath = blockPathByName.get(blockName);
if (referencedBlockPath && referencedBlockPath !== currentPath) {
const blockMeta = blockMetadata.get(referencedBlockPath);
if (blockMeta && !blockMeta.usedBy.includes(currentPath)) {
blockMeta.usedBy.push(currentPath);
}
}
}
} else if (typeof value === 'object' && value !== null) {
mapBlockReferences(value, blockMetadata, blockPathByName, currentPath, false);
}
}
};

/** Syncs FileSystem Metadata with Storage metadata */
export const genMetadata = async () => {
try {
const knownBlockTypes = new Set(getBlocks().map((x) => x.type));
const paths = [];
const blockPaths: string[] = [];

const walker = walk(join(DECO_FOLDER, BLOCKS_FOLDER), {
includeDirs: false,
Expand All @@ -67,25 +135,40 @@ export const genMetadata = async () => {
});

for await (const entry of walker) {
paths.push(entry.path);
blockPaths.push(entry.path);
}

const entries = await Promise.all(
paths.map(async (path) =>
const blockEntries = await Promise.all(
blockPaths.map(async (path) =>
[
`/${path.replaceAll("\\", "/")}`,
JSON.parse(await Deno.readTextFile(path)),
] as [string, unknown]
),
);

const metadata = Object.fromEntries(entries.map((
[path, content],
) => [path, inferMetadata(content, knownBlockTypes)]));
const blockMetadata: Map<string, Metadata> = new Map();
const blockPathByName = new Map<string, string>();

for (const [path, content] of blockEntries) {
const meta = inferMetadata(content, knownBlockTypes);
if (meta !== null) {
blockMetadata.set(path, meta);
const blockName = parseBlockId(basename(path));
blockPathByName.set(blockName, path);
}
}

const pathname = join(Deno.cwd(), METADATA_PATH);
await ensureFile(pathname);
await Deno.writeTextFile(pathname, JSON.stringify(metadata));
for (const [path, content] of blockEntries) {
mapBlockReferences(content, blockMetadata, blockPathByName, path);
}

const metadataPath = join(Deno.cwd(), METADATA_PATH);
await ensureFile(metadataPath);
await Deno.writeTextFile(
metadataPath,
JSON.stringify(Object.fromEntries(blockMetadata)),
);
} catch (error) {
console.error("Error while auto-generating blocks.json", error);
}
Expand Down
Loading