Skip to content

Commit

Permalink
Add required variables feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Vorlias committed Jan 11, 2023
1 parent e09607e commit bb6b344
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 9 deletions.
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 120,
"tabWidth": 4,
"useTabs": true,
"arrowParens": "avoid"
}
3 changes: 3 additions & 0 deletions example/src/shared/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ if ($env.boolean("ANALYTICS_API_URL")) {
}

export const DefaultValue = $env.number("DEFAULT_VALUE", 0.05);
export const DefaultString = $env.string("DEFAULT_STR");

// $env.expectString("TEST", "A 'TEST' variable is required in your environment.");

const test: number = DefaultValue;

Expand Down
12 changes: 12 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export namespace $env {
export function string(name: string): string | undefined;
export function string(name: string, defaultValue: string): string;

// /**
// * Attempts to fetch the given environment variable as a string - will throw a compiler error otherwise.
// * @param name The name of the environment variable to expect
// */
// export function expectString<_TCompilerError extends string>(name: string, message?: _TCompilerError): string;

/**
* Converts the given environment variable to a boolean - if not set will be set `defaultValue` or `false`.
*
Expand Down Expand Up @@ -41,6 +47,12 @@ export namespace $env {
*/
export function number(name: string): number | undefined;
export function number(name: string, defaultValue: number): number;

// /**
// * Attempts to fetch the given environment variable as a number - will throw a compiler error otherwise.
// * @param name The name of the environment variable to expect
// */
// export function expectNumber(name: string): number;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "rbxts-transform-env",
"version": "2.0.4",
"version": "2.1.0-beta.0",
"description": "Transformer for Roblox TypeScript compiler that allows getting values of process.env as string literals",
"main": "out/index.js",
"scripts": {
"build": "tsc",
"yalc": "tsc && yalc push",
"prepublish": "npm run build"
},
"repository": {
Expand Down
4 changes: 4 additions & 0 deletions src/class/environmentProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export class EnvironmentProvider {
return this.variables.get(name);
}

public has(name: string): boolean {
return this.variables.has(name);
}

public getAsNumber(name: string): number | undefined {
const value = this.get(name);
if (value && value.match(/\d+/gi)) {
Expand Down
3 changes: 3 additions & 0 deletions src/class/transformState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import { EnvironmentProvider } from "./environmentProvider";
import { LoggerProvider } from "./logProvider";
import { SymbolProvider } from "./symbolProvider";

type Handler = "warn" | "error" | "errorOnProduction";

export interface TransformConfiguration {
verbose?: boolean;
defaultEnvironment: string;
shortCircuitNodeEnv: boolean;
expectedVariables: Record<string, [Handler, string] | Handler> | undefined;
}

export class TransformState {
Expand Down
43 changes: 41 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
import ts from "typescript";
import { TransformConfiguration, TransformState } from "./class/transformState";
import { transformFile } from "./transform/transformFile";
import fs from "fs";
import { LoggerProvider } from "./class/logProvider";
import { EnvironmentProvider } from "./class/environmentProvider";

const DEFAULTS: TransformConfiguration = {
verbose: false,
defaultEnvironment: "production",
shortCircuitNodeEnv: true,
expectedVariables: undefined,
};

export default function transform(program: ts.Program, userConfiguration: TransformConfiguration) {
userConfiguration = { ...DEFAULTS, ...userConfiguration };
const isProduction = (process.env.NODE_ENV ?? userConfiguration.defaultEnvironment) === "production";

if (process.argv.includes("--verbose")) {
userConfiguration.verbose = true;
Expand All @@ -26,11 +28,48 @@ export default function transform(program: ts.Program, userConfiguration: Transf
logger.write("\n");
}
logger.infoIfVerbose("Loaded environment transformer");
let performedVariableCheck = false;

return (context: ts.TransformationContext): ((file: ts.SourceFile) => ts.Node) => {
const state = new TransformState(program, context, userConfiguration, logger);

if (state.symbolProvider.moduleFile === undefined) {
return (file) => file;
return file => file;
}

if (userConfiguration.expectedVariables && !performedVariableCheck) {
let hasDiagnostic = false;

for (const [variable, variableRequireConfig] of Object.entries(userConfiguration.expectedVariables)) {
const hasVariable = state.environmentProvider.has(variable);
if (!hasVariable) {
if (typeof variableRequireConfig === "string") {
if (
variableRequireConfig === "error" ||
(isProduction && variableRequireConfig === "errorOnProduction")
) {
logger.error(`Expected enviroment variable: '${variable}'`);
hasDiagnostic = true;
} else if (variableRequireConfig === "warn") {
logger.warnIfVerbose(`Missing environment variable '${variable}'`);
}
} else {
const [handler, message] = variableRequireConfig;
if (handler === "error" || (isProduction && handler === "errorOnProduction")) {
logger.error(message);
hasDiagnostic = true;
} else if (handler === "warn") {
logger.warnIfVerbose(message);
}
}
}
}

if (hasDiagnostic) {
throw new Error(`Required environment variable(s) have not been configured - see above`);
}

performedVariableCheck = true;
}

return (file: ts.SourceFile) => {
Expand Down
17 changes: 11 additions & 6 deletions src/transform/macros/call/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export function getEnvDefaultValue(expression: ts.CallExpression): ts.Expression
}
}

export function isUnsafeToPrint(variable: string): boolean {
const valueLower = variable.toLowerCase();
return valueLower.includes("token") || valueLower.includes("api") || valueLower.includes("key");
}

export const EnvCallAsStringMacro: CallMacro = {
getSymbol(state: TransformState) {
const envSymbol = state.symbolProvider.moduleFile?.envNamespace;
Expand All @@ -35,13 +40,13 @@ export const EnvCallAsStringMacro: CallMacro = {
(variableValue !== undefined ? toExpression(variableValue) : getEnvDefaultValue(callExpression)) ??
factory.createIdentifier("undefined");

if (state.config.verbose) {
if (state.config.verbose && !variableName.toLowerCase().includes("token")) {
state.logger.infoIfVerbose(
`Transform variable ${variableName} to ${printer.printNode(
ts.EmitHint.Expression,
expression,
callExpression.getSourceFile(),
)}`,
`Transform variable ${variableName} to ${
isUnsafeToPrint(variableName)
? "***"
: printer.printNode(ts.EmitHint.Expression, expression, callExpression.getSourceFile())
}`,
);
console.log("\t", callExpression.getSourceFile().fileName);
}
Expand Down

0 comments on commit bb6b344

Please sign in to comment.