Skip to content

Commit

Permalink
[🔥AUDIT🔥] [rg.audit.1] Add custom flow file for render-environment-js…
Browse files Browse the repository at this point in the history
…dom to work around flowgen issue (#616)

🖍 _This is an audit!_ 🖍

## Summary:
- Add the faster flowgen script from Wonder Blocks, and include the "custom overrides" support.
- Fix some flow type issues for core and render-environment-jsdom.

Issue: FEI-4960

## Test plan:
`yarn clean && yarn build && yarn build:types && yarn build:flowtypes`

Also check that the types will work for the usages I have to hand.

Author: somewhatabstract

Auditors: somewhatabstract, #typescript, #frontend-infra

Required Reviewers:

Approved By:

Checks: ⌛ Lint, typecheck, and coverage check (ubuntu-latest, 16.x), ✅ gerald, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ⌛ Analyze (javascript), ⏭  dependabot

Pull Request URL: #616
  • Loading branch information
somewhatabstract authored Apr 3, 2023
1 parent 7a3ba71 commit 0299cc9
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 10 deletions.
6 changes: 6 additions & 0 deletions .changeset/eleven-trains-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/wonder-stuff-render-environment-jsdom": patch
"@khanacademy/wonder-stuff-core": patch
---

Fix some errors in the generated flow types
57 changes: 47 additions & 10 deletions build-scripts/gen-flow-types.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,70 @@
import {execFileSync} from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as fglob from "fast-glob";
import {compiler, beautify} from "flowgen";

const rootDir = path.join(__dirname, "..");
const files = fglob.sync("packages/wonder-stuff-*/dist/**/*.d.ts", {
cwd: rootDir,
});

if (files.length) {
console.log(`found ${files.length} files`);
} else {
throw new Error("no typescript type definitions found");
}

// The node API for `flowgen` doesn't have an option to add a flow header
// so we need to do it ourselves.
const HEADER = `/**
* Flowtype definitions for data
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.21.0
* @flow
*/
`;

let errorCount = 0;
for (const inFile of files) {
const outFile = inFile.replace(".d.ts", ".js.flow");
const args = ["flowgen", inFile, "-o", outFile, "--add-flow-header"];

const overrideFile = outFile.replace("/dist/", "/src/");
if (fs.existsSync(overrideFile)) {
console.log(`copying\nfrom: ${overrideFile}\nto: ${outFile}`);
fs.cpSync(
path.join(rootDir, overrideFile),
path.join(rootDir, outFile),
);
continue;
}

try {
execFileSync("yarn", args, {cwd: rootDir});
console.log(`✅ wrote: ${outFile}`);
const source = fs.readFileSync(path.join(rootDir, inFile), "utf-8");
const options = {inexact: false};
let contents: string = compiler.compileDefinitionString(
source,
options,
);
contents = beautify(contents);

// `flowgen` sometimes outputs code that uses `mixins` instead of `extends`
// so we do some post-processing on the files to clean that up.
const contents = fs.readFileSync(path.join(rootDir, outFile), "utf-8");
if (contents.includes(" mixins ")) {
contents = contents.replace(/ mixins /g, " extends ");
console.log("replacing 'mixins' with 'extends'");
fs.writeFileSync(
path.join(rootDir, outFile),
contents.replace(/ mixins /g, " extends "),
"utf-8",
);
}

fs.writeFileSync(
path.join(rootDir, outFile),
HEADER + contents,
"utf-8",
);
console.log(`✅ wrote: ${outFile}`);
} catch (e) {
errorCount += 1;
console.log(`❌ error processing: ${inFile}: ${e}`);
}
}

// Fail the build step if there were any files we couldn't process
process.exit(errorCount);
34 changes: 34 additions & 0 deletions packages/wonder-stuff-core/src/types.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Flowtype definitions for data
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.21.0
* @flow
*/
export type MetadataPrimitive = string | number | boolean | null | void;
export type MetadataArray<T> = Array<T | MetadataArray<T>>;
/**
* A collection of data.
*/
export type Metadata = {
[name: string]:
| Metadata
| MetadataPrimitive
| MetadataArray<MetadataPrimitive | Metadata>,
};
/**
* A secret that is a string.
*
* This opaque type makes it clearer when secrets are being used and enforces
* the need for explicit casting if they must be used as a string.
*/
export opaque type SecretString = string;
/**
* A collection of secrets keyed by their names.
*/
export type Secrets = {
+[key: string]: SecretString,
};
/**
* Make a read-only type mutable.
*/
export type Mutable<T> = $ObjMapi<T, <P>(P) => $ElementType<T, P>>;
111 changes: 111 additions & 0 deletions packages/wonder-stuff-render-environment-jsdom/src/types.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Flowtype definitions for types
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.21.0
* @flow
*/

import type { ResourceLoader } from "jsdom";
import type {
RenderAPI,
ICloseable,
} from "@khanacademy/wonder-stuff-render-server";

/**
* Gate API for control flow.
*/
export type IGate = {
/**
* Open the gate.
*/
open(): void,

/**
* Close the gate.
*/
close: () => Promise<void> | void,

/**
* True, if the gate is open; otherwise, false.
*/
isOpen: boolean,
...
} & ICloseable;

/**
* Standard timer API as implemented by Node's global or a browser window.
*/
export interface ITimerAPI {
setTimeout: $PropertyType<typeof window, "setTimeout">;
setInterval: $PropertyType<typeof window, "setInterval">;
requestAnimationFrame: $PropertyType<typeof window, "requestAnimationFrame">;
}
/**
* A resource loader for use with JSDOM that can also have a close method for
* tidying up resources deterministically.
*/
export interface CloseableResourceLoader extends ICloseable, ResourceLoader {
/**
* Close the resource loader and tidy up resources.
*
* This is optional.
*/
+close?: () => void;
};

/**
* Configuration for a JSDOM environment.
*/
export interface IJSDOMConfiguration {
/**
* The name of the callback function that should be exposed by the
* environment for renderable code to use when registering for rendering.
*/
registrationCallbackName: string;

/**
* Get a JSDOM resource loader for the given render request.
* @param {string} url The URL that is to be rendered.
* @param {RenderAPI} renderAPI An API of utilities for assisting with the
* render operation.
* @returns {CloseableResourceLoader} A ResourceLoader instance for use
* with JSDOM that can optionally have a close() method, which will be
* invoked when the render completes.
*/
getResourceLoader(url: string, renderAPI: RenderAPI): CloseableResourceLoader;

/**
* Get the list of file URLs to retrieve and execute for the given request.
* @param {string} url The URL that is to be rendered.
* @param {RenderAPI} renderAPI An API of utilities for assisting with the
* render operation.
* @param {(url: string) => ?Promise<Buffer>} fetchFn Function to fetch a URL. Using this ensures proper tidy-up of associated
* sockets and agents.
* @returns {Promise<Array<string>>} An ordered array of absolute URLs for
* the JavaScript files that are to be executed. These are exectued in the
* same order as the array.
*/
getFileList(
url: string,
renderAPI: RenderAPI,
fetchFn: (url: string) => Promise<Buffer> | null | void
): Promise<Array<string>>;

/**
* Perform any additional environment setup.
*
* This method gets access to the actual environment in which code will
* execute. Be careful what you do.
* @param {string} url The URL that is to be rendered.
* @param {RenderAPI} renderAPI An API of utilities for assisting with the
* render operation.
* @param {any} vmContext The actual environment that is being setup.
* @returns {?Promise<void>} A promise that the additional setup is done.
*/
afterEnvSetup(
url: string,
fileURLs: $ReadOnlyArray<string>,
renderAPI: RenderAPI,
vmContext?: any
): Promise<ICloseable | null | void>;
}

0 comments on commit 0299cc9

Please sign in to comment.