Skip to content

Commit 31c15c1

Browse files
chore: [AB#13027] convert webflow sync files to ts
1 parent f632a9d commit 31c15c1

23 files changed

+4695
-0
lines changed

shared/src/industry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface Industry {
99
readonly canHavePermanentLocation: boolean;
1010
readonly additionalSearchTerms?: string;
1111
readonly defaultSectorId?: string;
12+
readonly webflowId?: string;
1213
readonly roadmapSteps: AddOn[];
1314
readonly nonEssentialQuestionsIds: string[];
1415
readonly modifications?: TaskModification[];
@@ -62,6 +63,7 @@ export const LookupIndustryById = (id: string | undefined): Industry => {
6263
roadmapSteps: [],
6364
nonEssentialQuestionsIds: [],
6465
naicsCodes: "",
66+
webflowId: "",
6567
isEnabled: false,
6668
industryOnboardingQuestions: {
6769
isProvidesStaffingServicesApplicable: undefined,

web/.dependency-cruiser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ module.exports = {
379379
"(^|/)tsconfig\\.json$", // TypeScript config
380380
"(^|/)(babel|webpack)\\.config\\.(js|cjs|mjs|ts|json)$", // other configs
381381
"src/pages/healthz.tsx", // healthcheck page
382+
"src/scripts/webflow/.*\\.ts$", // webflow scripts have both .mjs and .ts versions
382383
],
383384
},
384385
to: {},

web/jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default {
1818
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/", "<rootDir>/cypress/"],
1919
rootDir: "./",
2020
moduleDirectories: ["node_modules", "<rootDir>"],
21+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "mjs", "json", "node"],
2122
moduleNameMapper: {
2223
"\\.(scss|sass|css)$": "identity-obj-proxy",
2324
"@/components/(.*)": "<rootDir>/src/components/$1",
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import fs from "fs";
2+
import matter from "gray-matter";
3+
import { exportFundings, loadAllFundings, Funding } from "./fundingExport";
4+
5+
jest.mock("fs");
6+
jest.mock("gray-matter");
7+
8+
const mockFs = fs as jest.Mocked<typeof fs>;
9+
const mockMatter = matter as jest.MockedFunction<typeof matter>;
10+
11+
const generateMockFundingExportData = (overrides?: Partial<Omit<Funding, "contentMd">>): Omit<Funding, "contentMd"> => ({
12+
id: "test-id",
13+
name: "Test Funding",
14+
filename: "funding1",
15+
urlSlug: "test-funding",
16+
callToActionLink: "https://example.com",
17+
callToActionText: "Apply Now",
18+
fundingType: "Grant",
19+
programPurpose: "Business Support",
20+
agency: ["Test Agency"],
21+
agencyContact: "contact@test.com",
22+
publishStageArchive: "",
23+
openDate: "2024-01-01",
24+
dueDate: "2024-12-31",
25+
status: "Open",
26+
programFrequency: "Annual",
27+
businessStage: "Early Stage",
28+
employeesRequired: "0",
29+
homeBased: "Yes",
30+
certifications: [],
31+
preferenceForOpportunityZone: "",
32+
county: "",
33+
sector: [],
34+
...overrides,
35+
});
36+
37+
describe("fundingExport", () => {
38+
beforeEach(() => {
39+
jest.clearAllMocks();
40+
});
41+
42+
describe("loadAllFundings", () => {
43+
it("loads all funding files from the directory", () => {
44+
const mockFileNames = ["funding1.md", "funding2.md"];
45+
const mockFundingData = generateMockFundingExportData();
46+
47+
(mockFs.readdirSync as jest.Mock).mockReturnValue(mockFileNames);
48+
mockFs.readFileSync.mockReturnValue("mock file content");
49+
mockMatter.mockReturnValue({
50+
data: mockFundingData,
51+
content: "Test content",
52+
excerpt: "",
53+
matter: "",
54+
stringify: jest.fn(),
55+
orig: Buffer.from(""),
56+
language: "",
57+
});
58+
59+
const fundings = loadAllFundings();
60+
61+
expect(fundings).toHaveLength(2);
62+
expect(mockFs.readdirSync).toHaveBeenCalledTimes(1);
63+
expect(mockFs.readFileSync).toHaveBeenCalledTimes(2);
64+
});
65+
66+
it("converts funding markdown with escaped quotes", () => {
67+
const mockFileNames = ["funding1.md"];
68+
const mockFundingData = generateMockFundingExportData({
69+
id: "test-id",
70+
name: 'Test "Funding"',
71+
filename: "funding1",
72+
});
73+
74+
(mockFs.readdirSync as jest.Mock).mockReturnValue(mockFileNames);
75+
mockFs.readFileSync.mockReturnValue("mock file content");
76+
mockMatter.mockReturnValue({
77+
data: mockFundingData,
78+
content: 'Content with "quotes"',
79+
excerpt: "",
80+
matter: "",
81+
stringify: jest.fn(),
82+
orig: Buffer.from(""),
83+
language: "",
84+
});
85+
86+
const fundings = loadAllFundings();
87+
88+
expect(fundings[0].contentMd).toBe('Content with ""quotes""');
89+
expect(fundings[0].filename).toBe("funding1");
90+
});
91+
});
92+
93+
describe("exportFundings", () => {
94+
it("exports fundings to CSV file", () => {
95+
const mockFileNames = ["funding1.md"];
96+
const mockFundingData = generateMockFundingExportData({
97+
id: "test-id",
98+
name: "Test Funding",
99+
filename: "funding1",
100+
urlSlug: "test-funding",
101+
callToActionLink: "https://example.com",
102+
callToActionText: "Apply Now",
103+
fundingType: "Grant",
104+
programPurpose: "Business Support",
105+
agency: ["Test Agency"],
106+
agencyContact: "contact@test.com",
107+
publishStageArchive: "",
108+
openDate: "2024-01-01",
109+
dueDate: "2024-12-31",
110+
status: "Open",
111+
programFrequency: "Annual",
112+
businessStage: "Early Stage",
113+
employeesRequired: "0",
114+
homeBased: "Yes",
115+
certifications: [],
116+
preferenceForOpportunityZone: "",
117+
county: "",
118+
sector: [],
119+
});
120+
121+
(mockFs.readdirSync as jest.Mock).mockReturnValue(mockFileNames);
122+
mockFs.readFileSync.mockReturnValue("test content");
123+
mockMatter.mockReturnValue({
124+
data: mockFundingData,
125+
content: "Test content",
126+
excerpt: "",
127+
matter: "",
128+
stringify: jest.fn(),
129+
orig: Buffer.from(""),
130+
language: "",
131+
});
132+
mockFs.writeFileSync.mockImplementation(() => {
133+
// Mock implementation
134+
});
135+
136+
exportFundings();
137+
138+
expect(mockFs.writeFileSync).toHaveBeenCalledWith("fundings.csv", expect.stringContaining("id,name,filename"));
139+
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
140+
"fundings.csv",
141+
expect.stringContaining('"test-id","Test Funding"')
142+
);
143+
});
144+
145+
it("trims content markdown in CSV output", () => {
146+
const mockFileNames = ["funding1.md"];
147+
const mockFundingData = generateMockFundingExportData({
148+
id: "test-id",
149+
name: "Test Funding",
150+
filename: "funding1",
151+
urlSlug: "test-funding",
152+
callToActionLink: "",
153+
callToActionText: "",
154+
fundingType: "",
155+
programPurpose: "",
156+
agency: [],
157+
agencyContact: "",
158+
publishStageArchive: "",
159+
openDate: "",
160+
dueDate: "",
161+
status: "",
162+
programFrequency: "",
163+
businessStage: "",
164+
employeesRequired: "",
165+
homeBased: "",
166+
certifications: [],
167+
preferenceForOpportunityZone: "",
168+
county: "",
169+
sector: [],
170+
});
171+
172+
(mockFs.readdirSync as jest.Mock).mockReturnValue(mockFileNames);
173+
mockFs.readFileSync.mockReturnValue("test content");
174+
mockMatter.mockReturnValue({
175+
data: mockFundingData,
176+
content: " Test content with whitespace ",
177+
excerpt: "",
178+
matter: "",
179+
stringify: jest.fn(),
180+
orig: Buffer.from(""),
181+
language: "",
182+
});
183+
mockFs.writeFileSync.mockImplementation(() => {
184+
// Mock implementation
185+
});
186+
187+
exportFundings();
188+
189+
const csvCall = mockFs.writeFileSync.mock.calls[0][1] as string;
190+
expect(csvCall).toContain('Test content with whitespace"');
191+
expect(csvCall).not.toContain(' Test content with whitespace "');
192+
});
193+
});
194+
});

web/src/scripts/fundingExport.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import fs from "fs";
2+
import matter from "gray-matter";
3+
import path from "path";
4+
5+
const fundingDir = path.resolve(`${__dirname}/../../../content/src/fundings`);
6+
7+
export interface Funding {
8+
id: string;
9+
name: string;
10+
filename: string;
11+
urlSlug: string;
12+
callToActionLink: string;
13+
callToActionText: string;
14+
fundingType: string;
15+
programPurpose: string;
16+
agency: string[];
17+
agencyContact: string;
18+
publishStageArchive: string;
19+
openDate: string;
20+
dueDate: string;
21+
status: string;
22+
programFrequency: string;
23+
businessStage: string;
24+
employeesRequired: string;
25+
homeBased: string;
26+
certifications?: string[];
27+
preferenceForOpportunityZone: string;
28+
county: string;
29+
sector: string[];
30+
contentMd: string;
31+
[key: string]: unknown;
32+
}
33+
34+
const convertFundingMd = (oppMdContents: string, filename: string): Funding => {
35+
const matterResult = matter(oppMdContents);
36+
const oppGrayMatter = matterResult.data;
37+
38+
return {
39+
contentMd: matterResult.content.replaceAll('"', '""'),
40+
filename,
41+
...oppGrayMatter,
42+
} as Funding;
43+
};
44+
45+
export const loadAllFundings = (): Funding[] => {
46+
const fileNames = fs.readdirSync(fundingDir);
47+
return fileNames.map((fileName) => {
48+
return loadFundingByFileName(fileName);
49+
});
50+
};
51+
52+
const loadFundingByFileName = (fileName: string): Funding => {
53+
const fullPath = path.join(fundingDir, `${fileName}`);
54+
const fileContents = fs.readFileSync(fullPath, "utf8");
55+
56+
const fileNameWithoutMd = fileName.split(".md")[0];
57+
return convertFundingMd(fileContents, fileNameWithoutMd);
58+
};
59+
60+
export const exportFundings = (): void => {
61+
const fundings = loadAllFundings();
62+
let csvContent = `id,name,filename,urlSlug,callToActionLink,callToActionText,fundingType,programPurpose,agency,agencyContact,publishStageArchive,openDate,dueDate,status,programFrequency,businessStage,employeesRequired,homeBased,certifications,preferenceForOpportunityZone,county,sector,contentMd\n`;
63+
64+
for (const funding of fundings) {
65+
csvContent += `"${funding.id}","${funding.name}","${funding.filename}","${funding.urlSlug}","${
66+
funding.callToActionLink
67+
}","${funding.callToActionText}","${funding.fundingType}","${funding.programPurpose}","${
68+
funding.agency
69+
}","${funding.agencyContact}","${funding.publishStageArchive}","${funding.openDate}","${
70+
funding.dueDate
71+
}","${funding.status}","${funding.programFrequency}","${funding.businessStage}","${
72+
funding.employeesRequired
73+
}","${funding.homeBased}","${funding.certifications}","${funding.preferenceForOpportunityZone}","${
74+
funding.county
75+
}","${funding.sector}","${funding.contentMd.trim()}"\n`;
76+
}
77+
fs.writeFileSync("fundings.csv", csvContent);
78+
};
79+
80+
if (!process.argv.some((i) => i.includes("fundingExport")) || process.env.NODE_ENV === "test") {
81+
// Skip execution
82+
} else if (process.argv.some((i) => i.includes("--export"))) {
83+
exportFundings();
84+
} else {
85+
console.log("Expected at least one argument! Use one of the following: ");
86+
console.log("--export = exports fundings as csv");
87+
}

0 commit comments

Comments
 (0)