Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/yellow-cows-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/shared-ini-file-loader": minor
---

export readFile from shared-ini-file-loader
6 changes: 3 additions & 3 deletions packages/shared-ini-file-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@
"types": "./dist-types/index.d.ts",
"browser": {
"./dist-es/getSSOTokenFromFile": false,
"./dist-es/slurpFile": false
"./dist-es/readFile": false
},
"react-native": {
"./dist-cjs/getSSOTokenFromFile": false,
"./dist-cjs/slurpFile": false,
"./dist-cjs/readFile": false,
"./dist-es/getSSOTokenFromFile": false,
"./dist-es/slurpFile": false
"./dist-es/readFile": false
},
"engines": {
"node": ">=18.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { describe, expect, test as it } from "vitest";

import { externalDataInterceptor } from "./externalDataInterceptor";
import { getSSOTokenFromFile } from "./getSSOTokenFromFile";
import { slurpFile } from "./slurpFile";
import { readFile } from "./readFile";

describe("fileMockController", () => {
it("intercepts slurpFile", async () => {
it("intercepts readFile", async () => {
externalDataInterceptor.interceptFile("abcd", "contents");

expect(await slurpFile("abcd")).toEqual("contents");
expect(await readFile("abcd")).toEqual("contents");
expect(externalDataInterceptor.getFileRecord()).toEqual({
abcd: Promise.resolve("contents"),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { tokenIntercept } from "./getSSOTokenFromFile";
import { fileIntercept } from "./slurpFile";
import { fileIntercept } from "./readFile";

/**
* @internal
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// ToDo: Change to "fs/promises" when supporting nodejs>=14
import { promises } from "fs";
import * as promises from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";

import { getSSOTokenFilepath } from "./getSSOTokenFilepath";
import { getSSOTokenFromFile } from "./getSSOTokenFromFile";

vi.mock("fs", () => ({ promises: { readFile: vi.fn() } }));
vi.mock("node:fs/promises", () => ({ readFile: vi.fn() }));
vi.mock("./getSSOTokenFilepath");

describe(getSSOTokenFromFile.name, () => {
Expand Down
7 changes: 3 additions & 4 deletions packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// ToDo: Change to "fs/promises" when supporting nodejs>=14
import { promises as fsPromises } from "fs";
import { readFile } from "fs/promises";

import { getSSOTokenFilepath } from "./getSSOTokenFilepath";

const { readFile } = fsPromises;

/**
* Cached SSO token retrieved from SSO login flow.
* @public
Expand Down Expand Up @@ -54,7 +51,9 @@ export interface SSOToken {
}

/**
* For testing only.
* @internal
* @deprecated minimize use in application code.
*/
export const tokenIntercept = {} as Record<string, any>;

Expand Down
1 change: 1 addition & 0 deletions packages/shared-ini-file-loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./loadSsoSessionData";
export * from "./parseKnownFiles";
export { externalDataInterceptor } from "./externalDataInterceptor";
export * from "./types";
export { type ReadFileOptions, readFile } from "./readFile";
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { getCredentialsFilepath } from "./getCredentialsFilepath";
import { getHomeDir } from "./getHomeDir";
import { loadSharedConfigFiles } from "./loadSharedConfigFiles";
import { parseIni } from "./parseIni";
import { slurpFile } from "./slurpFile";
import { readFile } from "./readFile";

vi.mock("./getConfigData");
vi.mock("./getConfigFilepath");
vi.mock("./getCredentialsFilepath");
vi.mock("./parseIni");
vi.mock("./slurpFile");
vi.mock("./readFile");
vi.mock("./getHomeDir");

describe("loadSharedConfigFiles", () => {
Expand All @@ -29,7 +29,7 @@ describe("loadSharedConfigFiles", () => {
vi.mocked(getCredentialsFilepath).mockReturnValue(mockCredsFilepath);
vi.mocked(parseIni).mockImplementation((args: any) => args);
vi.mocked(getConfigData).mockImplementation((args) => args);
vi.mocked(slurpFile).mockImplementation((path) => Promise.resolve(path));
vi.mocked(readFile).mockImplementation((path) => Promise.resolve(path));
vi.mocked(getHomeDir).mockReturnValue(mockHomeDir);
});

Expand Down Expand Up @@ -71,7 +71,7 @@ describe("loadSharedConfigFiles", () => {

describe("swallows error and returns empty configuration", () => {
it("when readFile throws error", async () => {
vi.mocked(slurpFile).mockRejectedValue("error");
vi.mocked(readFile).mockRejectedValue("error");
const sharedConfigFiles = await loadSharedConfigFiles();
expect(sharedConfigFiles).toStrictEqual({ configFile: {}, credentialsFile: {} });
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getConfigFilepath } from "./getConfigFilepath";
import { getCredentialsFilepath } from "./getCredentialsFilepath";
import { getHomeDir } from "./getHomeDir";
import { parseIni } from "./parseIni";
import { slurpFile } from "./slurpFile";
import { readFile } from "./readFile";

/**
* @public
Expand Down Expand Up @@ -62,13 +62,13 @@ export const loadSharedConfigFiles = async (init: SharedConfigInit = {}): Promis
}

const parsedFiles = await Promise.all([
slurpFile(resolvedConfigFilepath, {
readFile(resolvedConfigFilepath, {
ignoreCache: init.ignoreCache,
})
.then(parseIni)
.then(getConfigData)
.catch(swallowError),
slurpFile(resolvedFilepath, {
readFile(resolvedFilepath, {
ignoreCache: init.ignoreCache,
})
.then(parseIni)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { getConfigFilepath } from "./getConfigFilepath";
import { getSsoSessionData } from "./getSsoSessionData";
import { loadSsoSessionData } from "./loadSsoSessionData";
import { parseIni } from "./parseIni";
import { slurpFile } from "./slurpFile";
import { readFile } from "./readFile";

vi.mock("./getConfigFilepath");
vi.mock("./getSsoSessionData");
vi.mock("./parseIni");
vi.mock("./slurpFile");
vi.mock("./readFile");

describe(loadSsoSessionData.name, () => {
const mockConfigFilepath = "/mock/file/path/config";
Expand All @@ -19,7 +19,7 @@ describe(loadSsoSessionData.name, () => {
vi.mocked(getConfigFilepath).mockReturnValue(mockConfigFilepath);
vi.mocked(parseIni).mockImplementation((args: any) => args);
vi.mocked(getSsoSessionData).mockReturnValue(mockSsoSessionData);
vi.mocked(slurpFile).mockImplementation((path) => Promise.resolve(path));
vi.mocked(readFile).mockImplementation((path) => Promise.resolve(path));
});

afterEach(() => {
Expand All @@ -42,7 +42,7 @@ describe(loadSsoSessionData.name, () => {

describe("swallows error and returns empty configuration", () => {
it("when readFile throws error", async () => {
vi.mocked(slurpFile).mockRejectedValue("error");
vi.mocked(readFile).mockRejectedValue("error");
const ssoSessionData = await loadSsoSessionData();
expect(ssoSessionData).toStrictEqual({});
});
Expand Down
4 changes: 2 additions & 2 deletions packages/shared-ini-file-loader/src/loadSsoSessionData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ParsedIniData } from "@smithy/types";
import { getConfigFilepath } from "./getConfigFilepath";
import { getSsoSessionData } from "./getSsoSessionData";
import { parseIni } from "./parseIni";
import { slurpFile } from "./slurpFile";
import { readFile } from "./readFile";

/**
* Subset of {@link SharedConfigInit}.
Expand All @@ -24,7 +24,7 @@ const swallowError = () => ({});
* @internal
*/
export const loadSsoSessionData = async (init: SsoSessionInit = {}): Promise<ParsedIniData> =>
slurpFile(init.configFilepath ?? getConfigFilepath())
readFile(init.configFilepath ?? getConfigFilepath())
.then(parseIni)
.then(getSsoSessionData)
.catch(swallowError);
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// ToDo: Change to "fs/promises" when supporting nodejs>=14
import { promises } from "fs";
import * as promises from "node:fs/promises";
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";

vi.mock("fs", () => ({ promises: { readFile: vi.fn() } }));
vi.mock("node:fs/promises", () => ({ readFile: vi.fn() }));

describe("slurpFile", () => {
describe("readFile", () => {
const UTF8 = "utf8";
const getMockFileContents = (path: string, options = UTF8) => JSON.stringify({ path, options });

Expand All @@ -20,44 +19,44 @@ describe("slurpFile", () => {
vi.resetModules();
});

describe("makes one readFile call for a filepath irrespective of slurpFile calls", () => {
describe("makes one fs.readFile call for a filepath irrespective of smithy.readFile calls", () => {
it.each([10, 100, 1000, 10000])("parallel calls: %d ", async (num: number) => {
const { slurpFile } = await import("./slurpFile");
const { readFile } = await import("./readFile");
const mockPath = "/mock/path";
const mockPathContent = getMockFileContents(mockPath);

expect(promises.readFile).not.toHaveBeenCalled();
const fileContentArr = await Promise.all(Array(num).fill(slurpFile(mockPath)));
const fileContentArr = await Promise.all(Array(num).fill(readFile(mockPath)));
expect(fileContentArr).toStrictEqual(Array(num).fill(mockPathContent));

// There is one readFile call even through slurpFile is called in parallel num times.
// There is one fs.readFile call even through smithy.readFile is called in parallel num times.
expect(promises.readFile).toHaveBeenCalledTimes(1);
expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8);
});

it("two parallel calls and one sequential call", async () => {
const { slurpFile } = await import("./slurpFile");
const { readFile } = await import("./readFile");
const mockPath = "/mock/path";
const mockPathContent = getMockFileContents(mockPath);

expect(promises.readFile).not.toHaveBeenCalled();
const fileContentArr = await Promise.all([slurpFile(mockPath), slurpFile(mockPath)]);
const fileContentArr = await Promise.all([readFile(mockPath), readFile(mockPath)]);
expect(fileContentArr).toStrictEqual([mockPathContent, mockPathContent]);

// There is one readFile call even through slurpFile is called in parallel twice.
// There is one fs.readFile call even though smithy.readFile is called in parallel twice.
expect(promises.readFile).toHaveBeenCalledTimes(1);
expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8);

const fileContent = await slurpFile(mockPath);
const fileContent = await readFile(mockPath);
expect(fileContent).toStrictEqual(mockPathContent);

// There is one readFile call even through slurpFile is called for the third time.
// There is one fs.readFile call even though smithy.readFile is called for the third time.
expect(promises.readFile).toHaveBeenCalledTimes(1);
});
});

it("makes multiple readFile calls with based on filepaths", async () => {
const { slurpFile } = await import("./slurpFile");
const { readFile } = await import("./readFile");

const mockPath1 = "/mock/path/1";
const mockPathContent1 = getMockFileContents(mockPath1);
Expand All @@ -66,45 +65,45 @@ describe("slurpFile", () => {
const mockPathContent2 = getMockFileContents(mockPath2);

expect(promises.readFile).not.toHaveBeenCalled();
const fileContentArr = await Promise.all([slurpFile(mockPath1), slurpFile(mockPath2)]);
const fileContentArr = await Promise.all([readFile(mockPath1), readFile(mockPath2)]);
expect(fileContentArr).toStrictEqual([mockPathContent1, mockPathContent2]);

// There are two readFile calls as slurpFile is called in parallel with different filepaths.
// There are two fs.readFile calls as smithy.readFile is called in parallel with different file paths.
expect(promises.readFile).toHaveBeenCalledTimes(2);
expect(promises.readFile).toHaveBeenNthCalledWith(1, mockPath1, UTF8);
expect(promises.readFile).toHaveBeenNthCalledWith(2, mockPath2, UTF8);

const fileContent1 = await slurpFile(mockPath1);
const fileContent1 = await readFile(mockPath1);
expect(fileContent1).toStrictEqual(mockPathContent1);
const fileContent2 = await slurpFile(mockPath2);
const fileContent2 = await readFile(mockPath2);
expect(fileContent2).toStrictEqual(mockPathContent2);

// There is one readFile call even through slurpFile is called for the third time.
// There is one fs.readFile call even though smithy.readFile is called for the third time.
expect(promises.readFile).toHaveBeenCalledTimes(2);
});

it("makes multiple readFile calls when called with ignoreCache option", async () => {
const { slurpFile } = await import("./slurpFile");
const { readFile } = await import("./readFile");

const mockPath1 = "/mock/path/1";
const mockPathContent1 = getMockFileContents(mockPath1);

expect(promises.readFile).not.toHaveBeenCalled();
const fileContentArr = await Promise.all([
slurpFile(mockPath1, { ignoreCache: true }),
slurpFile(mockPath1, { ignoreCache: true }),
readFile(mockPath1, { ignoreCache: true }),
readFile(mockPath1, { ignoreCache: true }),
]);
expect(fileContentArr).toStrictEqual([mockPathContent1, mockPathContent1]);

// There are two readFile calls as slurpFile is called in parallel with the same filepath.
// There are two fs.readFile calls as smithy.readFile is called in parallel with the same filepath.
expect(promises.readFile).toHaveBeenCalledTimes(2);
expect(promises.readFile).toHaveBeenNthCalledWith(1, mockPath1, UTF8);
expect(promises.readFile).toHaveBeenNthCalledWith(2, mockPath1, UTF8);

const fileContent1 = await slurpFile(mockPath1);
const fileContent1 = await readFile(mockPath1);
expect(fileContent1).toStrictEqual(mockPathContent1);

// There is no readFile call since slurpFile is now called without refresh.
// There is no fs.readFile call since smithy.readFile is now called without refresh.
expect(promises.readFile).toHaveBeenCalledTimes(2);
});
});
34 changes: 34 additions & 0 deletions packages/shared-ini-file-loader/src/readFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { readFile as fsReadFile } from "node:fs/promises";

/**
* Runtime file cache.
* @internal
*/
export const filePromises: Record<string, Promise<string>> = {};

/**
* For testing only.
* @internal
* @deprecated minimize use in application code.
*/
export const fileIntercept: Record<string, Promise<string>> = {};

/**
* @internal
*/
export interface ReadFileOptions {
ignoreCache?: boolean;
}

/**
* @internal
*/
export const readFile = (path: string, options?: ReadFileOptions) => {
if (fileIntercept[path] !== undefined) {
return fileIntercept[path];
}
if (!filePromises[path] || options?.ignoreCache) {
filePromises[path] = fsReadFile(path, "utf8");
}
return filePromises[path];
};
21 changes: 0 additions & 21 deletions packages/shared-ini-file-loader/src/slurpFile.ts

This file was deleted.