Initial Commit
Some checks failed
Publish to Private NPM Registry / publish (push) Failing after 1m0s
Some checks failed
Publish to Private NPM Registry / publish (push) Failing after 1m0s
This commit is contained in:
commit
18cc482293
12 changed files with 4353 additions and 0 deletions
109
.forgejo/workflows/publish.yml
Normal file
109
.forgejo/workflows/publish.yml
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
name: Publish to Private NPM Registry
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: default
|
||||
|
||||
steps:
|
||||
# Checkout the repository
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Set up NPM Auth Token
|
||||
- name: Set up NPM Auth Token
|
||||
run: echo "NODE_AUTH_TOKEN=${{ secrets.NPM_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
# Set up Node.js
|
||||
- name: Set up Node.js version
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
|
||||
#
|
||||
# > Version Spec of the version to use in SemVer notation.
|
||||
# > It also admits such aliases as lts/*, latest, nightly and canary builds
|
||||
# > Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node
|
||||
node-version: '20.x'
|
||||
|
||||
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
|
||||
#
|
||||
# > Optional registry to set up for auth. Will set the registry in a project level .npmrc and .yarnrc file,
|
||||
# > and set up auth to read in from env.NODE_AUTH_TOKEN.
|
||||
# > Default: ''
|
||||
registry-url: 'https://npm.pkg.bridgemanaccessible.ca'
|
||||
|
||||
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
|
||||
#
|
||||
# > Optional scope for authenticating against scoped registries.
|
||||
# > Will fall back to the repository owner when using the GitHub Packages registry (https://npm.pkg.github.com/).
|
||||
scope: '@BridgemanAccessible'
|
||||
|
||||
# Transpile/Build the package (TypeScript -> JavaScript)
|
||||
- name: Transpile/Build the package (TypeScript -> JavaScript)
|
||||
run: |
|
||||
# Because Yarn is used locally better to install and use it than have to debug weird inconsistencies
|
||||
npm install --global yarn
|
||||
|
||||
# Install needed dependencies
|
||||
yarn install
|
||||
|
||||
# Run tests to ensure the package is working as expected before publishing
|
||||
yarn test
|
||||
|
||||
# Build the package
|
||||
yarn build
|
||||
|
||||
- name: Determine Version and Increment (if needed)
|
||||
id: version_check
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Version: $VERSION"
|
||||
|
||||
NAME=$(node -p "require('./package.json').name")
|
||||
LATEST_VERSION=$(npm show $NAME version --registry https://npm.pkg.bridgemanaccessible.ca)
|
||||
echo "Latest version: $LATEST_VERSION"
|
||||
|
||||
if [ "$LATEST_VERSION" != "$VERSION" ]; then
|
||||
echo "Manually updated version detected: $VERSION"
|
||||
else
|
||||
NEW_VERSION=$(npm version patch --no-git-tag-version)
|
||||
echo "New version: $NEW_VERSION"
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_ENV
|
||||
echo "version_changed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit Version Change (if needed)
|
||||
if: steps.version_check.outputs.version_changed == 'true'
|
||||
run: |
|
||||
# Update remote URL to use the GITHUB_TOKEN for authentication
|
||||
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.bridgemanaccessible.ca/${{ github.repository }}.git
|
||||
|
||||
# Setup git user details for committing the version change
|
||||
git config user.name "Forgejo Actions"
|
||||
git config user.email "actions@git.bridgemanaccessible.ca"
|
||||
|
||||
# Commit the version change to the `package.json` file
|
||||
git add package.json
|
||||
git commit -m "[Forgejo Actions] Update version to ${{ env.new_version }}"
|
||||
|
||||
# Push the changes to the repository
|
||||
git push origin HEAD:main
|
||||
|
||||
# Publish to private NPM registry
|
||||
- name: Publish the package
|
||||
run: |
|
||||
# Copy over the files to the build output (`dist`) folder
|
||||
cp package.json dist/package.json
|
||||
cp README.md dist/README.md
|
||||
cp LICENSE dist/LICENSE
|
||||
|
||||
# Change directory to the build output (`dist`) folder
|
||||
cd dist
|
||||
|
||||
# Publish the package to the private NPM registry
|
||||
npm publish --registry http://npm.pkg.bridgemanaccessible.ca/
|
||||
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Node modules
|
||||
node_modules
|
||||
|
||||
# Yarn stuff
|
||||
.yarn/
|
||||
.yarnrc.yml
|
||||
|
||||
# Credentials/Secrets
|
||||
.npmrc
|
||||
|
||||
# Build output
|
||||
dist
|
||||
|
||||
# Automation Scripts
|
||||
unpublish-publish.ps1
|
||||
unpublish-publish.sh
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Bridgeman Accessible
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2
README.md
Normal file
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# File based Implementation of bridgeman Accessible Auth Keystore
|
||||
This is the file based implementation of the Bridgeman Accessible Auth Keystore.
|
||||
36
jest.config.cjs
Normal file
36
jest.config.cjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest/presets/default-esm',
|
||||
testEnvironment: 'node',
|
||||
|
||||
// Tell Jest where to look for source code and tests
|
||||
roots: ['<rootDir>/src', '<rootDir>/tests'],
|
||||
|
||||
// Explicitly match files in the tests folder
|
||||
testMatch: [
|
||||
'<rootDir>/tests/**/*.test.ts',
|
||||
'<rootDir>/tests/**/*.spec.ts'
|
||||
],
|
||||
|
||||
// Tell Jest to treat .ts files as ESM modules
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
|
||||
// Fixes the "Cannot find module" error
|
||||
moduleNameMapper: {
|
||||
// If the import starts with . or .. and ends with .js, strip the .js
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
|
||||
// Map the library to mock file
|
||||
// This tells Jest to load simple JS file instead of parsing the complex node_module
|
||||
//'^@BridgemanAccessible/ba-auth$': '<rootDir>/tests/__mocks__/ba-auth.js'
|
||||
},
|
||||
|
||||
// Configure the transformer to enable ESM support
|
||||
transform: {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
36
package.json
Normal file
36
package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@BridgemanAccessible/ba-auth_keystore_file",
|
||||
"version": "1.0.0",
|
||||
"description": "A file based Keystore implementation for use in combination with the Bridgeman Accessible Auth Package",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"repository": "https://git.bridgemanaccessible.ca/Bridgeman-Accessible/ba-auth_keystore_file.git",
|
||||
"author": "Bridgeman Accessible<info@bridgemanaccessible.ca>",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js",
|
||||
"types": "./index.d.ts"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.2.3",
|
||||
"@types/node-jose": "^1.1.13",
|
||||
"cross-env": "^10.1.0",
|
||||
"jest": "^30.2.0",
|
||||
"ts-jest": "^29.4.6",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@BridgemanAccessible/ba-auth": "^1.0.30",
|
||||
"@BridgemanAccessible/ba-logging": "^1.0.1",
|
||||
"node-jose": "^2.2.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
||||
89
src/FileKeystore.ts
Normal file
89
src/FileKeystore.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import NodeJose from 'node-jose';
|
||||
import type { JWK as JWKTypes } from 'node-jose';
|
||||
|
||||
import { BaseKeystore, Keystore } from '@BridgemanAccessible/ba-auth/keystore';
|
||||
|
||||
const { JWK } = NodeJose;
|
||||
|
||||
/**
|
||||
* File implementation for the keystore
|
||||
*/
|
||||
export class FileKeystore extends BaseKeystore {
|
||||
private keystoreFilePath: string | undefined;
|
||||
|
||||
/**
|
||||
* Create a new keystore from a file
|
||||
*
|
||||
* @param keystoreFile The name of the keystore file
|
||||
* @param certDir The directory that contains the keystore file
|
||||
*/
|
||||
private constructor() {
|
||||
// Call the super constructor
|
||||
super();
|
||||
}
|
||||
|
||||
/** Get the file path of the keystore */
|
||||
getKeystoreFilePath(): string {
|
||||
if(typeof this.keystoreFilePath === 'undefined') {
|
||||
throw new Error('Keystore file path not set');
|
||||
}
|
||||
|
||||
return this.keystoreFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup/Load the keystore
|
||||
*
|
||||
* If the keystore file doesn't exist, it will be created (including generating an initial signing and encryption key pair)
|
||||
* If the keystore file does exist, it will be loaded
|
||||
*
|
||||
* @param keystoreFile The name of the keystore file
|
||||
* @param certDir The directory to save the keystore file in
|
||||
* @returns The keystore object
|
||||
*/
|
||||
private async setupKeystore(keystoreFile: string, certDir: string): Promise<JWKTypes.KeyStore> {
|
||||
// Combine the directory and the file into a singular file path
|
||||
this.keystoreFilePath = path.join(certDir, keystoreFile);
|
||||
|
||||
// Create a variable to hold the keystore
|
||||
let keystore: JWKTypes.KeyStore;
|
||||
|
||||
// Check if the keystore file already exists
|
||||
if (!fs.existsSync(this.keystoreFilePath)) {
|
||||
// Because the keystore file doesn't exist, check if the cert directory exists
|
||||
if (!fs.existsSync(certDir)) {
|
||||
// Because the directory doesn't exist, create it
|
||||
fs.mkdirSync(certDir)
|
||||
}
|
||||
|
||||
// Create a new keystore
|
||||
keystore = JWK.createKeyStore();
|
||||
|
||||
// Generate two new RSA key pairs (one for signing the other foe encryption)
|
||||
const signingKey = await keystore.generate('RSA', 2048, { alg: 'RS256', use: 'sig' });
|
||||
|
||||
//const encryptionKey = await keystore.generate('RSA', 2048, { enc: 'A128CBC-HS256', use: 'enc' });
|
||||
|
||||
const encryptionKey = await keystore.generate('RSA', 2048, { /*alg: 'RS-OAEP', enc: 'A128CBC-HS256',*/ use: 'enc' });
|
||||
|
||||
// Save the keystore to a file
|
||||
fs.writeFileSync(this.keystoreFilePath, JSON.stringify(keystore.toJSON(true), null, 4));
|
||||
}
|
||||
else {
|
||||
// Load the keystore from the file
|
||||
keystore = await JWK.asKeyStore(JSON.parse(fs.readFileSync(this.keystoreFilePath, 'utf-8')));
|
||||
}
|
||||
|
||||
return keystore;
|
||||
}
|
||||
|
||||
static async create(keystoreFile: string = 'keystore.json', certDir: string = '.cert') {
|
||||
const keystore = new FileKeystore();
|
||||
keystore.keystore = await keystore.setupKeystore(keystoreFile, certDir);
|
||||
return keystore;
|
||||
}
|
||||
}
|
||||
|
||||
Keystore.addKeystoreType('file', FileKeystore.create);
|
||||
3
src/index.ts
Normal file
3
src/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { FileKeystore } from './FileKeystore.js';
|
||||
|
||||
export { FileKeystore };
|
||||
147
tests/FileKeystore.test.ts
Normal file
147
tests/FileKeystore.test.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import path from 'path';
|
||||
import NodeJose from 'node-jose';
|
||||
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
||||
import type { FileKeystore } from '../src/FileKeystore.js'; // Import TYPE only (safe in ESM)
|
||||
|
||||
// We use unstable_mockModule because standard jest.mock struggles to intercept
|
||||
// core Node modules (like fs) in strict ESM mode.
|
||||
jest.unstable_mockModule('fs', () => ({
|
||||
// We must mock the 'default' export because your code uses: import fs from 'fs'
|
||||
default: {
|
||||
existsSync: jest.fn(),
|
||||
mkdirSync: jest.fn(),
|
||||
writeFileSync: jest.fn(),
|
||||
readFileSync: jest.fn(),
|
||||
},
|
||||
// We also mock named exports purely for safety, though 'default' is the key one here
|
||||
existsSync: jest.fn(),
|
||||
mkdirSync: jest.fn(),
|
||||
writeFileSync: jest.fn(),
|
||||
readFileSync: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock dependency
|
||||
jest.unstable_mockModule('@BridgemanAccessible/ba-auth/keystore', () => ({
|
||||
BaseKeystore: class {},
|
||||
Keystore: {
|
||||
addKeystoreType: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('FileKeystore', () => {
|
||||
// Variables to hold our dynamically imported modules
|
||||
let FileKeystoreClass: typeof FileKeystore;
|
||||
let mockFs: any;
|
||||
|
||||
const TEST_DIR = '.test-cert';
|
||||
const TEST_FILE = 'test-keystore.json';
|
||||
const FULL_PATH = path.join(TEST_DIR, TEST_FILE);
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// 3. DYNAMICALLY IMPORT THE MODULES
|
||||
// This is the magic. We import AFTER the mock is defined.
|
||||
// This ensures 'fs' inside FileKeystore.ts is actually our mock.
|
||||
const fsModule = await import('fs');
|
||||
mockFs = fsModule.default; // Access the default export we mocked above
|
||||
|
||||
// Import your class under test
|
||||
// Note: You might need to add '.js' to the path if your resolving is strict
|
||||
const module = await import('../src/FileKeystore.js');
|
||||
FileKeystoreClass = module.FileKeystore;
|
||||
});
|
||||
|
||||
test('should create a NEW keystore if the file does not exist', async () => {
|
||||
// SETUP:
|
||||
// 1. Pretend the file does NOT exist
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
// 2. Mock writeFileSync to do nothing (just verify it was called)
|
||||
mockFs.writeFileSync.mockImplementation(() => {});
|
||||
// 3. Mock mkdirSync
|
||||
mockFs.mkdirSync.mockImplementation(() => undefined);
|
||||
|
||||
// EXECUTE
|
||||
const keystoreInstance = await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
|
||||
|
||||
// VERIFY
|
||||
// 1. Verify it checked for file existence
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith(FULL_PATH);
|
||||
|
||||
// 2. Verify it checked for directory existence (and created it)
|
||||
expect(mockFs.mkdirSync).toHaveBeenCalledWith(TEST_DIR);
|
||||
|
||||
// 3. Verify it wrote the new keystore to disk
|
||||
// We expect the first argument to be the path, and the second to be a JSON string
|
||||
expect(mockFs.writeFileSync).toHaveBeenCalledWith(
|
||||
FULL_PATH,
|
||||
expect.stringContaining('"keys":') // Simple check to ensure it looks like JSON
|
||||
);
|
||||
|
||||
// 4. Verify the instance path is set correctly
|
||||
expect(keystoreInstance.getKeystoreFilePath()).toBe(FULL_PATH);
|
||||
|
||||
// 5. Verify internal keystore state (node-jose should have generated keys)
|
||||
// @ts-ignore - accessing public property 'keystore' which might be protected in your real usage,
|
||||
// but implied public in the provided snippet logic
|
||||
const keys = keystoreInstance.keystore.all();
|
||||
expect(keys.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('should LOAD an existing keystore if the file exists', async () => {
|
||||
// SETUP:
|
||||
// 1. Create a real temporary keystore using node-jose to mimic a file on disk
|
||||
const realKeystore = await NodeJose.JWK.createKeyStore().generate('RSA', 2048);
|
||||
const keystoreJson = JSON.stringify(realKeystore.keystore.toJSON(true));
|
||||
|
||||
// 2. Pretend the file DOES exist
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
|
||||
// 3. Pretend readFileSync returns our JSON string
|
||||
mockFs.readFileSync.mockReturnValue(keystoreJson);
|
||||
|
||||
// EXECUTE
|
||||
const keystoreInstance = await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
|
||||
|
||||
// VERIFY
|
||||
// 1. It should check existence
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith(FULL_PATH);
|
||||
|
||||
// 2. It should READ the file
|
||||
expect(mockFs.readFileSync).toHaveBeenCalledWith(FULL_PATH, 'utf-8');
|
||||
|
||||
// 3. CRITICAL: It should NOT overwrite the file
|
||||
expect(mockFs.writeFileSync).not.toHaveBeenCalled();
|
||||
|
||||
// 4. CRITICAL: It should NOT try to create the directory again
|
||||
// (The code checks !existsSync(file), so it goes into the else block)
|
||||
expect(mockFs.mkdirSync).not.toHaveBeenCalled();
|
||||
|
||||
// 5. Verify the loaded keys match what we "mocked" on disk
|
||||
// @ts-ignore
|
||||
const loadedKeys = keystoreInstance.keystore.all();
|
||||
expect(loadedKeys.length).toBe(1);
|
||||
});
|
||||
|
||||
test('should create directory only if it is missing', async () => {
|
||||
// Scenario: File missing, Directory also missing
|
||||
mockFs.existsSync
|
||||
.mockReturnValueOnce(false) // First check: File exists? No.
|
||||
.mockReturnValueOnce(false); // Second check: Dir exists? No.
|
||||
|
||||
await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
|
||||
|
||||
expect(mockFs.mkdirSync).toHaveBeenCalledWith(TEST_DIR);
|
||||
});
|
||||
|
||||
test('should NOT create directory if it already exists', async () => {
|
||||
// Scenario: File missing, Directory exists
|
||||
mockFs.existsSync
|
||||
.mockReturnValueOnce(false) // First check: File exists? No.
|
||||
.mockReturnValueOnce(true); // Second check: Dir exists? Yes.
|
||||
|
||||
await FileKeystoreClass.create(TEST_FILE, TEST_DIR);
|
||||
|
||||
expect(mockFs.mkdirSync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
17
tsconfig.build.json
Normal file
17
tsconfig.build.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
// Visit https://aka.ms/tsconfig to read more about this file
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"types": [],
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"tests"
|
||||
],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx"
|
||||
]
|
||||
}
|
||||
50
tsconfig.json
Normal file
50
tsconfig.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
// Visit https://aka.ms/tsconfig to read more about this file
|
||||
"compilerOptions": {
|
||||
// File Layout
|
||||
// "rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
|
||||
// Environment Settings
|
||||
// See also https://aka.ms/tsconfig/module
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"types": ["node", "jest"],
|
||||
// For nodejs:
|
||||
// "lib": ["esnext"],
|
||||
// "types": ["node"],
|
||||
// and npm install -D @types/node
|
||||
|
||||
// Other Outputs
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
// Stricter Typechecking Options
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
|
||||
// Style Options
|
||||
// "noImplicitReturns": true,
|
||||
// "noImplicitOverride": true,
|
||||
// "noUnusedLocals": true,
|
||||
// "noUnusedParameters": true,
|
||||
// "noFallthroughCasesInSwitch": true,
|
||||
// "noPropertyAccessFromIndexSignature": true,
|
||||
|
||||
// Recommended Options
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"moduleDetection": "force",
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx",
|
||||
"./tests/**/*.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue