Skip to content

Commit

Permalink
feat(generate): use nodecg-types in generated bundles (#25)
Browse files Browse the repository at this point in the history
Refer to codeoverflow-org/nodecg-io#239 for more information about `nodecg-types`. This PR updates the generation logic to generate bundles depending on `nodecg-types` instead of `nodecg` for typings when generating a TypeScript bundle.
  • Loading branch information
hlxid authored Nov 14, 2021
1 parent 7458776 commit 6201338
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/generate/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function genExtension(opts: GenerationOptions, install: ProductionI
genImport(writer, "requireService", opts.corePackage.name, opts.language);

if (opts.language === "typescript") {
genImport(writer, "NodeCG", "nodecg/types/server", opts.language);
genImport(writer, "NodeCG", "nodecg-types/types/server", opts.language);
// Service import statements
services.forEach((svc) => {
genImport(writer, svc.clientName, svc.packageName, opts.language);
Expand Down
10 changes: 3 additions & 7 deletions src/generate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const generateModule: CommandModule = {

const opts = await promptGenerationOpts(nodecgDir, install);

await generateBundle(nodecgDir, opts, install);
await generateBundle(opts, install);

logger.success(`Successfully generated bundle ${opts.bundleName}.`);
} catch (e) {
Expand Down Expand Up @@ -63,11 +63,7 @@ export function ensureValidInstallation(install: Installation | undefined): inst
return true;
}

export async function generateBundle(
nodecgDir: string,
opts: GenerationOptions,
install: ProductionInstallation,
): Promise<void> {
export async function generateBundle(opts: GenerationOptions, install: ProductionInstallation): Promise<void> {
// Create dir if necessary
if (!(await directoryExists(opts.bundlePath))) {
await fs.promises.mkdir(opts.bundlePath);
Expand All @@ -84,7 +80,7 @@ export async function generateBundle(
}

// All of these calls only generate files if they are set accordingly in the GenerationOptions
await genPackageJson(nodecgDir, opts);
await genPackageJson(opts);
await genTsConfig(opts);
await genGitIgnore(opts);
await genExtension(opts, install);
Expand Down
17 changes: 8 additions & 9 deletions src/generate/packageJson.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GenerationOptions } from "./prompt";
import { logger } from "../utils/log";
import { getNodeCGVersion } from "../utils/nodecgInstallation";
import { getLatestPackageVersion } from "../utils/npm";
import { genNodeCGDashboardConfig, genNodeCGGraphicConfig } from "./panel";
import { SemVer } from "semver";
Expand All @@ -17,7 +16,7 @@ type Dependency = [string, string];
* @param nodecgDir the directory in which nodecg is installed
* @param opts the options that the user chose for the bundle.
*/
export async function genPackageJson(nodecgDir: string, opts: GenerationOptions): Promise<void> {
export async function genPackageJson(opts: GenerationOptions): Promise<void> {
const serviceDeps: Dependency[] = opts.servicePackages.map((pkg) => [pkg.name, addSemverCaret(pkg.version)]);

const content = {
Expand All @@ -32,7 +31,7 @@ export async function genPackageJson(nodecgDir: string, opts: GenerationOptions)
},
// These scripts are for compiling TS and thus are only needed when generating a TS bundle
scripts: genScripts(opts),
dependencies: Object.fromEntries(await genDependencies(opts, serviceDeps, nodecgDir)),
dependencies: Object.fromEntries(await genDependencies(opts, serviceDeps)),
};

await writeBundleFile(content, opts.bundlePath, "package.json");
Expand All @@ -46,12 +45,12 @@ export async function genPackageJson(nodecgDir: string, opts: GenerationOptions)
* @param nodecgDir the directory in which nodecg is installed
* @return the dependencies for a bundle with the given options.
*/
async function genDependencies(opts: GenerationOptions, serviceDeps: Dependency[], nodecgDir: string) {
async function genDependencies(opts: GenerationOptions, serviceDeps: Dependency[]) {
const core = [opts.corePackage.name, addSemverCaret(opts.corePackage.version)];

if (opts.language === "typescript") {
// For typescript we need core, all services (for typings) and special packages like ts itself or node typings.
const deps = [core, ...serviceDeps, ...(await genTypeScriptDependencies(nodecgDir))];
const deps = [core, ...serviceDeps, ...(await genTypeScriptDependencies())];
deps.sort();
return deps;
} else {
Expand All @@ -65,16 +64,16 @@ async function genDependencies(opts: GenerationOptions, serviceDeps: Dependency[
* and types for node.
* @param nodecgDir the directory in which nodecg is installed. Used to get nodecg version which will be used by nodecg dependency.
*/
async function genTypeScriptDependencies(nodecgDir: string): Promise<Dependency[]> {
logger.debug("Fetching latest typescript and @types/node versions...");
async function genTypeScriptDependencies(): Promise<Dependency[]> {
logger.debug("Fetching latest nodecg-types, typescript and @types/node versions...");
const [nodecgVersion, latestNodeTypes, latestTypeScript] = await Promise.all([
getNodeCGVersion(nodecgDir),
getLatestPackageVersion("nodecg-types"),
getLatestPackageVersion("@types/node"),
getLatestPackageVersion("typescript"),
]);

return [
["nodecg", addSemverCaret(nodecgVersion)],
["nodecg-types", addSemverCaret(nodecgVersion)],
["@types/node", addSemverCaret(latestNodeTypes)],
["typescript", addSemverCaret(latestTypeScript)],
];
Expand Down
22 changes: 7 additions & 15 deletions test/generate/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import { vol } from "memfs";
import {
corePkg,
fsRoot,
nodecgPackageJson,
nodecgPackageJsonStr,
twitchChatPkg,
validDevInstall,
validProdInstall,
} from "../test.util";
import { corePkg, fsRoot, nodecgPackageJsonStr, twitchChatPkg, validDevInstall, validProdInstall } from "../test.util";
import { SemVer } from "semver";
import * as path from "path";
import * as installation from "../../src/utils/installation";
Expand Down Expand Up @@ -56,29 +48,29 @@ describe("generateBundle", () => {
test("should fail if bundle directory already contains files", async () => {
// Create some file inside the directory in which the bundle would be generated.
await vol.promises.writeFile(packageJsonPath, "");
await expect(generateBundle(fsRoot, defaultOpts, validProdInstall)).rejects.toThrow(
await expect(generateBundle(defaultOpts, validProdInstall)).rejects.toThrow(
"already exists and contains files",
);
});

test("should install dependencies", async () => {
const installMock = jest.spyOn(npm, "runNpmInstall").mockResolvedValue();
await generateBundle(fsRoot, defaultOpts, validProdInstall);
await generateBundle(defaultOpts, validProdInstall);

expect(installMock).toHaveBeenCalled();
expect(installMock).toHaveBeenCalledWith(defaultOpts.bundlePath, false);
});

test("should run build if typescript", async () => {
const buildMock = jest.spyOn(npm, "runNpmBuild").mockClear().mockResolvedValue();
await generateBundle(fsRoot, defaultOpts, validProdInstall);
await generateBundle(defaultOpts, validProdInstall);
expect(buildMock).toHaveBeenCalledTimes(1);
expect(buildMock).toHaveBeenCalledWith(defaultOpts.bundlePath);
});

test("should not run build if javascript", async () => {
const buildMock = jest.spyOn(npm, "runNpmBuild").mockClear().mockResolvedValue();
await generateBundle(fsRoot, jsOpts, validProdInstall);
await generateBundle(jsOpts, validProdInstall);
expect(buildMock).toHaveBeenCalledTimes(0);
});
});
Expand All @@ -87,7 +79,7 @@ describe("genPackageJson", () => {
// We don't have a good type for a package.json and this is only testing code so this should be fine.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function genPackageJSON(opts: GenerationOptions = defaultOpts): Promise<any> {
await generateBundle(fsRoot, opts, validProdInstall);
await generateBundle(opts, validProdInstall);
const packageJsonStr = await vol.promises.readFile(packageJsonPath);
if (!packageJsonStr) throw new Error("package.json does not exist");
return JSON.parse(packageJsonStr.toString());
Expand All @@ -112,12 +104,12 @@ describe("genPackageJson", () => {
test("should have all required typing packages as dependency if typescript", async () => {
const deps = (await genPackageJSON(defaultOpts))["dependencies"];
const e = Object.entries(deps);
expect(e).toEqual(expect.arrayContaining([["nodecg", `^${nodecgPackageJson.version}`]]));
expect(e).toEqual(expect.arrayContaining([[twitchChatPkg.name, `^${twitchChatPkg.version}`]]));

// These dependencies should always have the latest version which is fetched by the mocked getLatestPackageVersion
expect(e).toEqual(expect.arrayContaining([["typescript", `^1.2.3`]]));
expect(e).toEqual(expect.arrayContaining([["@types/node", `^1.2.3`]]));
expect(e).toEqual(expect.arrayContaining([["nodecg-types", `^1.2.3`]]));
});

test("should have build scripts if typescript", async () => {
Expand Down

0 comments on commit 6201338

Please sign in to comment.