Fix ESLint failures (#1386)

- Updates to ESLint v10 and fixes lint failures.
- Aligns tsconfig on Node 20, which is the current minimum required Node
  version.
- Adds package-lock.json files to source control to avoid future random
  failures when dependencies update.

Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
This commit is contained in:
Mark S. Lewis 2026-02-26 04:20:40 +00:00 committed by GitHub
parent 4f70b668aa
commit 42ff1e629b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
73 changed files with 17109 additions and 4500 deletions

1
.gitignore vendored
View file

@ -13,7 +13,6 @@ vendor/
.idea .idea
# Dependency directories # Dependency directories
node_modules/ node_modules/
package-lock.json
# Ignore Gradle build output directory # Ignore Gradle build output directory
build build
# Ignore Maven build output directory # Ignore Maven build output directory

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -23,11 +23,11 @@
"@hyperledger/fabric-gateway": "^1.10.0" "@hyperledger/fabric-gateway": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.18.6", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4", "typescript": "~5.8",
"typescript-eslint": "^7.13.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,
@ -10,6 +10,6 @@
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": ["./src/**/*"], "include": ["src/"],
"exclude": ["./src/**/*.spec.ts"] "exclude": ["**/*.spec.*"]
} }

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -31,11 +31,11 @@
"sort-keys-recursive": "^2.1.0" "sort-keys-recursive": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.19.33", "@eslint/js": "^10.0.1",
"@eslint/js": "^9.3.0", "@tsconfig/node20": "^20.1.9",
"@tsconfig/node18": "^18.2.4", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -23,11 +23,11 @@
"@hyperledger/fabric-gateway": "^1.10.0" "@hyperledger/fabric-gateway": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.18.6", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4", "typescript": "~5.8",
"typescript-eslint": "^7.13.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,
@ -10,6 +10,6 @@
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": ["./src/**/*"], "include": ["src/"],
"exclude": ["./src/**/*.spec.ts"] "exclude": ["**/*.spec.*"]
} }

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,9 @@
"version": "0.0.1", "version": "0.0.1",
"description": "Hyperledger Fabric Asset Transfer Chaincode written in TypeScript", "description": "Hyperledger Fabric Asset Transfer Chaincode written in TypeScript",
"main": "dist/index.js", "main": "dist/index.js",
"engines": {
"node": ">=20"
},
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"lint": "eslint src", "lint": "eslint src",
@ -17,11 +20,11 @@
"sort-keys-recursive": "^2.1.0" "sort-keys-recursive": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.19.33", "@eslint/js": "^10.0.1",
"@eslint/js": "^9.3.0", "@tsconfig/node20": "^20.1.9",
"@tsconfig/node18": "^18.2.4", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -335,7 +335,7 @@ export class AssetTransferContract extends Contract {
const nanos = historyRecord.timestamp.nanos; const nanos = historyRecord.timestamp.nanos;
const secondsNumber = const secondsNumber =
typeof seconds === 'number' ? seconds : Number(seconds.low); typeof seconds === 'number' ? seconds : seconds.low;
const timestamp = new Date(secondsNumber * 1000 + nanos / 1e6); const timestamp = new Date(secondsNumber * 1000 + nanos / 1e6);
results.push({ results.push({
txId: historyRecord.txId, txId: historyRecord.txId,
@ -355,7 +355,7 @@ export class AssetTransferContract extends Contract {
@Returns('boolean') @Returns('boolean')
public async AssetExists(ctx: Context, assetID: string): Promise<boolean> { public async AssetExists(ctx: Context, assetID: string): Promise<boolean> {
const assetBytes = await ctx.stub.getState(assetID); const assetBytes = await ctx.stub.getState(assetID);
return !!(assetBytes.length > 0); return assetBytes.length > 0;
} }
// InitLedger creates the initial set of assets in the ledger. // InitLedger creates the initial set of assets in the ledger.
@ -436,6 +436,7 @@ export class AssetTransferContract extends Contract {
let record: Asset; let record: Asset;
try { try {
record = JSON.parse(strValue) as Asset; record = JSON.parse(strValue) as Asset;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) { } catch (err) {
record = {} as Asset; record = {} as Asset;
} }

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -23,11 +23,11 @@
"@hyperledger/fabric-gateway": "^1.10.0" "@hyperledger/fabric-gateway": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.18.6", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4", "typescript": "~5.8",
"typescript-eslint": "^7.13.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -31,11 +31,11 @@
"sort-keys-recursive": "^2.1.10" "sort-keys-recursive": "^2.1.10"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.19.33", "@eslint/js": "^10.0.1",
"@eslint/js": "^9.3.0", "@tsconfig/node20": "^20.1.9",
"@tsconfig/node18": "^18.2.4", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -465,9 +465,7 @@ export class AssetTransfer extends Contract {
const b64ID = ctx.clientIdentity.getID(); const b64ID = ctx.clientIdentity.getID();
// base64.StdEncoding.DecodeString(b64ID); // base64.StdEncoding.DecodeString(b64ID);
const decodeID = Buffer.from(b64ID, 'base64').toString('binary'); return Buffer.from(b64ID, 'base64').toString('binary');
return String(decodeID);
} }
// verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id. // verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id.
public verifyClientOrgMatchesPeerOrg(ctx: Context): void { public verifyClientOrgMatchesPeerOrg(ctx: Context): void {

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -25,11 +25,11 @@
"fabric-shim": "~2.5" "fabric-shim": "~2.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.19.33", "@eslint/js": "^10.0.1",
"@eslint/js": "^9.3.0", "@tsconfig/node20": "^20.1.9",
"@tsconfig/node18": "^18.2.4", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

View file

@ -1,4 +1,5 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {

File diff suppressed because it is too large Load diff

View file

@ -23,11 +23,11 @@
"@hyperledger/fabric-gateway": "^1.10.0" "@hyperledger/fabric-gateway": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.18.6", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4", "typescript": "~5.8",
"typescript-eslint": "^7.13.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -5,7 +5,7 @@
*/ */
import { Contract } from '@hyperledger/fabric-gateway'; import { Contract } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util'; import { TextDecoder } from 'util';
import { GREEN, parse, RED, RESET } from './utils'; import { GREEN, RED, RESET } from './utils';
import crypto from 'crypto'; import crypto from 'crypto';
import { mspIdOrg2 } from './connect'; import { mspIdOrg2 } from './connect';
@ -92,16 +92,16 @@ export class ContractWrapper {
const resultBytes = await this.#contract.evaluateTransaction('ReadAsset', assetKey); const resultBytes = await this.#contract.evaluateTransaction('ReadAsset', assetKey);
const result = this.#utf8Decoder.decode(resultBytes); const result = this.#utf8Decoder.decode(resultBytes);
if (result.length !== 0) { if (!result) {
const json = parse<AssetJSON>(result); throw new Error('No Asset Found');
}
const json = JSON.parse(result) as AssetJSON;
if (json.ownerOrg === ownerOrg) { if (json.ownerOrg === ownerOrg) {
console.log(`*** Result from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC: ${json.publicDescription}`); console.log(`*** Result from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC: ${json.publicDescription}`);
} else { } else {
console.log(`${RED}*** Failed owner check from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC:${json.publicDescription}.${RESET}`); console.log(`${RED}*** Failed owner check from ${this.#org} - asset ${json.assetID} owned by ${json.ownerOrg} DESC:${json.publicDescription}.${RESET}`);
} }
} else {
throw new Error('No Asset Found');
}
} }
public async getAssetPrivateProperties(assetKey: string, ownerOrg: string): Promise<void> { public async getAssetPrivateProperties(assetKey: string, ownerOrg: string): Promise<void> {
@ -113,7 +113,7 @@ export class ContractWrapper {
const resultBytes = await this.#contract.evaluateTransaction('GetAssetPrivateProperties', assetKey); const resultBytes = await this.#contract.evaluateTransaction('GetAssetPrivateProperties', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes); const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPropertiesJSON>(resultString); const json = JSON.parse(resultString) as AssetPropertiesJSON;
const result: AssetProperties = { const result: AssetProperties = {
color: json.color, color: json.color,
size: json.size, size: json.size,
@ -167,16 +167,16 @@ export class ContractWrapper {
}); });
const resultString = this.#utf8Decoder.decode(resultBytes); const resultString = this.#utf8Decoder.decode(resultBytes);
if (resultString.length !== 0) { if (!resultString) {
const json = parse<AssetPropertiesJSON>(resultString); throw new Error(`Private information about asset ${assetId} has not been verified by ${this.#org}`);
}
const json = JSON.parse(resultString) as AssetPropertiesJSON;
if (typeof json === 'object') { if (typeof json === 'object') {
console.log(`*** Success VerifyAssetProperties, private information about asset ${assetId} has been verified by ${this.#org}`); console.log(`*** Success VerifyAssetProperties, private information about asset ${assetId} has been verified by ${this.#org}`);
} else { } else {
console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetId} has not been verified by ${this.#org}`); console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetId} has not been verified by ${this.#org}`);
} }
} else {
throw new Error(`Private information about asset ${assetId} has not been verified by ${this.#org}`);
}
} }
public async agreeToBuy(assetPrice: AssetPrice, privateData: AssetPrivateData): Promise<void> { public async agreeToBuy(assetPrice: AssetPrice, privateData: AssetPrivateData): Promise<void> {
@ -217,7 +217,7 @@ export class ContractWrapper {
const resultBytes = await this.#contract.evaluateTransaction('GetAssetSalesPrice', assetKey); const resultBytes = await this.#contract.evaluateTransaction('GetAssetSalesPrice', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes); const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPriceJSON>(resultString); const json = JSON.parse(resultString) as AssetPriceJSON;
const result: AssetPrice = { const result: AssetPrice = {
assetId: json.assetID, assetId: json.assetID,
@ -238,7 +238,7 @@ export class ContractWrapper {
const resultBytes = await this.#contract.evaluateTransaction('GetAssetBidPrice', assetKey); const resultBytes = await this.#contract.evaluateTransaction('GetAssetBidPrice', assetKey);
const resultString = this.#utf8Decoder.decode(resultBytes); const resultString = this.#utf8Decoder.decode(resultBytes);
const json = parse<AssetPriceJSON>(resultString); const json = JSON.parse(resultString) as AssetPriceJSON;
const result: AssetPrice = { const result: AssetPrice = {
assetId: json.assetID, assetId: json.assetID,
price: json.price, price: json.price,

View file

@ -7,7 +7,3 @@
export const RED = '\x1b[31m\n'; export const RED = '\x1b[31m\n';
export const GREEN = '\x1b[32m\n'; export const GREEN = '\x1b[32m\n';
export const RESET = '\x1b[0m'; export const RESET = '\x1b[0m';
export function parse<T>(data: string): T {
return JSON.parse(data) as T;
}

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,

View file

@ -8,7 +8,7 @@ function print() {
echo -e "${GREEN}${1}${NC}" echo -e "${GREEN}${1}${NC}"
} }
dirs=("$(find . -name "*-typescript" -type d -not -path '*/.*')") dirs=("$(find . \( -name '.?*' -o -name 'node_modules' \) -prune -o -type d -name '*-typescript' -print)")
for dir in $dirs; do for dir in $dirs; do
print "Linting $dir" print "Linting $dir"
pushd $dir pushd $dir

View file

@ -8,7 +8,6 @@ coverage
# Dependencies # Dependencies
node_modules/ node_modules/
jspm_packages/ jspm_packages/
package-lock.json
# Compiled TypeScript files # Compiled TypeScript files
dist dist

View file

@ -10,7 +10,7 @@ This project is based on the [trader-typescript](../trader-typescript) sample Ga
## Prerequisites ## Prerequisites
The client application requires Node.js 16 or later. The client application requires Node.js 20 or later.
## Set up ## Set up

View file

@ -0,0 +1,14 @@
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,7 @@
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"build:watch": "tsc -w", "build:watch": "tsc -w",
"lint": "eslint ./src", "lint": "eslint src",
"prepare": "npm run build", "prepare": "npm run build",
"pretest": "npm run lint", "pretest": "npm run lint",
"start": "node ./dist/app", "start": "node ./dist/app",
@ -21,16 +21,16 @@
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.14.0", "@grpc/grpc-js": "^1.14.0",
"@hyperledger/fabric-gateway": "^1.10.0", "@hyperledger/fabric-gateway": "^1.10.0",
"axios": "^1.7.7", "axios": "^1.13.5",
"source-map-support": "^0.5.21" "source-map-support": "^0.5.21"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node18": "^18.2.2", "@eslint/js": "^10.0.1",
"@types/node": "^18.18.6", "@tsconfig/node20": "^20.1.9",
"@types/node": "^20.19.33",
"@types/source-map-support": "^0.5.6", "@types/source-map-support": "^0.5.6",
"@typescript-eslint/eslint-plugin": "^6.9.0", "eslint": "^10.0.2",
"@typescript-eslint/parser": "^6.9.0", "typescript": "~5.8",
"eslint": "^8.52.0", "typescript-eslint": "^8.56.1"
"typescript": "~5.2.2"
} }
} }

View file

@ -13,6 +13,11 @@ import { ExpectedError } from './expectedError';
async function main(): Promise<void> { async function main(): Promise<void> {
const commandName = process.argv[2]; const commandName = process.argv[2];
if (!commandName) {
printUsage();
throw new Error('No command specified');
}
const args = process.argv.slice(3); const args = process.argv.slice(3);
const command = commands[commandName]; const command = commands[commandName];
@ -44,7 +49,7 @@ function printUsage(): void {
console.log(`\t${Object.keys(commands).sort().join('\n\t')}`); console.log(`\t${Object.keys(commands).sort().join('\n\t')}`);
} }
main().catch(error => { main().catch((error: unknown) => {
if (error instanceof ExpectedError) { if (error instanceof ExpectedError) {
console.log(error); console.log(error);
} else { } else {

View file

@ -7,10 +7,14 @@
import { Gateway } from '@hyperledger/fabric-gateway'; import { Gateway } from '@hyperledger/fabric-gateway';
import { CHAINCODE_NAME, CHANNEL_NAME } from '../config'; import { CHAINCODE_NAME, CHANNEL_NAME } from '../config';
import { AssetTransfer } from '../contract'; import { AssetTransfer } from '../contract';
import { assertAllDefined } from '../utils'; import { assertDefined } from '../utils';
const usage = 'Arguments: <assetId> <ownerName> <color>';
export default async function main(gateway: Gateway, args: string[]): Promise<void> { export default async function main(gateway: Gateway, args: string[]): Promise<void> {
const [assetId, owner, color] = assertAllDefined([args[0], args[1], args[2]], 'Arguments: <assetId> <ownerName> <color>'); const assetId = assertDefined(args[0], usage);
const owner = assertDefined(args[1], usage);
const color = assertDefined(args[2], usage);
const network = gateway.getNetwork(CHANNEL_NAME); const network = gateway.getNetwork(CHANNEL_NAME);
const contract = network.getContract(CHAINCODE_NAME); const contract = network.getContract(CHAINCODE_NAME);

View file

@ -7,10 +7,10 @@ import { ChaincodeEvent, checkpointers, Gateway } from '@hyperledger/fabric-gate
import * as path from 'path'; import * as path from 'path';
import { CHAINCODE_NAME, CHANNEL_NAME } from '../config'; import { CHAINCODE_NAME, CHANNEL_NAME } from '../config';
import { Asset } from '../contract'; import { Asset } from '../contract';
import { assertDefined } from '../utils'; import { assertDefined, randomElement } from '../utils';
import { TextDecoder } from 'util'; import { TextDecoder } from 'util';
import axios from 'axios'
const axios = require('axios');
const utf8Decoder = new TextDecoder(); const utf8Decoder = new TextDecoder();
const checkpointFile = path.resolve(process.env.CHECKPOINT_FILE ?? 'checkpoint.json'); const checkpointFile = path.resolve(process.env.CHECKPOINT_FILE ?? 'checkpoint.json');
@ -33,7 +33,7 @@ export default async function main(gateway: Gateway): Promise<void> {
const checkpointer = await checkpointers.file(checkpointFile); const checkpointer = await checkpointers.file(checkpointFile);
console.log(`Connecting to #discord webhook ${webhookURL}`); console.log(`Connecting to #discord webhook ${webhookURL}`);
console.log(`Starting event discording from block ${checkpointer.getBlockNumber() ?? startBlock}`); console.log(`Starting event discording from block ${String(checkpointer.getBlockNumber() ?? startBlock)}`);
console.log('Last processed transaction ID within block:', checkpointer.getTransactionId()); console.log('Last processed transaction ID within block:', checkpointer.getTransactionId());
const events = await network.getChaincodeEvents(CHAINCODE_NAME, { const events = await network.getChaincodeEvents(CHAINCODE_NAME, {
@ -58,7 +58,7 @@ export default async function main(gateway: Gateway): Promise<void> {
} }
// Relay a quick message to the discord webhook to indicate the transaction has been processed. // Relay a quick message to the discord webhook to indicate the transaction has been processed.
async function discord(webhookURL: string, event: ChaincodeEvent): Promise<void> { function discord(webhookURL: string, event: ChaincodeEvent): Promise<void> {
const asset = parseJson(event.payload); const asset = parseJson(event.payload);
console.log(`\n<-- Chaincode event received: ${event.eventName}: `, asset); console.log(`\n<-- Chaincode event received: ${event.eventName}: `, asset);
@ -66,11 +66,11 @@ async function discord(webhookURL: string, event: ChaincodeEvent): Promise<void>
// const message = boringLogMessage(event, asset); // const message = boringLogMessage(event, asset);
const message = splashyShoutMessage(event, asset); const message = splashyShoutMessage(event, asset);
deliverMessage(webhookURL, message); return deliverMessage(webhookURL, message);
} }
// Send an event to a discord webhook. // Send an event to a discord webhook.
async function deliverMessage(webhookURL: string, message: any): Promise<void> { async function deliverMessage(webhookURL: string, message: object): Promise<void> {
console.log('--> Sending to discord webhook: ' + webhookURL); console.log('--> Sending to discord webhook: ' + webhookURL);
console.log(JSON.stringify(message)); console.log(JSON.stringify(message));
@ -82,7 +82,8 @@ async function deliverMessage(webhookURL: string, message: any): Promise<void> {
} }
} }
function boringLogMessage(event: ChaincodeEvent, asset: Asset): any { // eslint-disable-next-line @typescript-eslint/no-unused-vars
function boringLogMessage(event: ChaincodeEvent, asset: Asset): object {
const owner = ownerNickname(asset); const owner = ownerNickname(asset);
const text = format(event, asset, owner); const text = format(event, asset, owner);
@ -93,9 +94,9 @@ function boringLogMessage(event: ChaincodeEvent, asset: Asset): any {
} }
} }
function splashyShoutMessage(event: ChaincodeEvent, asset: Asset): any { function splashyShoutMessage(event: ChaincodeEvent, asset: Asset): object {
const owner:any = JSON.parse(asset.Owner); const owner = JSON.parse(asset.Owner) as Owner;
if (event.eventName == 'CreateAsset') { if (event.eventName == 'CreateAsset') {
return { return {
@ -104,7 +105,7 @@ function splashyShoutMessage(event: ChaincodeEvent, asset: Asset): any {
content: `${bold(owner.user)} has caught a wild ${bold(asset.ID)}!` + getRandomEmoji(), content: `${bold(owner.user)} has caught a wild ${bold(asset.ID)}!` + getRandomEmoji(),
embeds: [ embeds: [
{ {
title: `${owner.org}`, title: owner.org,
image: { image: {
// an actual conga comic (sometimes png and sometimes jpg) // an actual conga comic (sometimes png and sometimes jpg)
// url: `https://congacomic.github.io/assets/img/blockheight-${offset}.png` // url: `https://congacomic.github.io/assets/img/blockheight-${offset}.png`
@ -140,7 +141,7 @@ function format(event: ChaincodeEvent, asset: Asset, owner: string): string {
function parseJson(jsonBytes: Uint8Array): Asset { function parseJson(jsonBytes: Uint8Array): Asset {
const json = utf8Decoder.decode(jsonBytes); const json = utf8Decoder.decode(jsonBytes);
return JSON.parse(json); return JSON.parse(json) as Asset;
} }
function quote(s: string): string { function quote(s: string): string {
@ -160,7 +161,7 @@ function snippet(s: string) {
} }
function ownerNickname(asset: Asset): string { function ownerNickname(asset: Asset): string {
const owner:any = JSON.parse(asset.Owner); const owner = JSON.parse(asset.Owner) as Owner;
return `${owner.org}, ${owner.user}`; return `${owner.org}, ${owner.user}`;
} }
@ -169,7 +170,10 @@ function ownerNickname(asset: Asset): string {
// Simple method that returns a random emoji from list // Simple method that returns a random emoji from list
function getRandomEmoji(): string { function getRandomEmoji(): string {
const emojiList = ['😭','😄','😌','🤓','😎','😤','🤖','😶‍🌫', '🌏','📸','💿','👋','🌊','✨']; const emojiList = ['😭','😄','😌','🤓','😎','😤','🤖','😶‍🌫', '🌏','📸','💿','👋','🌊','✨'];
return emojiList[Math.floor(Math.random() * emojiList.length)]; return randomElement(emojiList)
} }
interface Owner {
user: string;
org: string;
}

View file

@ -16,5 +16,7 @@ export default async function main(gateway: Gateway): Promise<void> {
const assets = await smartContract.getAllAssets(); const assets = await smartContract.getAllAssets();
const assetsJson = JSON.stringify(assets, undefined, 2); const assetsJson = JSON.stringify(assets, undefined, 2);
assetsJson.split('\n').forEach(line => console.log(line)); // Write line-by-line to avoid truncation assetsJson.split('\n').forEach(line => {
console.log(line);
}); // Write line-by-line to avoid truncation
} }

View file

@ -7,10 +7,14 @@
import { Gateway } from '@hyperledger/fabric-gateway'; import { Gateway } from '@hyperledger/fabric-gateway';
import { CHAINCODE_NAME, CHANNEL_NAME } from '../config'; import { CHAINCODE_NAME, CHANNEL_NAME } from '../config';
import { AssetTransfer } from '../contract'; import { AssetTransfer } from '../contract';
import { assertAllDefined } from '../utils'; import { assertDefined } from '../utils';
const usage = 'Arguments: <assetId> <newOwner> <newOwnerOrg>';
export default async function main(gateway: Gateway, args: string[]): Promise<void> { export default async function main(gateway: Gateway, args: string[]): Promise<void> {
const [assetId, newOwner, newOwnerOrg] = assertAllDefined([args[0], args[1], args[2]], 'Arguments: <assetId> <ownerName> <ownerMspId>'); const assetId = assertDefined(args[0], usage);
const newOwner = assertDefined(args[1], usage);
const newOwnerOrg = assertDefined(args[2], usage);
const network = gateway.getNetwork(CHANNEL_NAME); const network = gateway.getNetwork(CHANNEL_NAME);
const contract = network.getContract(CHAINCODE_NAME); const contract = network.getContract(CHAINCODE_NAME);

View file

@ -83,7 +83,7 @@ export class AssetTransfer {
} }
async function submitWithRetry<T>(submit: () => Promise<T>): Promise<T> { async function submitWithRetry<T>(submit: () => Promise<T>): Promise<T> {
let lastError: unknown | undefined; let lastError: unknown;
for (let retryCount = 0; retryCount < RETRIES; retryCount++) { for (let retryCount = 0; retryCount < RETRIES; retryCount++) {
try { try {

View file

@ -13,7 +13,8 @@ const utf8Decoder = new TextDecoder();
* @param values Candidate elements. * @param values Candidate elements.
*/ */
export function randomElement<T>(values: T[]): T { export function randomElement<T>(values: T[]): T {
return values[randomInt(values.length)]; const result = values[randomInt(values.length)];
return assertDefined(result, `Missing element in ${String(values)}`);
} }
/** /**
@ -47,7 +48,7 @@ export async function allFulfilled(promises: Promise<unknown>[]): Promise<void>
if (failures.length > 0) { if (failures.length > 0) {
const failMessages = '- ' + failures.join('\n- '); const failMessages = '- ' + failures.join('\n- ');
throw new Error(`${failures.length} failures:\n${failMessages}\n`); throw new Error(`${String(failures.length)} failures:\n${failMessages}\n`);
} }
} }
@ -61,11 +62,6 @@ export function printable<T extends object>(event: T): PrintView<T> {
) as PrintView<T>; ) as PrintView<T>;
} }
export function assertAllDefined<T>(values: (T | undefined)[], message: string | (() => string)): T[] {
values.forEach(value => assertDefined(value, message));
return values as T[];
}
export function assertDefined<T>(value: T | undefined, message: string | (() => string)): T { export function assertDefined<T>(value: T | undefined, message: string | (() => string)): T {
if (value == undefined) { if (value == undefined) {
throw new Error(typeof message === 'string' ? message : message()); throw new Error(typeof message === 'string' ? message : message());

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
@ -8,12 +8,13 @@
"outDir": "dist", "outDir": "dist",
"rootDir": "src", "rootDir": "src",
"noUnusedLocals": false, "noUnusedLocals": false,
"noImplicitReturns": true "noImplicitReturns": true,
"noUncheckedIndexedAccess": true
}, },
"include": [ "include": [
"src/" "src/"
], ],
"exclude": [ "exclude": [
"src/**/*.spec.ts" "**/*.spec.*"
] ]
} }

View file

@ -8,7 +8,6 @@ coverage
# Dependencies # Dependencies
node_modules/ node_modules/
jspm_packages/ jspm_packages/
package-lock.json
# Compiled TypeScript files # Compiled TypeScript files
dist dist

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -23,11 +23,11 @@
"@hyperledger/fabric-gateway": "^1.10.0" "@hyperledger/fabric-gateway": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.4", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.19.33", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -48,7 +48,7 @@ function getSimulatedFailureCount(): number {
const value = process.env.SIMULATED_FAILURE_COUNT ?? '0'; const value = process.env.SIMULATED_FAILURE_COUNT ?? '0';
const count = Math.floor(Number(value)); const count = Math.floor(Number(value));
if (isNaN(count) || count < 0) { if (isNaN(count) || count < 0) {
throw new Error(`Invalid SIMULATED_FAILURE_COUNT value: ${String(value)}`); throw new Error(`Invalid SIMULATED_FAILURE_COUNT value: ${value}`);
} }
return count; return count;

View file

@ -14,7 +14,7 @@ const utf8Decoder = new TextDecoder();
*/ */
export function randomElement<T>(values: T[]): T { export function randomElement<T>(values: T[]): T {
const result = values[randomInt(values.length)]; const result = values[randomInt(values.length)];
return assertDefined(result, `Missing element in {String(values)}`); return assertDefined(result, `Missing element in ${String(values)}`);
} }
/** /**

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

View file

@ -31,14 +31,6 @@
"json-stringify-deterministic": "^1.0.7", "json-stringify-deterministic": "^1.0.7",
"sort-keys-recursive": "^2.1.7" "sort-keys-recursive": "^2.1.7"
}, },
"devDependencies": {
"@types/node": "^18.19.33",
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.4",
"eslint": "^8.57.0",
"typescript": "~5.4.5",
"typescript-eslint": "^7.11.0"
},
"nyc": { "nyc": {
"extension": [ "extension": [
".ts", ".ts",
@ -58,5 +50,13 @@
"branches": 100, "branches": 100,
"functions": 100, "functions": 100,
"lines": 100 "lines": 100
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tsconfig/node20": "^20.1.9",
"@types/node": "^20.19.33",
"eslint": "^10.0.2",
"typescript": "~5.8",
"typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

View file

@ -1,3 +1,2 @@
dist/ dist/
node_modules/ node_modules/
package-lock.json

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -21,13 +21,12 @@
"@hyperledger/fabric-gateway": "^1.10.0" "@hyperledger/fabric-gateway": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.4", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.19.33", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"npm-run-all": "^4.1.5",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,

View file

@ -1,7 +1,8 @@
import js from '@eslint/js'; import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, { export default defineConfig(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: { languageOptions: {
ecmaVersion: 2023, ecmaVersion: 2023,
sourceType: 'module', sourceType: 'module',

File diff suppressed because it is too large Load diff

View file

@ -23,11 +23,11 @@
"@hyperledger/fabric-protos": "^0.3.4" "@hyperledger/fabric-protos": "^0.3.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^10.0.1",
"@tsconfig/node18": "^18.2.4", "@tsconfig/node20": "^20.1.9",
"@types/node": "^18.19.33", "@types/node": "^20.19.33",
"eslint": "^8.57.0", "eslint": "^10.0.2",
"typescript": "~5.4.5", "typescript": "~5.8",
"typescript-eslint": "^7.11.0" "typescript-eslint": "^8.56.1"
} }
} }

View file

@ -240,7 +240,7 @@ function getSimulatedFailureCount(): number {
const value = process.env.SIMULATED_FAILURE_COUNT ?? '0'; const value = process.env.SIMULATED_FAILURE_COUNT ?? '0';
const count = Math.floor(Number(value)); const count = Math.floor(Number(value));
if (isNaN(count) || count < 0) { if (isNaN(count) || count < 0) {
throw new Error(`Invalid SIMULATED_FAILURE_COUNT value: ${String(value)}`); throw new Error(`Invalid SIMULATED_FAILURE_COUNT value: ${value}`);
} }
return count; return count;

View file

@ -1,5 +1,5 @@
{ {
"extends": "@tsconfig/node18/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,
@ -10,6 +10,6 @@
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": ["./src/**/*"], "include": ["src/"],
"exclude": ["./src/**/*.spec.ts"] "exclude": ["**/*.spec.*"]
} }