Skip to content

Commit

Permalink
feat: merge VLE 1.2.0 branch (#565)
Browse files Browse the repository at this point in the history
Merges the 1.2.0 branch, which includes changes and features for VLE,
into main.

---------

Co-authored-by: Alexis Sanehisa <[email protected]>
Co-authored-by: Jacob <[email protected]>
Co-authored-by: briantstephan <[email protected]>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Brian Baugher <[email protected]>
Co-authored-by: Ben Life <[email protected]>
  • Loading branch information
7 people authored Jan 24, 2025
1 parent 07b9600 commit 34ed444
Show file tree
Hide file tree
Showing 32 changed files with 1,903 additions and 139 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
PagesJS is a collection of tools that make it easy to develop on [Yext Pages](https://www.yext.com/platform/pages). It provides 2 main tools:

1. A default development server, backed by [Vite](https://vitejs.dev/), that makes local development fast and easy.
1. A Vite plugin used to bundle your assets and templates for Yext Pages.
2. A Vite plugin used to bundle your assets and templates for Yext Pages.

## Packages

Expand All @@ -17,11 +17,11 @@ PagesJS is a collection of tools that make it easy to develop on [Yext Pages](ht
## Utility Functions

| Function |
| -------------------------------------------------------------------------------------------------------- | --- |
| -------------------------------------------------------------------------------------------------------- |
| [fetch()](https://github.com/yext/pages/blob/main/packages/pages/src/util/README.md#fetch) |
| [getRuntime()](https://github.com/yext/pages/blob/main/packages/pages/src/util/README.md#getRuntime) |
| [isProduction()](https://github.com/yext/pages/blob/main/packages/pages/src/util/README.md#isProduction) |
| [useDocument()](https://github.com/yext/pages/blob/main/packages/pages/src/util/README.md#useDocument) | |
| [useDocument()](https://github.com/yext/pages/blob/main/packages/pages/src/util/README.md#useDocument) |

## Development

Expand Down
11 changes: 11 additions & 0 deletions packages/pages/etc/pages.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,17 @@ export interface TemplateConfig {
streamId?: string;
}

// @internal
export interface TemplateManifest {
templates: {
name: string;
description: string;
exampleSiteUrl: string;
layoutRequired: boolean;
defaultLayoutData: string;
}[];
}

// @public
export interface TemplateModule<
T extends TemplateProps,
Expand Down
124 changes: 122 additions & 2 deletions packages/pages/src/common/src/feature/stream.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { describe, it, expect } from "vitest";
import { convertTemplateConfigToStreamConfig, StreamConfig } from "./stream.js";
import { describe, it, expect, vi, afterEach } from "vitest";
import {
convertTemplateConfigToStreamConfig,
StreamConfig,
formatSiteStream,
readSiteStream,
} from "./stream.js";
import { TemplateConfigInternal } from "../template/internal/types.js";
import fs from "fs";
import { ProjectStructure } from "../project/structure.js";

describe("stream", () => {
it("returns void if no stream", async () => {
Expand Down Expand Up @@ -45,3 +52,116 @@ describe("stream", () => {
expect(streamConfig).toEqual(expectedStreamConfig);
});
});

const siteStreamPath = "foo/bar";

describe("formatSiteStream", () => {
it("errors and exits when there are multiple entityIds", () => {
const testJson = {
$id: "site-stream",
filter: { entityIds: ["1234", "123"] },
localization: { locales: ["en"] },
fields: [],
};
const mockExit = vi
.spyOn(process, "exit")
.mockImplementation(() => undefined as never);
formatSiteStream(testJson, siteStreamPath);
expect(mockExit).toHaveBeenCalledWith(1);
});

it("returns expected entityId", () => {
const testJson = {
$id: "my-site-stream",
filter: { entityIds: ["1234"] },
localization: { locales: ["en"] },
fields: [],
};
const expectedJson = {
id: "my-site-stream",
entityId: "1234",
localization: { locales: ["en"] },
fields: [],
};
expect(formatSiteStream(testJson, siteStreamPath)).toEqual(expectedJson);
});

it("returns expected full config", () => {
const testJson = {
$id: "site-stream-123",
fields: ["meta", "name"],
filter: { entityIds: ["1234"] },
source: "foo",
localization: { locales: ["en"] },
};
const expectedJson = {
id: "site-stream-123",
entityId: "1234",
fields: ["meta", "name"],
localization: { locales: ["en"] },
};
expect(formatSiteStream(testJson, siteStreamPath)).toEqual(expectedJson);
});
});

describe("readSiteStream", () => {
afterEach(() => {
if (fs.existsSync("config.yaml")) {
fs.rmSync("config.yaml");
}
if (fs.existsSync("sites-config/site-stream.json")) {
fs.rmSync("sites-config", { recursive: true, force: true });
}
});

const projectStructure = new ProjectStructure({});

it("reads siteStream from config.yaml", () => {
const path = "config.yaml";
fs.writeFileSync(
path,
`siteStream:
id: site-stream
entityId: site-stream-from-yaml
fields:
- c_visualLayouts.c_visualConfiguration
localization:
locales:
- en
`
);
const siteStream = readSiteStream(projectStructure);
expect(siteStream).toEqual({
id: "site-stream",
entityId: "site-stream-from-yaml",
fields: ["c_visualLayouts.c_visualConfiguration"],
localization: { locales: ["en"] },
});
});

it("reads siteStream from sites-config/sites-stream.json", () => {
projectStructure.getSitesConfigPath;
const path = "sites-config/site-stream.json";
fs.mkdirSync("sites-config");
fs.writeFileSync(
path,
`
{
"$id": "site-stream",
"filter": {
"entityIds": ["site-stream-from-json"]
},
"fields": ["c_visualLayouts.c_visualConfiguration"],
"localization": {"locales": ["en"]}
}
`
);
const siteStream = readSiteStream(projectStructure);
expect(siteStream).toEqual({
id: "site-stream",
entityId: "site-stream-from-json",
fields: ["c_visualLayouts.c_visualConfiguration"],
localization: { locales: ["en"] },
});
});
});
93 changes: 93 additions & 0 deletions packages/pages/src/common/src/feature/stream.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import fs from "fs";
import path from "path";
import YAML from "yaml";

import { readJsonSync } from "../../../upgrade/migrateConfig.js";
import { TemplateConfigInternal } from "../template/internal/types.js";
import { RedirectConfigInternal } from "../redirect/internal/types.js";
import { ProjectStructure } from "../project/structure.js";
import { logErrorAndExit } from "../../../util/logError.js";
import { Stream } from "../template/types.js";

/**
* The shape of data that represents a stream configuration.
Expand Down Expand Up @@ -39,6 +46,38 @@ export interface StreamConfig {
};
}

/**
* The shape of data that represents a site stream.
* Similar to {@link StreamConfig} but there can only be one entityId.
*/
export interface SiteStream {
/** Identifies the stream */
id: string;
/** The entity id of the site stream */
entityId: string;
/** The fields to apply to the stream */
fields: string[];
/** The localization used by the filter. Either set primary: true or specify a locales array. */
localization:
| {
/** The entity profiles languages to apply to the stream. */
locales: string[];
primary?: never;
}
| {
/** Use the primary profile language. */
primary: true;
locales?: never;
};
/** The transformation to apply to the stream */
transform?: {
/** The option fields to be expanded to include the display fields, numeric values, and selected boolean */
expandOptionFields?: string[];
/** The option fields to be replaced with display names */
replaceOptionValuesWithDisplayNames?: string[];
};
}

/**
* Converts a {@link TemplateConfig.config.stream} into a valid {@link StreamConfig}.
*/
Expand Down Expand Up @@ -85,3 +124,57 @@ export const convertRedirectConfigToStreamConfig = (
};
}
};

/**
* Loads the site stream specified in config.yaml or site-stream.json into a {@link SiteStream}.
*/
export const readSiteStream = (
projectStructure: ProjectStructure
): SiteStream | undefined => {
// read site stream from deprecated sites-config directory if it exists
const siteStreamJsonPath = path.resolve(
projectStructure.getSitesConfigPath().path,
projectStructure.config.sitesConfigFiles.siteStream
);
if (fs.existsSync(siteStreamJsonPath)) {
const sitesJson = readJsonSync(siteStreamJsonPath);
return formatSiteStream(sitesJson, siteStreamJsonPath);
}

// read site stream from config.yaml
const configYamlPath = projectStructure.getConfigYamlPath().getAbsolutePath();
if (fs.existsSync(configYamlPath)) {
const yamlDoc = YAML.parse(fs.readFileSync(configYamlPath, "utf-8"));
if (yamlDoc.siteStream) {
yamlDoc.siteStream.entityId = yamlDoc.siteStream?.entityId?.toString();
return yamlDoc.siteStream;
}
}

return;
};

/**
* Converts the deprecated format of a siteStream specified in site-stream.json into
* the format of a siteStream specified in config.yaml
*/
export const formatSiteStream = (
sitesJson: Stream,
siteStreamPath: string
): SiteStream => {
let entityId;
if (sitesJson.filter?.entityIds && sitesJson.filter?.entityIds.length === 1) {
entityId = sitesJson.filter.entityIds[0];
} else if (sitesJson.filter?.entityIds) {
logErrorAndExit(
`Unable to migrate ${siteStreamPath} due to multiple entityIds`
);
}

return {
id: sitesJson.$id, // Replace $id with id and keeps id in the first position
entityId: entityId?.toString() || "",
localization: sitesJson.localization,
fields: sitesJson.fields,
};
};
32 changes: 32 additions & 0 deletions packages/pages/src/common/src/parsers/puckConfigParser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, it, expect } from "vitest";
import fs from "node:fs";
import { addDataToPuckConfig } from "./puckConfigParser.js";

describe("addDataToPuckConfig", () => {
it("should throw an error if the filepath is invalid", () => {
expect(() => addDataToPuckConfig("fileName", "invalid/filepath")).toThrow(
'Filepath "invalid/filepath" is invalid.'
);
});

it("correctly adds new config to the puck config file", () => {
try {
fs.writeFileSync(
"test.tsx",
`export const componentRegistry = new Map<string, Config<any>>([
["location", locationConfig],
]);`
);
addDataToPuckConfig("foo", "test.tsx");
const modifiedContent = fs.readFileSync("test.tsx", "utf-8");
expect(modifiedContent).toContain('["foo", fooConfig]');
expect(modifiedContent).toContain(
`export const fooConfig: Config<FooProps>`
);
} finally {
if (fs.existsSync("test.tsx")) {
fs.unlinkSync("test.tsx");
}
}
});
});
44 changes: 44 additions & 0 deletions packages/pages/src/common/src/parsers/puckConfigParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fs from "node:fs";
import SourceFileParser, { createTsMorphProject } from "./sourceFileParser.js";
import { newConfig } from "../../../scaffold/template/sampleTemplates.js";
import { SyntaxKind } from "ts-morph";

/**
* Adds variables to the puck config file and adds the new config to
* the exported map.
* @param fileName template name with invalid chars and spaces removed
* @param filepath /src/ve.config.tsx
*/
export function addDataToPuckConfig(fileName: string, filepath: string) {
if (!fs.existsSync(filepath)) {
throw new Error(`Filepath "${filepath}" is invalid.`);
}
const parser = new SourceFileParser(filepath, createTsMorphProject());

const puckConfigsStatement = parser.getVariableStatement("componentRegistry");

const formattedTemplateName =
fileName.charAt(0).toUpperCase() + fileName.slice(1);

const puckConfigsStartLocation = puckConfigsStatement.getStart();
parser.insertStatement(
newConfig(formattedTemplateName, fileName),
puckConfigsStartLocation
);

const puckConfigsDeclaration =
parser.getVariableDeclaration("componentRegistry");
const puckConfigsInitializer = puckConfigsDeclaration.getInitializer();
if (
puckConfigsInitializer &&
puckConfigsInitializer.getKind() === SyntaxKind.NewExpression
) {
const newExpression = puckConfigsInitializer;
const puckConfigsArray = newExpression.getFirstChildByKindOrThrow(
SyntaxKind.ArrayLiteralExpression
);
puckConfigsArray.addElement(`["${fileName}", ${fileName}Config]`);
}
parser.format();
parser.save();
}
Loading

0 comments on commit 34ed444

Please sign in to comment.