Skip to content

Commit

Permalink
Enable caching in RigConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
dmichon-msft committed Aug 26, 2021
1 parent 5e5623c commit 9e2f410
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rig-package",
"comment": "Cache rig.json reads",
"type": "minor"
}
],
"packageName": "@rushstack/rig-package",
"email": "dmichon-msft@users.noreply.github.com"
}
4 changes: 2 additions & 2 deletions common/reviews/api/rig-package.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

// @public
export interface ILoadForProjectFolderOptions {
bypassCache?: boolean;
overrideRigJsonObject?: IRigConfigJson;
projectFolderPath: string;
}
Expand Down Expand Up @@ -33,7 +34,6 @@ export class RigConfig {
readonly rigProfile: string;
tryResolveConfigFilePath(configFileRelativePath: string): string | undefined;
tryResolveConfigFilePathAsync(configFileRelativePath: string): Promise<string | undefined>;
}

}

```
147 changes: 93 additions & 54 deletions libraries/rig-package/src/RigConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ interface IRigConfigOptions {
rigFound: boolean;
filePath: string;
rigPackageName: string;
rigProfile: string;
rigProfile?: string;
}

/**
Expand All @@ -60,6 +60,11 @@ export interface ILoadForProjectFolderOptions {
* If specified, instead of loading the `config/rig.json` from disk, this object will be substituted instead.
*/
overrideRigJsonObject?: IRigConfigJson;

/**
* If specified, force a fresh load instead of returning a cached entry, if one existed.
*/
bypassCache?: boolean;
}

/**
Expand Down Expand Up @@ -91,6 +96,8 @@ export class RigConfig {
public static jsonSchemaPath: string = path.resolve(__dirname, './schemas/rig.schema.json');
private static _jsonSchemaObject: object | undefined = undefined;

private static readonly _configCache: Map<string, RigConfig> = new Map();

/**
* The project folder path that was passed to {@link RigConfig.loadForProjectFolder},
* which maybe an absolute or relative path.
Expand Down Expand Up @@ -160,13 +167,15 @@ export class RigConfig {
private _resolvedProfileFolder: string | undefined;

private constructor(options: IRigConfigOptions) {
this.projectFolderOriginalPath = options.projectFolderPath;
this.projectFolderPath = path.resolve(options.projectFolderPath);
const { projectFolderPath, rigFound, filePath, rigPackageName, rigProfile = 'default' } = options;

this.projectFolderOriginalPath = projectFolderPath;
this.projectFolderPath = path.resolve(projectFolderPath);

this.rigFound = options.rigFound;
this.filePath = options.filePath;
this.rigPackageName = options.rigPackageName;
this.rigProfile = options.rigProfile;
this.rigFound = rigFound;
this.filePath = filePath;
this.rigPackageName = rigPackageName;
this.rigProfile = rigProfile;

if (this.rigFound) {
this.relativeProfileFolderPath = 'profiles/' + this.rigProfile;
Expand Down Expand Up @@ -199,80 +208,110 @@ export class RigConfig {
* equal to `false`.
*/
public static loadForProjectFolder(options: ILoadForProjectFolderOptions): RigConfig {
const rigConfigFilePath: string = path.join(options.projectFolderPath, 'config/rig.json');
const { overrideRigJsonObject, projectFolderPath } = options;

let json: IRigConfigJson;
try {
if (options.overrideRigJsonObject) {
json = options.overrideRigJsonObject;
} else {
if (!fs.existsSync(rigConfigFilePath)) {
return new RigConfig({
projectFolderPath: options.projectFolderPath,

rigFound: false,
filePath: '',
rigPackageName: '',
rigProfile: ''
});
}
const fromCache: RigConfig | undefined =
!options.bypassCache && !overrideRigJsonObject
? RigConfig._configCache.get(projectFolderPath)
: undefined;

if (fromCache) {
return fromCache;
}

const rigConfigFilePath: string = path.join(projectFolderPath, 'config/rig.json');

let config: RigConfig | undefined;
let json: IRigConfigJson | undefined = overrideRigJsonObject;
try {
if (!json) {
const rigConfigFileContent: string = fs.readFileSync(rigConfigFilePath).toString();
json = JSON.parse(stripJsonComments(rigConfigFileContent));
json = JSON.parse(stripJsonComments(rigConfigFileContent)) as IRigConfigJson;
}
RigConfig._validateSchema(json);
} catch (error) {
throw new Error(error.message + '\nError loading config file: ' + rigConfigFilePath);
config = RigConfig._handleConfigError(error, projectFolderPath, rigConfigFilePath);
}

return new RigConfig({
projectFolderPath: options.projectFolderPath,
if (!config) {
config = new RigConfig({
projectFolderPath: projectFolderPath,

rigFound: true,
filePath: rigConfigFilePath,
rigPackageName: json.rigPackageName,
rigProfile: json.rigProfile || 'default'
});
rigFound: true,
filePath: rigConfigFilePath,
rigPackageName: json!.rigPackageName,
rigProfile: json!.rigProfile
});
}

if (!overrideRigJsonObject) {
RigConfig._configCache.set(projectFolderPath, config);
}
return config;
}

/**
* An async variant of {@link RigConfig.loadForProjectFolder}
*/
public static async loadForProjectFolderAsync(options: ILoadForProjectFolderOptions): Promise<RigConfig> {
const rigConfigFilePath: string = path.join(options.projectFolderPath, 'config/rig.json');
const { overrideRigJsonObject, projectFolderPath } = options;

let json: IRigConfigJson;
try {
if (options.overrideRigJsonObject) {
json = options.overrideRigJsonObject;
} else {
if (!(await Helpers.fsExistsAsync(rigConfigFilePath))) {
return new RigConfig({
projectFolderPath: options.projectFolderPath,

rigFound: false,
filePath: '',
rigPackageName: '',
rigProfile: ''
});
}
const fromCache: RigConfig | false | undefined =
!options.bypassCache && !overrideRigJsonObject && RigConfig._configCache.get(projectFolderPath);

if (fromCache) {
return fromCache;
}

const rigConfigFilePath: string = path.join(projectFolderPath, 'config/rig.json');

let config: RigConfig | undefined;
let json: IRigConfigJson | undefined = overrideRigJsonObject;
try {
if (!json) {
const rigConfigFileContent: string = (await fs.promises.readFile(rigConfigFilePath)).toString();
json = JSON.parse(stripJsonComments(rigConfigFileContent));
json = JSON.parse(stripJsonComments(rigConfigFileContent)) as IRigConfigJson;
}

RigConfig._validateSchema(json);
} catch (error) {
config = RigConfig._handleConfigError(error, projectFolderPath, rigConfigFilePath);
}

if (!config) {
config = new RigConfig({
projectFolderPath: projectFolderPath,

rigFound: true,
filePath: rigConfigFilePath,
rigPackageName: json!.rigPackageName,
rigProfile: json!.rigProfile
});
}

if (!overrideRigJsonObject) {
RigConfig._configCache.set(projectFolderPath, config);
}
return config;
}

private static _handleConfigError(
error: NodeJS.ErrnoException,
projectFolderPath: string,
rigConfigFilePath: string
): RigConfig {
if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') {
throw new Error(error.message + '\nError loading config file: ' + rigConfigFilePath);
}

// File not found, i.e. no rig config
return new RigConfig({
projectFolderPath: options.projectFolderPath,
projectFolderPath,

rigFound: true,
filePath: rigConfigFilePath,
rigPackageName: json.rigPackageName,
rigProfile: json.rigProfile || 'default'
rigFound: false,
filePath: '',
rigPackageName: '',
rigProfile: ''
});
}

Expand Down
42 changes: 36 additions & 6 deletions libraries/rig-package/src/test/RigConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,29 @@ describe('RigConfig tests', () => {
}

it('synchronously', () => {
const rigConfig: RigConfig = RigConfig.loadForProjectFolder({ projectFolderPath: testProjectFolder });
const rigConfig: RigConfig = RigConfig.loadForProjectFolder({
projectFolderPath: testProjectFolder,
bypassCache: true
});
validate(rigConfig);

// Should cache result
const rigConfig2: RigConfig = RigConfig.loadForProjectFolder({ projectFolderPath: testProjectFolder });
expect(rigConfig2).toBe(rigConfig);
});

it('asynchronously', async () => {
const rigConfig: RigConfig = await RigConfig.loadForProjectFolderAsync({
projectFolderPath: testProjectFolder
projectFolderPath: testProjectFolder,
bypassCache: true
});
validate(rigConfig);

// Should cache result
const rigConfig2: RigConfig = await RigConfig.loadForProjectFolderAsync({
projectFolderPath: testProjectFolder
});
expect(rigConfig2).toBe(rigConfig);
});
});

Expand All @@ -51,15 +65,29 @@ describe('RigConfig tests', () => {
}

it('synchronously', () => {
const rigConfig: RigConfig = RigConfig.loadForProjectFolder({ projectFolderPath: __dirname });
const rigConfig: RigConfig = RigConfig.loadForProjectFolder({
projectFolderPath: __dirname,
bypassCache: true
});
validate(rigConfig);

// Should cache result
const rigConfig2: RigConfig = RigConfig.loadForProjectFolder({ projectFolderPath: __dirname });
expect(rigConfig2).toBe(rigConfig);
});

it('asynchronously', async () => {
const rigConfig: RigConfig = await RigConfig.loadForProjectFolderAsync({
projectFolderPath: __dirname
projectFolderPath: __dirname,
bypassCache: true
});
validate(rigConfig);

// Should cache result
const rigConfig2: RigConfig = await RigConfig.loadForProjectFolderAsync({
projectFolderPath: __dirname
});
expect(rigConfig2).toBe(rigConfig);
});
});

Expand Down Expand Up @@ -126,7 +154,8 @@ describe('RigConfig tests', () => {
describe(`resolves a config file path`, () => {
it('synchronously', () => {
const rigConfig: RigConfig = RigConfig.loadForProjectFolder({
projectFolderPath: testProjectFolder
projectFolderPath: testProjectFolder,
bypassCache: true
});

expect(rigConfig.rigFound).toBe(true);
Expand All @@ -142,7 +171,8 @@ describe('RigConfig tests', () => {

it('asynchronously', async () => {
const rigConfig: RigConfig = await RigConfig.loadForProjectFolderAsync({
projectFolderPath: testProjectFolder
projectFolderPath: testProjectFolder,
bypassCache: true
});

expect(rigConfig.rigFound).toBe(true);
Expand Down

0 comments on commit 9e2f410

Please sign in to comment.