diff --git a/.github/workflows/update_neuron_compatible.yml b/.github/workflows/update_neuron_compatible.yml new file mode 100644 index 0000000000..c95d5751c7 --- /dev/null +++ b/.github/workflows/update_neuron_compatible.yml @@ -0,0 +1,51 @@ +name: Update Neuron compatibility table + +on: + push: + branches: + - 'rc/**' + paths: + - 'package.json' + +jobs: + update-neuron-compatible: + name: Update Neuron compatibility table + runs-on: ubuntu-latest + permissions: + pull-requests: write # open PR + contents: write # update version files + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + + - name: Update versions + id: update_versions + run: | + npm run update:neuron-compatible + git add compatible.json + + - name: Set GPG + if: ${{ success() }} + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Open PR to RC branch + if: ${{ success() }} + uses: peter-evans/create-pull-request@v5 + with: + title: Update Neuron compatibility table + commit-message: 'feat: Update Neuron compatibility table' + body: 'Update Neuron compatibility table for a new release' + committer: Chen Yu + branch: update-neuron-compatible + base: ${{ github.ref_name }} diff --git a/compatible.csv b/compatible.csv deleted file mode 100644 index f39484153b..0000000000 --- a/compatible.csv +++ /dev/null @@ -1,6 +0,0 @@ -CKB,0.111,0.110,0.109,0.108,0.107,0.106,0.105,0.104,0.103 -Neuron,,,,,,,,, -0.111,yes,yes,yes,no,no,no,no,no,no -0.110,yes,yes,yes,no,no,no,no,no,no -0.106,no,no,no,yes,yes,yes,yes,no,no -0.103,no,no,no,no,no,no,no,yes,yes diff --git a/compatible.json b/compatible.json new file mode 100644 index 0000000000..732472a581 --- /dev/null +++ b/compatible.json @@ -0,0 +1,57 @@ +{ + "fullVersions": [ + "0.111", + "0.110", + "0.109", + "0.108", + "0.107", + "0.106", + "0.105", + "0.104", + "0.103" + ], + "lightVersions": [ + "0.3", + "0.2" + ], + "compatible": { + "0.111": { + "full": [ + "0.111", + "0.110", + "0.109" + ], + "light": [ + "0.3", + "0.2" + ] + }, + "0.110": { + "full": [ + "0.111", + "0.110", + "0.109" + ], + "light": [ + "0.3", + "0.2" + ] + }, + "0.106": { + "full": [ + "0.108", + "0.107", + "0.106", + "0.105" + ], + "light": [] + }, + "0.103": { + "full": [ + "0.104", + "0.103" + ], + "light": [] + } + } +} diff --git a/package.json b/package.json index 90c0bf91d2..143b5c5396 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "postinstall": "husky install", "db:chain": "node ./node_modules/.bin/typeorm", "update:valid-target": "node ./scripts/update-valid-target.js", + "update:neuron-compatible": "node ./scripts/add-neuron-version-in-compatibility-table.js", "update:client-versions": "node ./scripts/update-ckb-client-versions.js" }, "devDependencies": { diff --git a/packages/neuron-wallet/electron-builder.yml b/packages/neuron-wallet/electron-builder.yml index 4b3de23b0f..514c724ef2 100644 --- a/packages/neuron-wallet/electron-builder.yml +++ b/packages/neuron-wallet/electron-builder.yml @@ -13,7 +13,7 @@ afterSign: scripts/notarize.js files: - from: "../.." to: "." - filter: ["!**/*", ".ckb-version", ".ckb-light-version", "ormconfig.json", "compatible.csv"] + filter: ["!**/*", ".ckb-version", ".ckb-light-version", "ormconfig.json", "compatible.json"] - package.json - dist - ".env" diff --git a/packages/neuron-wallet/src/services/node.ts b/packages/neuron-wallet/src/services/node.ts index 42ad0d2387..f59d6e035e 100644 --- a/packages/neuron-wallet/src/services/node.ts +++ b/packages/neuron-wallet/src/services/node.ts @@ -231,23 +231,20 @@ class NodeService { private getNeuronCompatibilityCKB() { const appPath = electronApp.isPackaged ? electronApp.getAppPath() : path.join(__dirname, '../../../..') - const compatiblePath = path.join(appPath, 'compatible.csv') + const compatiblePath = path.join(appPath, 'compatible.json') if (fs.existsSync(compatiblePath)) { try { - const content = fs.readFileSync(compatiblePath, 'utf8')?.split('\n') - const ckbVersions = content?.[0]?.split(',')?.slice(1) - const neuronCompatible = content?.slice(2) - const result: Record> = {} - for (let index = 0; index < neuronCompatible.length; index++) { - const [neuronVersion, ...campatibleValues] = neuronCompatible[index].split(',') - result[neuronVersion] = {} - campatibleValues.forEach((v, idx) => { - result[neuronVersion][ckbVersions[idx]] = v === 'yes' - }) - } - return result + // eslint-disable-next-line @typescript-eslint/no-var-requires + const content = require(compatiblePath) + return (content?.compatible ?? {}) as Record< + string, + { + full: string[] + light: string[] + } + > } catch (err) { - logger.error('App\t: get compatible table failed') + logger.error('App\t: get compatible failed', err) } } } @@ -256,7 +253,7 @@ class NodeService { const compatibilities = this.getNeuronCompatibilityCKB() const neuronCompatibleVersion = neuronVersion.split('.').slice(0, 2).join('.') const externalCKBCompatibleVersion = externalCKBVersion.split('.').slice(0, 2).join('.') - return compatibilities?.[neuronCompatibleVersion]?.[externalCKBCompatibleVersion] + return compatibilities?.[neuronCompatibleVersion]?.full?.includes(externalCKBCompatibleVersion) } private verifyCKbNodeShouldUpdate(neuronCKBVersion: string, externalCKBVersion: string) { diff --git a/packages/neuron-wallet/tests/services/node.test.ts b/packages/neuron-wallet/tests/services/node.test.ts index aebdbf01ba..726423e026 100644 --- a/packages/neuron-wallet/tests/services/node.test.ts +++ b/packages/neuron-wallet/tests/services/node.test.ts @@ -28,6 +28,8 @@ describe('NodeService', () => { const rpcRequestMock = jest.fn() const getChainMock = jest.fn() const getLocalNodeInfoMock = jest.fn() + const pathJoinMock = jest.fn() + const redistCheckMock = jest.fn() const fakeHTTPUrl = 'http://fakeurl' @@ -54,6 +56,8 @@ describe('NodeService', () => { getLocalNodeInfoMock.mockReset() stubbedStartLightNode.mockReset() stubbedStopLightNode.mockReset() + pathJoinMock.mockReset() + redistCheckMock.mockReset() } beforeEach(() => { @@ -173,6 +177,12 @@ describe('NodeService', () => { } }) + jest.doMock('path', () => ({ + join: pathJoinMock, + })) + + jest.doMock('utils/redist-check', () => redistCheckMock) + stubbedRxjsDebounceTime.mockReturnValue((x: any) => x) getChainMock.mockRejectedValue('no chain') }) @@ -345,6 +355,7 @@ describe('NodeService', () => { nodeService.ckb.node.url = bundledNodeUrl }) stubbedStartCKBNode.mockResolvedValue(true) + redistCheckMock.mockResolvedValue(true) stubbedNetworsServiceGet.mockReturnValue({ remote: bundledNodeUrl, readonly: true }) getLocalNodeInfoMock.mockRejectedValue('not start') await nodeService.tryStartNodeOnDefaultURI() @@ -409,10 +420,7 @@ describe('NodeService', () => { nodeService = new NodeService() nodeService.getNeuronCompatibilityCKB = () => ({ '0.110': { - '0.110': true, - '0.109': true, - '0.108': false, - '0.107': false, + full: ['0.110', '0.109'], }, }) stubbedNetworsServiceGet.mockReturnValue({ remote: BUNDLED_CKB_URL, readonly: true }) @@ -456,10 +464,7 @@ describe('NodeService', () => { nodeService = new NodeService() nodeService.getNeuronCompatibilityCKB = () => ({ '0.110': { - '0.110': true, - '0.109': true, - '0.108': false, - '0.107': false, + full: ['0.110', '0.109'], }, }) stubbedNetworsServiceGet.mockReturnValueOnce({ remote: BUNDLED_CKB_URL, readonly: true }) @@ -525,21 +530,32 @@ describe('NodeService', () => { }) it('read file error', () => { existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue(new Error('read failed')) + pathJoinMock.mockReturnValue('./not-exist.json') expect(nodeService.getNeuronCompatibilityCKB()).toBeUndefined() }) it('ckb version content is wrong', async () => { existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue('') + pathJoinMock.mockReturnValue('exist.json') + jest.doMock('exist.json', () => ({}), { virtual: true }) expect(nodeService.getNeuronCompatibilityCKB()).toStrictEqual({}) }) it('success', async () => { existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue('ckb,0.110,0.109\nNeuron,,\n0.109,yes,no') + pathJoinMock.mockReturnValue('success.json') + jest.doMock( + 'success.json', + () => ({ + compatible: { + '0.109': { + full: ['0.108'], + }, + }, + }), + { virtual: true } + ) expect(nodeService.getNeuronCompatibilityCKB()).toStrictEqual({ '0.109': { - '0.110': true, - '0.109': false, + full: ['0.108'], }, }) }) diff --git a/scripts/add-neuron-version-in-compatibility-table.js b/scripts/add-neuron-version-in-compatibility-table.js new file mode 100644 index 0000000000..b2d2772754 --- /dev/null +++ b/scripts/add-neuron-version-in-compatibility-table.js @@ -0,0 +1,27 @@ +const fs = require('node:fs') +const path = require('node:path') + +const exec = () => { + const compatiblePath = path.resolve(__dirname, '../compatible.json') + const info = require(compatiblePath) + const packagePath = path.resolve(__dirname, '../package.json') + const neuronReleaseVersion = require(packagePath).version + const newNeuronVersion = neuronReleaseVersion.slice(0, neuronReleaseVersion.lastIndexOf('.')) + + const lastNeuronVersion = Object.keys(info.compatible).sort((a, b) => { + const [aMajor, aMinor] = a.split('.')?.map(v => +v) ?? [] + const [bMajor, bMinor] = b.split('.')?.map(v => +v) ?? [] + if (aMajor !== bMajor) return bMajor - aMajor + return bMinor - aMinor + })[0] + + if (newNeuronVersion && lastNeuronVersion !== newNeuronVersion) { + info.compatible[newNeuronVersion] = info.compatible[lastNeuronVersion] + fs.writeFileSync(compatiblePath, `${JSON.stringify(info, null, 2)}\r\n`) + } else { + process.exit(1) + } +} + +exec() + diff --git a/scripts/update-ckb-client-versions.js b/scripts/update-ckb-client-versions.js index 323ff3ab64..0071385017 100644 --- a/scripts/update-ckb-client-versions.js +++ b/scripts/update-ckb-client-versions.js @@ -31,17 +31,57 @@ const fetchBuiltinCkbVersion = async () => { } } +const getMajorAndMinorVersion = (full) => { + // v0.3.0 -> 0.3 + return full.slice(1, full.lastIndexOf('.')) +} + +const updateCompatible = ({ + node, + lightClient, +}) => { + const compatiblePath = path.resolve(__dirname, '../compatible.json') + const info = require(compatiblePath) + const lastFullVersion = info.fullVersions[0] + if (node && lastFullVersion !== node) { + info.fullVersions.unshift(node) + Object.values(info.compatible).forEach(v => { + if (v.full.includes(lastFullVersion)) { + v.full.unshift(node) + } + }) + } + const lastLightVersion = info.lightVersions[0] + if (lightClient && lastLightVersion !== lightClient) { + info.lightVersions.unshift(lightClient) + Object.values(info.compatible).forEach(v => { + if (v.light.includes(lastLightVersion)) { + v.light.unshift(lightClient) + } + }) + } + fs.writeFileSync(compatiblePath, `${JSON.stringify(info, null, 2)}\r\n`) +} + const exec = async () => { const latestVersions = await fetchCkbLatestVersion() const builtinVersions = await fetchBuiltinCkbVersion() + const compatibleUpdaterParams = { + node: undefined, + lightClient: undefined, + } if (latestVersions.node !== builtinVersions.node) { fs.writeFileSync(BUILTIN_VERSION_PATH.node, `${latestVersions.node}\n`) + compatibleUpdaterParams.node = getMajorAndMinorVersion(latestVersions.node) } if (latestVersions.lightClient !== builtinVersions.lightClient) { fs.writeFileSync(BUILTIN_VERSION_PATH.lightClient, `${latestVersions.lightClient}\n`) + compatibleUpdaterParams.lightClient = getMajorAndMinorVersion(latestVersions.lightClient) } + + updateCompatible(compatibleUpdaterParams) } exec()