From b09c9155d44519ae668325d3c2c01957b438e920 Mon Sep 17 00:00:00 2001 From: iteye Date: Fri, 16 Jan 2026 18:24:23 +0800 Subject: [PATCH 01/15] add test --- src/cli/wallet/utils.ts | 8 ++ test/test.mjs | 226 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 test/test.mjs diff --git a/src/cli/wallet/utils.ts b/src/cli/wallet/utils.ts index 7240540..3fe5324 100644 --- a/src/cli/wallet/utils.ts +++ b/src/cli/wallet/utils.ts @@ -1,6 +1,14 @@ import readlineSync from 'readline-sync'; export function promptPassword(message: string): string { + if (process.env.GOE_TEST_MODE === '1') { + const pwd = process.env.GOE_TEST_PASSWORD; + if (!pwd) { + throw new Error('GOE_TEST_PASSWORD not set'); + } + return pwd; + } + return readlineSync.question(message, { hideEchoBack: true, mask: '*' diff --git a/test/test.mjs b/test/test.mjs new file mode 100644 index 0000000..43ebd57 --- /dev/null +++ b/test/test.mjs @@ -0,0 +1,226 @@ +// test/goeE2ETest.mjs +import 'dotenv/config'; +import path from 'path'; +import fs from 'fs'; +import { exec, spawn } from 'child_process'; +import { fileURLToPath } from 'url'; + +const DIST_CLI = path.resolve('./dist/cli/index.js'); +const CHAIN_ID = 11155111; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = path.resolve(__dirname, '../test'); +const TEMP_DIR = path.join(PROJECT_ROOT, '/.tmp'); +const CLONE_DIR = path.join(TEMP_DIR, 'clone'); + +const PRIVATE_KEY = process.env.GOE_TEST_PK; +const PASSWORD = process.env.GOE_TEST_PASSWORD; + +if (!PRIVATE_KEY || !PASSWORD) { + console.error('Please set GOE_TEST_PK and GOE_TEST_PASSWORD in .env'); + process.exit(1); +} + + +/* -------- exec: capture stdout -------- */ +export const testCommandExec = (command) => { + console.log(`\n[exec] ${command}`); + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + console.error(error.message); + return reject(error); + } + if (stderr) { + console.error(stderr.trim()); + } + if (stdout) { + console.log(stdout.trim()); + } + resolve(stdout || ''); + }); + }); +}; + +/* -------- spawn: realtime -------- */ +export const testCommandSpawn = (command, args, options = {}) => { + return new Promise((resolve, reject) => { + console.log(`\n[spawn] ${command} ${args.join(' ')}`); + + const p = spawn(command, args, { + stdio: 'inherit', + shell: true, + ...options + }); + + p.on('close', (code) => { + if (code !== 0) { + return reject(new Error(`Process exited with code ${code}`)); + } + resolve(true); + }); + }); +}; + + +/* ---------------- utils ---------------- */ + +const randomRepoName = () => + `goe-e2e-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; + +const stripAnsi = (s) => + s.replace(/\x1B\[[0-9;]*m/g, ''); + + +/* ------------------- Wallet ------------------- */ +async function checkWalletExists() { + const out = await testCommandExec(`node ${DIST_CLI} wallet list`); + if (out.includes('No wallets found')) { + throw new Error('No wallet exists. Create and fund one first.'); + } +} + +async function unlockWallet() { + console.log('Unlocking wallet using GOE_TEST_PASSWORD...'); + await testCommandSpawn( + 'node', + [DIST_CLI, 'wallet', 'unlock'], + { + env: { + ...process.env, + GOE_TEST_MODE: '1', + GOE_TEST_PASSWORD: PASSWORD + } + } + ); +} + +const lockWallet = () => + testCommandExec( + `node ${DIST_CLI} wallet lock` + ); + +/* ------------------- Repo ------------------- */ +async function createRepo() { + const name = randomRepoName(); + const out = await testCommandExec( + `node ${DIST_CLI} repo create ${name} --chain-id ${CHAIN_ID}` + ); + const match = stripAnsi(out).match(/0x[a-fA-F0-9]{40}/); + if (!match) { + throw new Error('Repo address not found'); + } + + return { name, address: match[0] }; +} + +const listRepos = () => + testCommandExec(`node ${DIST_CLI} repo list --chain-id ${CHAIN_ID}`); + +const listBranches = (addr) => + testCommandExec( + `node ${DIST_CLI} repo branches ${addr} --chain-id ${CHAIN_ID}` + ); + +const setDefaultBranch = (addr) => + testCommandSpawn( + 'node', + [DIST_CLI, 'repo', 'default-branch', addr, 'main', '--chain-id', CHAIN_ID] + ); + +/* ------------------- Git flow ------------------- */ +async function gitFlow(repoAddress) { + const repoPath = path.join(TEMP_DIR, 'repo'); + fs.mkdirSync(repoPath, { recursive: true }); + process.chdir(repoPath); + + await testCommandSpawn('git', ['init']); + await testCommandSpawn('git', [ + 'remote', + 'add', + 'origin', + `goe://${repoAddress}:${CHAIN_ID}` + ]); + await testCommandSpawn('git', ['checkout', '-b', 'main']); + + fs.writeFileSync('README.md', '# GoE E2E\n'); + await testCommandSpawn('git', ['add', '.']); + await testCommandSpawn('git', ['commit', '-m', 'init']); + await testCommandSpawn('git', ['push', 'origin', 'main']); + + await testCommandSpawn('git', ['checkout', '-b', 'feature']); + fs.writeFileSync('feature.txt', 'feature\n'); + await testCommandSpawn('git', ['add', '.']); + await testCommandSpawn('git', ['commit', '-m', 'feature']); + await testCommandSpawn('git', ['push', 'origin', 'feature']); + + fs.writeFileSync('feature.txt', 'force\n'); + await testCommandSpawn('git', ['add', '.']); + await testCommandSpawn('git', ['commit', '-m', `force-update`]); + await testCommandSpawn('git', ['push', '--force', 'origin', 'feature']); + + await testCommandSpawn('git', ['push', 'origin', '--delete', 'feature']); +} + +async function gitCloneVerify(repoAddress) { + process.chdir(TEMP_DIR); + + await testCommandSpawn('git', [ + 'clone', + `goe://${repoAddress}:${CHAIN_ID}`, + CLONE_DIR + ]); + + const readme = fs.readFileSync( + path.join(CLONE_DIR, 'README.md'), + 'utf8' + ); + if (!readme.includes('GoE E2E')) { + throw new Error('Clone verification failed'); + } + + process.chdir(CLONE_DIR); + const branches = await testCommandExec('git branch -r'); + if (branches.includes('feature')) { + throw new Error('Deleted branch still exists'); + } +} + +function cleanup() { + process.chdir(PROJECT_ROOT); + fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + console.log('Local test data cleaned'); +} +/* ------------------- main ------------------- */ +async function npmLink() { + console.log('\nLinking local goe-cli...'); + await testCommandSpawn('npm', ['link']); +} + +(async () => { + fs.mkdirSync(TEMP_DIR, { recursive: true }); + + // goe + await npmLink(); + await checkWalletExists(); + await unlockWallet(); + const {name, address} = await createRepo(); + await listRepos(); + + // git helper + try { + await gitFlow(address); + await gitCloneVerify(address); + } finally { + cleanup(); + } + + await listBranches(address); + await setDefaultBranch(address); + + await lockWallet(); + console.log('\n=== GoE E2E test finished successfully ==='); +})().catch((e) => { + console.error(e.message); + process.exit(1); +}); From 5f2d1bc26594dd3494e8a256aee66b35b42be442 Mon Sep 17 00:00:00 2001 From: iteye Date: Mon, 19 Jan 2026 15:02:00 +0800 Subject: [PATCH 02/15] nff-1 --- test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 | 1 + test/.tmp/nff/nff.txt | 1 + 2 files changed, 2 insertions(+) create mode 160000 test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 create mode 100644 test/.tmp/nff/nff.txt diff --git a/test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 b/test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 new file mode 160000 index 0000000..ca56d06 --- /dev/null +++ b/test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 @@ -0,0 +1 @@ +Subproject commit ca56d06b503268463478f980eb93b1efebb83b35 diff --git a/test/.tmp/nff/nff.txt b/test/.tmp/nff/nff.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/test/.tmp/nff/nff.txt @@ -0,0 +1 @@ +1 \ No newline at end of file From 26e238d4ebe1b175a5163289e4bde91d4318e07c Mon Sep 17 00:00:00 2001 From: iteye Date: Mon, 19 Jan 2026 15:11:33 +0800 Subject: [PATCH 03/15] add other test --- test/test.mjs | 96 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/test/test.mjs b/test/test.mjs index 43ebd57..e9de830 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -23,7 +23,7 @@ if (!PRIVATE_KEY || !PASSWORD) { /* -------- exec: capture stdout -------- */ -export const testCommandExec = (command) => { +function testCommandExec(command) { console.log(`\n[exec] ${command}`); return new Promise((resolve, reject) => { exec(command, (error, stdout, stderr) => { @@ -40,10 +40,10 @@ export const testCommandExec = (command) => { resolve(stdout || ''); }); }); -}; +} /* -------- spawn: realtime -------- */ -export const testCommandSpawn = (command, args, options = {}) => { +function testCommandSpawn(command, args, options = {}) { return new Promise((resolve, reject) => { console.log(`\n[spawn] ${command} ${args.join(' ')}`); @@ -60,7 +60,16 @@ export const testCommandSpawn = (command, args, options = {}) => { resolve(true); }); }); -}; +} + +async function expectFailure(promise, reason) { + try { + await promise; + throw new Error(`Expected failure but succeeded: ${reason}`); + } catch (err) { + console.log(`[expected failure] ${reason}`); + } +} /* ---------------- utils ---------------- */ @@ -141,6 +150,7 @@ async function gitFlow(repoAddress) { 'origin', `goe://${repoAddress}:${CHAIN_ID}` ]); + await testCommandSpawn('git', ['checkout', '-b', 'main']); fs.writeFileSync('README.md', '# GoE E2E\n'); @@ -148,23 +158,25 @@ async function gitFlow(repoAddress) { await testCommandSpawn('git', ['commit', '-m', 'init']); await testCommandSpawn('git', ['push', 'origin', 'main']); + // feature branch await testCommandSpawn('git', ['checkout', '-b', 'feature']); fs.writeFileSync('feature.txt', 'feature\n'); await testCommandSpawn('git', ['add', '.']); await testCommandSpawn('git', ['commit', '-m', 'feature']); await testCommandSpawn('git', ['push', 'origin', 'feature']); + // force push fs.writeFileSync('feature.txt', 'force\n'); await testCommandSpawn('git', ['add', '.']); - await testCommandSpawn('git', ['commit', '-m', `force-update`]); + await testCommandSpawn('git', ['commit', '-m', 'force-update']); await testCommandSpawn('git', ['push', '--force', 'origin', 'feature']); + // delete branch await testCommandSpawn('git', ['push', 'origin', '--delete', 'feature']); } async function gitCloneVerify(repoAddress) { process.chdir(TEMP_DIR); - await testCommandSpawn('git', [ 'clone', `goe://${repoAddress}:${CHAIN_ID}`, @@ -180,17 +192,85 @@ async function gitCloneVerify(repoAddress) { } process.chdir(CLONE_DIR); + // check branch + const head = await testCommandExec('git symbolic-ref HEAD'); + if (!head.includes('refs/heads/main')) { + throw new Error('HEAD is not refs/heads/main'); + } const branches = await testCommandExec('git branch -r'); if (branches.includes('feature')) { throw new Error('Deleted branch still exists'); } } +async function gitRejectNonFastForward(repoAddress) { + const repoPath = path.join(TEMP_DIR, 'nff'); + fs.mkdirSync(repoPath, { recursive: true }); + process.chdir(repoPath); + + await testCommandSpawn('git', [ + 'clone', + `goe://${repoAddress}:${CHAIN_ID}` + ]); + + // push + fs.writeFileSync('nff.txt', '1'); + await testCommandSpawn('git', ['add', '.']); + await testCommandSpawn('git', ['commit', '-m', 'nff-1']); + await testCommandSpawn('git', ['push', 'origin', 'main']); + + // reset + await testCommandSpawn('git', ['reset', '--hard', 'HEAD~1']); + + // not fast-forward push,fail + await expectFailure( + testCommandSpawn('git', ['push', 'origin', 'main']), + 'non-fast-forward push' + ); +} + +async function gitMergePush(repoAddress) { + const repoPath = path.join(TEMP_DIR, 'merge'); + fs.mkdirSync(repoPath, { recursive: true }); + process.chdir(repoPath); + + await testCommandSpawn('git', [ + 'clone', + `goe://${repoAddress}:${CHAIN_ID}` + ]); + + await testCommandSpawn('git', ['checkout', '-b', 'merge-a']); + fs.writeFileSync('a.txt', 'a'); + await testCommandSpawn('git', ['commit', '-am', 'a']); + + await testCommandSpawn('git', ['checkout', 'main']); + await testCommandSpawn('git', ['merge', 'merge-a']); + + await testCommandSpawn('git', ['push', 'origin', 'main']); +} + +export async function gitFetchUpdate(repoAddress) { + process.chdir(path.join(TEMP_DIR, 'repo')); + fs.writeFileSync('fetch.txt', 'x'); + await testCommandSpawn('git', ['commit', '-am', 'fetch-update']); + await testCommandSpawn('git', ['push', 'origin', 'main']); + + process.chdir(path.join(TEMP_DIR, CLONE_DIR)); + await testCommandSpawn('git', ['fetch', 'origin']); + + const log = await testCommandExec('git log origin/main --oneline -1'); + if (!log.includes('fetch-update')) { + throw new Error('Fetch did not update origin/main'); + } +} + + function cleanup() { process.chdir(PROJECT_ROOT); fs.rmSync(TEMP_DIR, { recursive: true, force: true }); console.log('Local test data cleaned'); } + /* ------------------- main ------------------- */ async function npmLink() { console.log('\nLinking local goe-cli...'); @@ -211,13 +291,15 @@ async function npmLink() { try { await gitFlow(address); await gitCloneVerify(address); + await gitRejectNonFastForward(address); + await gitMergePush(address); + await gitFetchUpdate(address); } finally { cleanup(); } await listBranches(address); await setDefaultBranch(address); - await lockWallet(); console.log('\n=== GoE E2E test finished successfully ==='); })().catch((e) => { From 475c938b4a218312c059396f596ff9a232c52137 Mon Sep 17 00:00:00 2001 From: iteye Date: Mon, 19 Jan 2026 15:14:27 +0800 Subject: [PATCH 04/15] remove test file --- test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 | 1 - test/.tmp/nff/nff.txt | 1 - 2 files changed, 2 deletions(-) delete mode 160000 test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 delete mode 100644 test/.tmp/nff/nff.txt diff --git a/test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 b/test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 deleted file mode 160000 index ca56d06..0000000 --- a/test/.tmp/nff/0x63a1c27D0e5D394765A46070621b57e3DDA4e176 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ca56d06b503268463478f980eb93b1efebb83b35 diff --git a/test/.tmp/nff/nff.txt b/test/.tmp/nff/nff.txt deleted file mode 100644 index 56a6051..0000000 --- a/test/.tmp/nff/nff.txt +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file From 29ef1bbeafa8353ec0559aff2c6369dd9906f139 Mon Sep 17 00:00:00 2001 From: iteye Date: Mon, 19 Jan 2026 16:09:12 +0800 Subject: [PATCH 05/15] fix error --- src/helper/core/goe.ts | 7 ++- test/test.mjs | 111 +++++++++++++++++------------------------ 2 files changed, 53 insertions(+), 65 deletions(-) diff --git a/src/helper/core/goe.ts b/src/helper/core/goe.ts index 49b969f..45c40c2 100644 --- a/src/helper/core/goe.ts +++ b/src/helper/core/goe.ts @@ -127,7 +127,12 @@ class Goe { internalResult = `error ${dst} refusing to push to non-branch ref`; } else if (!force) { // fast-forward push - internalResult = await this.handlePush(src, dst, gasIncPct) + if (src === "") { + // delete + internalResult = await this.handleBranchDeletion(dst, this.defaultBranch); + } else { + internalResult = await this.handlePush(src, dst, gasIncPct) + } } else { // force push or delete if (src === "") { diff --git a/test/test.mjs b/test/test.mjs index e9de830..7c1cdb5 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -62,16 +62,6 @@ function testCommandSpawn(command, args, options = {}) { }); } -async function expectFailure(promise, reason) { - try { - await promise; - throw new Error(`Expected failure but succeeded: ${reason}`); - } catch (err) { - console.log(`[expected failure] ${reason}`); - } -} - - /* ---------------- utils ---------------- */ const randomRepoName = () => @@ -139,20 +129,16 @@ const setDefaultBranch = (addr) => /* ------------------- Git flow ------------------- */ async function gitFlow(repoAddress) { - const repoPath = path.join(TEMP_DIR, 'repo'); + const repoPath = path.join(TEMP_DIR, 'flow'); fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); + // init + remote await testCommandSpawn('git', ['init']); - await testCommandSpawn('git', [ - 'remote', - 'add', - 'origin', - `goe://${repoAddress}:${CHAIN_ID}` - ]); - + await testCommandSpawn('git', ['remote', 'add', 'origin', `goe://${repoAddress}:${CHAIN_ID}`]); await testCommandSpawn('git', ['checkout', '-b', 'main']); + // init commit fs.writeFileSync('README.md', '# GoE E2E\n'); await testCommandSpawn('git', ['add', '.']); await testCommandSpawn('git', ['commit', '-m', 'init']); @@ -176,31 +162,21 @@ async function gitFlow(repoAddress) { } async function gitCloneVerify(repoAddress) { + const clonePath = path.join(TEMP_DIR, 'clone'); + fs.mkdirSync(clonePath, { recursive: true }); process.chdir(TEMP_DIR); - await testCommandSpawn('git', [ - 'clone', - `goe://${repoAddress}:${CHAIN_ID}`, - CLONE_DIR - ]); - - const readme = fs.readFileSync( - path.join(CLONE_DIR, 'README.md'), - 'utf8' - ); - if (!readme.includes('GoE E2E')) { - throw new Error('Clone verification failed'); - } - process.chdir(CLONE_DIR); - // check branch + await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, clonePath]); + process.chdir(clonePath); + + const readme = fs.readFileSync(path.join(clonePath, 'README.md'), 'utf8'); + if (!readme.includes('GoE E2E')) throw new Error('Clone verification failed'); + const head = await testCommandExec('git symbolic-ref HEAD'); - if (!head.includes('refs/heads/main')) { - throw new Error('HEAD is not refs/heads/main'); - } + if (!head.includes('refs/heads/main')) throw new Error('HEAD is not refs/heads/main'); + const branches = await testCommandExec('git branch -r'); - if (branches.includes('feature')) { - throw new Error('Deleted branch still exists'); - } + if (branches.includes('feature')) throw new Error('Deleted branch still exists'); } async function gitRejectNonFastForward(repoAddress) { @@ -208,25 +184,24 @@ async function gitRejectNonFastForward(repoAddress) { fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', [ - 'clone', - `goe://${repoAddress}:${CHAIN_ID}` - ]); + await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, '.']); - // push + // push new file fs.writeFileSync('nff.txt', '1'); await testCommandSpawn('git', ['add', '.']); await testCommandSpawn('git', ['commit', '-m', 'nff-1']); await testCommandSpawn('git', ['push', 'origin', 'main']); - // reset + // reset head await testCommandSpawn('git', ['reset', '--hard', 'HEAD~1']); - // not fast-forward push,fail - await expectFailure( - testCommandSpawn('git', ['push', 'origin', 'main']), - 'non-fast-forward push' - ); + // non-fast-forward push should fail + try { + await testCommandSpawn('git', ['push', 'origin', 'main']); + throw new Error('Expected non-fast-forward push to fail'); + } catch { + console.log('[expected] non-fast-forward rejected'); + } } async function gitMergePush(repoAddress) { @@ -234,34 +209,42 @@ async function gitMergePush(repoAddress) { fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', [ - 'clone', - `goe://${repoAddress}:${CHAIN_ID}` - ]); + await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, '.']); + // create a branch and commit await testCommandSpawn('git', ['checkout', '-b', 'merge-a']); fs.writeFileSync('a.txt', 'a'); - await testCommandSpawn('git', ['commit', '-am', 'a']); + await testCommandSpawn('git', ['add', 'a.txt']); + await testCommandSpawn('git', ['commit', '-m', 'a']); + // merge back to main await testCommandSpawn('git', ['checkout', 'main']); await testCommandSpawn('git', ['merge', 'merge-a']); await testCommandSpawn('git', ['push', 'origin', 'main']); } -export async function gitFetchUpdate(repoAddress) { - process.chdir(path.join(TEMP_DIR, 'repo')); +async function gitFetchUpdate(repoAddress) { + const repoPath = path.join(TEMP_DIR, 'fetch'); + fs.mkdirSync(repoPath, { recursive: true }); + process.chdir(repoPath); + + await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, '.']); + fs.writeFileSync('fetch.txt', 'x'); - await testCommandSpawn('git', ['commit', '-am', 'fetch-update']); + await testCommandSpawn('git', ['add', 'fetch.txt']); + await testCommandSpawn('git', ['commit', '-m', 'fetch-update']); await testCommandSpawn('git', ['push', 'origin', 'main']); - process.chdir(path.join(TEMP_DIR, CLONE_DIR)); - await testCommandSpawn('git', ['fetch', 'origin']); + // simulate fetch from another clone + const clonePath = path.join(TEMP_DIR, 'fetch-clone'); + fs.mkdirSync(clonePath, { recursive: true }); + await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, clonePath]); + process.chdir(clonePath); - const log = await testCommandExec('git log origin/main --oneline -1'); - if (!log.includes('fetch-update')) { - throw new Error('Fetch did not update origin/main'); - } + await testCommandSpawn('git', ['fetch', 'origin']); + const success = await testCommandSpawn('git', ['log', 'origin/main', '--oneline', '-1'], { capture: true }); + if (!success) throw new Error('Fetch did not update origin/main'); } From a2528932880420d66385ea19c6e288cf1c8f7f0d Mon Sep 17 00:00:00 2001 From: iteye Date: Mon, 19 Jan 2026 16:39:38 +0800 Subject: [PATCH 06/15] support repo name --- test/test.mjs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/test.mjs b/test/test.mjs index 7c1cdb5..3aee51f 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -11,7 +11,6 @@ const CHAIN_ID = 11155111; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = path.resolve(__dirname, '../test'); const TEMP_DIR = path.join(PROJECT_ROOT, '/.tmp'); -const CLONE_DIR = path.join(TEMP_DIR, 'clone'); const PRIVATE_KEY = process.env.GOE_TEST_PK; const PASSWORD = process.env.GOE_TEST_PASSWORD; @@ -128,14 +127,14 @@ const setDefaultBranch = (addr) => ); /* ------------------- Git flow ------------------- */ -async function gitFlow(repoAddress) { +async function gitFlow(goe) { const repoPath = path.join(TEMP_DIR, 'flow'); fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); // init + remote await testCommandSpawn('git', ['init']); - await testCommandSpawn('git', ['remote', 'add', 'origin', `goe://${repoAddress}:${CHAIN_ID}`]); + await testCommandSpawn('git', ['remote', 'add', 'origin', goe]); await testCommandSpawn('git', ['checkout', '-b', 'main']); // init commit @@ -161,12 +160,12 @@ async function gitFlow(repoAddress) { await testCommandSpawn('git', ['push', 'origin', '--delete', 'feature']); } -async function gitCloneVerify(repoAddress) { +async function gitCloneVerify(goe) { const clonePath = path.join(TEMP_DIR, 'clone'); fs.mkdirSync(clonePath, { recursive: true }); process.chdir(TEMP_DIR); - await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, clonePath]); + await testCommandSpawn('git', ['clone', goe, clonePath]); process.chdir(clonePath); const readme = fs.readFileSync(path.join(clonePath, 'README.md'), 'utf8'); @@ -179,12 +178,12 @@ async function gitCloneVerify(repoAddress) { if (branches.includes('feature')) throw new Error('Deleted branch still exists'); } -async function gitRejectNonFastForward(repoAddress) { +async function gitRejectNonFastForward(goe) { const repoPath = path.join(TEMP_DIR, 'nff'); fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, '.']); + await testCommandSpawn('git', ['clone', goe, '.']); // push new file fs.writeFileSync('nff.txt', '1'); @@ -204,12 +203,12 @@ async function gitRejectNonFastForward(repoAddress) { } } -async function gitMergePush(repoAddress) { +async function gitMergePush(goe) { const repoPath = path.join(TEMP_DIR, 'merge'); fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, '.']); + await testCommandSpawn('git', ['clone', goe, '.']); // create a branch and commit await testCommandSpawn('git', ['checkout', '-b', 'merge-a']); @@ -224,12 +223,12 @@ async function gitMergePush(repoAddress) { await testCommandSpawn('git', ['push', 'origin', 'main']); } -async function gitFetchUpdate(repoAddress) { +async function gitFetchUpdate(goe) { const repoPath = path.join(TEMP_DIR, 'fetch'); fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, '.']); + await testCommandSpawn('git', ['clone', goe, '.']); fs.writeFileSync('fetch.txt', 'x'); await testCommandSpawn('git', ['add', 'fetch.txt']); @@ -239,7 +238,7 @@ async function gitFetchUpdate(repoAddress) { // simulate fetch from another clone const clonePath = path.join(TEMP_DIR, 'fetch-clone'); fs.mkdirSync(clonePath, { recursive: true }); - await testCommandSpawn('git', ['clone', `goe://${repoAddress}:${CHAIN_ID}`, clonePath]); + await testCommandSpawn('git', ['clone', goe, clonePath]); process.chdir(clonePath); await testCommandSpawn('git', ['fetch', 'origin']); @@ -271,12 +270,13 @@ async function npmLink() { await listRepos(); // git helper + const goe = `goe://${name}:${CHAIN_ID}` try { - await gitFlow(address); - await gitCloneVerify(address); - await gitRejectNonFastForward(address); - await gitMergePush(address); - await gitFetchUpdate(address); + await gitFlow(goe); + await gitCloneVerify(goe); + await gitRejectNonFastForward(goe); + await gitMergePush(goe); + await gitFetchUpdate(goe); } finally { cleanup(); } From f9f590499ef6f4270d892705ac725ed636d6bacf Mon Sep 17 00:00:00 2001 From: iteye Date: Tue, 20 Jan 2026 14:10:56 +0800 Subject: [PATCH 07/15] fix comment --- test/test.mjs | 165 ++++++++++++++++++++++++-------------------------- 1 file changed, 79 insertions(+), 86 deletions(-) diff --git a/test/test.mjs b/test/test.mjs index 3aee51f..c81017f 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -2,11 +2,11 @@ import 'dotenv/config'; import path from 'path'; import fs from 'fs'; -import { exec, spawn } from 'child_process'; +import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; const DIST_CLI = path.resolve('./dist/cli/index.js'); -const CHAIN_ID = 11155111; +const CHAIN_ID = '11155111'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = path.resolve(__dirname, '../test'); @@ -22,41 +22,40 @@ if (!PRIVATE_KEY || !PASSWORD) { /* -------- exec: capture stdout -------- */ -function testCommandExec(command) { - console.log(`\n[exec] ${command}`); +export function runCommand(cmd, args = [], options = {}) { + const { + capture = false, + env = {}, + cwd, + } = options; + return new Promise((resolve, reject) => { - exec(command, (error, stdout, stderr) => { - if (error) { - console.error(error.message); - return reject(error); - } - if (stderr) { - console.error(stderr.trim()); - } - if (stdout) { - console.log(stdout.trim()); - } - resolve(stdout || ''); + const child = spawn(cmd, args, { + shell: false, // ⭐ + stdio: capture ? 'pipe' : 'inherit', + env: { ...process.env, ...env }, + cwd, }); - }); -} -/* -------- spawn: realtime -------- */ -function testCommandSpawn(command, args, options = {}) { - return new Promise((resolve, reject) => { - console.log(`\n[spawn] ${command} ${args.join(' ')}`); + let stdout = ''; + let stderr = ''; - const p = spawn(command, args, { - stdio: 'inherit', - shell: true, - ...options - }); + if (capture) { + child.stdout.on('data', d => (stdout += d)); + child.stderr.on('data', d => (stderr += d)); + } - p.on('close', (code) => { + child.on('error', reject); + + child.on('close', (code) => { if (code !== 0) { - return reject(new Error(`Process exited with code ${code}`)); + return reject( + new Error( + stderr.trim() || `Command failed: ${cmd} ${args.join(' ')}` + ) + ); } - resolve(true); + resolve(capture ? stdout : true); }); }); } @@ -72,7 +71,7 @@ const stripAnsi = (s) => /* ------------------- Wallet ------------------- */ async function checkWalletExists() { - const out = await testCommandExec(`node ${DIST_CLI} wallet list`); + const out = await runCommand('node', [DIST_CLI, 'wallet', 'list'], { capture: true }); if (out.includes('No wallets found')) { throw new Error('No wallet exists. Create and fund one first.'); } @@ -80,29 +79,28 @@ async function checkWalletExists() { async function unlockWallet() { console.log('Unlocking wallet using GOE_TEST_PASSWORD...'); - await testCommandSpawn( + await runCommand( 'node', [DIST_CLI, 'wallet', 'unlock'], { env: { - ...process.env, GOE_TEST_MODE: '1', - GOE_TEST_PASSWORD: PASSWORD - } + GOE_TEST_PASSWORD: PASSWORD, + }, } ); } const lockWallet = () => - testCommandExec( - `node ${DIST_CLI} wallet lock` - ); + runCommand('node', [DIST_CLI, 'wallet', 'lock']); /* ------------------- Repo ------------------- */ async function createRepo() { const name = randomRepoName(); - const out = await testCommandExec( - `node ${DIST_CLI} repo create ${name} --chain-id ${CHAIN_ID}` + const out = await runCommand( + 'node', + [DIST_CLI, 'repo', 'create', name, '--chain-id', CHAIN_ID], + { capture: true } ); const match = stripAnsi(out).match(/0x[a-fA-F0-9]{40}/); if (!match) { @@ -113,18 +111,13 @@ async function createRepo() { } const listRepos = () => - testCommandExec(`node ${DIST_CLI} repo list --chain-id ${CHAIN_ID}`); + runCommand('node', [DIST_CLI, 'repo', 'list', '--chain-id', CHAIN_ID]); const listBranches = (addr) => - testCommandExec( - `node ${DIST_CLI} repo branches ${addr} --chain-id ${CHAIN_ID}` - ); + runCommand('node', [DIST_CLI, 'repo', 'branches', addr, '--chain-id', CHAIN_ID],); const setDefaultBranch = (addr) => - testCommandSpawn( - 'node', - [DIST_CLI, 'repo', 'default-branch', addr, 'main', '--chain-id', CHAIN_ID] - ); + runCommand('node', [DIST_CLI, 'repo', 'default-branch', addr, 'main', '--chain-id', CHAIN_ID],); /* ------------------- Git flow ------------------- */ async function gitFlow(goe) { @@ -133,31 +126,31 @@ async function gitFlow(goe) { process.chdir(repoPath); // init + remote - await testCommandSpawn('git', ['init']); - await testCommandSpawn('git', ['remote', 'add', 'origin', goe]); - await testCommandSpawn('git', ['checkout', '-b', 'main']); + await runCommand('git', ['init']); + await runCommand('git', ['remote', 'add', 'origin', goe]); + await runCommand('git', ['checkout', '-b', 'main']); // init commit fs.writeFileSync('README.md', '# GoE E2E\n'); - await testCommandSpawn('git', ['add', '.']); - await testCommandSpawn('git', ['commit', '-m', 'init']); - await testCommandSpawn('git', ['push', 'origin', 'main']); + await runCommand('git', ['add', '.']); + await runCommand('git', ['commit', '-m', 'init']); + await runCommand('git', ['push', 'origin', 'main']); // feature branch - await testCommandSpawn('git', ['checkout', '-b', 'feature']); + await runCommand('git', ['checkout', '-b', 'feature']); fs.writeFileSync('feature.txt', 'feature\n'); - await testCommandSpawn('git', ['add', '.']); - await testCommandSpawn('git', ['commit', '-m', 'feature']); - await testCommandSpawn('git', ['push', 'origin', 'feature']); + await runCommand('git', ['add', '.']); + await runCommand('git', ['commit', '-m', 'feature']); + await runCommand('git', ['push', 'origin', 'feature']); // force push fs.writeFileSync('feature.txt', 'force\n'); - await testCommandSpawn('git', ['add', '.']); - await testCommandSpawn('git', ['commit', '-m', 'force-update']); - await testCommandSpawn('git', ['push', '--force', 'origin', 'feature']); + await runCommand('git', ['add', '.']); + await runCommand('git', ['commit', '-m', 'force-update']); + await runCommand('git', ['push', '--force', 'origin', 'feature']); // delete branch - await testCommandSpawn('git', ['push', 'origin', '--delete', 'feature']); + await runCommand('git', ['push', 'origin', '--delete', 'feature']); } async function gitCloneVerify(goe) { @@ -165,16 +158,16 @@ async function gitCloneVerify(goe) { fs.mkdirSync(clonePath, { recursive: true }); process.chdir(TEMP_DIR); - await testCommandSpawn('git', ['clone', goe, clonePath]); + await runCommand('git', ['clone', goe, clonePath]); process.chdir(clonePath); const readme = fs.readFileSync(path.join(clonePath, 'README.md'), 'utf8'); if (!readme.includes('GoE E2E')) throw new Error('Clone verification failed'); - const head = await testCommandExec('git symbolic-ref HEAD'); + const head = await runCommand('git',['symbolic-ref', 'HEAD'], { capture: true }); if (!head.includes('refs/heads/main')) throw new Error('HEAD is not refs/heads/main'); - const branches = await testCommandExec('git branch -r'); + const branches = await runCommand('git', ['branch', '-r'], { capture: true }); if (branches.includes('feature')) throw new Error('Deleted branch still exists'); } @@ -183,20 +176,20 @@ async function gitRejectNonFastForward(goe) { fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', ['clone', goe, '.']); + await runCommand('git', ['clone', goe, '.']); // push new file fs.writeFileSync('nff.txt', '1'); - await testCommandSpawn('git', ['add', '.']); - await testCommandSpawn('git', ['commit', '-m', 'nff-1']); - await testCommandSpawn('git', ['push', 'origin', 'main']); + await runCommand('git', ['add', '.']); + await runCommand('git', ['commit', '-m', 'nff-1']); + await runCommand('git', ['push', 'origin', 'main']); // reset head - await testCommandSpawn('git', ['reset', '--hard', 'HEAD~1']); + await runCommand('git', ['reset', '--hard', 'HEAD~1']); // non-fast-forward push should fail try { - await testCommandSpawn('git', ['push', 'origin', 'main']); + await runCommand('git', ['push', 'origin', 'main']); throw new Error('Expected non-fast-forward push to fail'); } catch { console.log('[expected] non-fast-forward rejected'); @@ -208,19 +201,19 @@ async function gitMergePush(goe) { fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', ['clone', goe, '.']); + await runCommand('git', ['clone', goe, '.']); // create a branch and commit - await testCommandSpawn('git', ['checkout', '-b', 'merge-a']); + await runCommand('git', ['checkout', '-b', 'merge-a']); fs.writeFileSync('a.txt', 'a'); - await testCommandSpawn('git', ['add', 'a.txt']); - await testCommandSpawn('git', ['commit', '-m', 'a']); + await runCommand('git', ['add', 'a.txt']); + await runCommand('git', ['commit', '-m', 'a']); // merge back to main - await testCommandSpawn('git', ['checkout', 'main']); - await testCommandSpawn('git', ['merge', 'merge-a']); + await runCommand('git', ['checkout', 'main']); + await runCommand('git', ['merge', 'merge-a']); - await testCommandSpawn('git', ['push', 'origin', 'main']); + await runCommand('git', ['push', 'origin', 'main']); } async function gitFetchUpdate(goe) { @@ -228,21 +221,21 @@ async function gitFetchUpdate(goe) { fs.mkdirSync(repoPath, { recursive: true }); process.chdir(repoPath); - await testCommandSpawn('git', ['clone', goe, '.']); + await runCommand('git', ['clone', goe, '.']); fs.writeFileSync('fetch.txt', 'x'); - await testCommandSpawn('git', ['add', 'fetch.txt']); - await testCommandSpawn('git', ['commit', '-m', 'fetch-update']); - await testCommandSpawn('git', ['push', 'origin', 'main']); + await runCommand('git', ['add', 'fetch.txt']); + await runCommand('git', ['commit', '-m', 'fetch-update']); + await runCommand('git', ['push', 'origin', 'main']); // simulate fetch from another clone const clonePath = path.join(TEMP_DIR, 'fetch-clone'); fs.mkdirSync(clonePath, { recursive: true }); - await testCommandSpawn('git', ['clone', goe, clonePath]); + await runCommand('git', ['clone', goe, clonePath]); process.chdir(clonePath); - await testCommandSpawn('git', ['fetch', 'origin']); - const success = await testCommandSpawn('git', ['log', 'origin/main', '--oneline', '-1'], { capture: true }); + await runCommand('git', ['fetch', 'origin']); + const success = await runCommand('git', ['log', 'origin/main', '--oneline', '-1'], { capture: true }); if (!success) throw new Error('Fetch did not update origin/main'); } @@ -256,7 +249,7 @@ function cleanup() { /* ------------------- main ------------------- */ async function npmLink() { console.log('\nLinking local goe-cli...'); - await testCommandSpawn('npm', ['link']); + await runCommand('npm', ['link']); } (async () => { From d802d4bc8056a85b7b73519e1d0eebf1c55ae67f Mon Sep 17 00:00:00 2001 From: iteye Date: Tue, 20 Jan 2026 18:21:03 +0800 Subject: [PATCH 08/15] revert code --- src/helper/core/goe.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/helper/core/goe.ts b/src/helper/core/goe.ts index 45c40c2..50a5dc7 100644 --- a/src/helper/core/goe.ts +++ b/src/helper/core/goe.ts @@ -128,10 +128,9 @@ class Goe { } else if (!force) { // fast-forward push if (src === "") { - // delete internalResult = await this.handleBranchDeletion(dst, this.defaultBranch); } else { - internalResult = await this.handlePush(src, dst, gasIncPct) + internalResult = await this.handlePush(src, dst, gasIncPct); } } else { // force push or delete From a788c0dcf078c261f80ec905488b2368eb6250fa Mon Sep 17 00:00:00 2001 From: iteye Date: Tue, 20 Jan 2026 18:23:50 +0800 Subject: [PATCH 09/15] remove pk --- test/test.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test.mjs b/test/test.mjs index c81017f..d963910 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -12,10 +12,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = path.resolve(__dirname, '../test'); const TEMP_DIR = path.join(PROJECT_ROOT, '/.tmp'); -const PRIVATE_KEY = process.env.GOE_TEST_PK; const PASSWORD = process.env.GOE_TEST_PASSWORD; -if (!PRIVATE_KEY || !PASSWORD) { +if (!PASSWORD) { console.error('Please set GOE_TEST_PK and GOE_TEST_PASSWORD in .env'); process.exit(1); } From b1f2c53b19d23872f2dee82b74626e4a37894803 Mon Sep 17 00:00:00 2001 From: iteye Date: Wed, 21 Jan 2026 15:06:27 +0800 Subject: [PATCH 10/15] fix comment --- .github/workflows/wallet-ci.yml | 24 ++++++++++++++++++ test/createWalletCI.mjs | 43 ++++++++++++++++++++++++++++++++ test/runCommand.mjs | 40 ++++++++++++++++++++++++++++++ test/test.mjs | 44 +++------------------------------ 4 files changed, 110 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/wallet-ci.yml create mode 100644 test/createWalletCI.mjs create mode 100644 test/runCommand.mjs diff --git a/.github/workflows/wallet-ci.yml b/.github/workflows/wallet-ci.yml new file mode 100644 index 0000000..2d64760 --- /dev/null +++ b/.github/workflows/wallet-ci.yml @@ -0,0 +1,24 @@ +name: goe-wallet-ci + +on: + workflow_dispatch: + push: + branches: [main] + +jobs: + wallet-create: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - run: npm run build + + - name: Run Wallet Create CI Test + run: node test/createWalletCI.mjs diff --git a/test/createWalletCI.mjs b/test/createWalletCI.mjs new file mode 100644 index 0000000..a966035 --- /dev/null +++ b/test/createWalletCI.mjs @@ -0,0 +1,43 @@ +import path from 'path'; +import { runCommand } from "./runCommand.mjs"; + +const DIST_CLI = path.resolve('./dist/cli/index.js'); +const PASSWORD = '12345678'; + +(async () => { + try { + const output = await runCommand( + 'node', + [DIST_CLI, 'wallet', 'create'], + { + env: { + GOE_TEST_MODE: '1', + GOE_TEST_PASSWORD: PASSWORD, + }, + capture: true + } + ); + if (!output.includes('Wallet Address:')) { + throw new Error('Wallet creation failed: no address found'); + } + + console.log('Unlocking wallet using GOE_TEST_PASSWORD...'); + await runCommand( + 'node', + [DIST_CLI, 'wallet', 'unlock'], + { + env: { + GOE_TEST_MODE: '1', + GOE_TEST_PASSWORD: PASSWORD, + }, + } + ); + + await runCommand('node', [DIST_CLI, 'wallet', 'lock']); + + console.log('✅ Wallet create + unlock/lock test passed!'); + } catch (e) { + console.error('❌ Wallet create test failed:', e.message); + process.exit(1); + } +})(); diff --git a/test/runCommand.mjs b/test/runCommand.mjs new file mode 100644 index 0000000..950e807 --- /dev/null +++ b/test/runCommand.mjs @@ -0,0 +1,40 @@ +/* -------- exec: capture stdout -------- */ +import {spawn} from "child_process"; + +export function runCommand(cmd, args = [], options = {}) { + const { + capture = false, + env = {}, + cwd, + } = options; + + return new Promise((resolve, reject) => { + const child = spawn(cmd, args, { + shell: false, // ⭐ + stdio: capture ? 'pipe' : 'inherit', + env: { ...process.env, ...env }, + cwd, + }); + + let stdout = ''; + let stderr = ''; + + if (capture) { + child.stdout.on('data', d => (stdout += d)); + child.stderr.on('data', d => (stderr += d)); + } + + child.on('error', reject); + + child.on('close', (code) => { + if (code !== 0) { + return reject( + new Error( + stderr.trim() || `Command failed: ${cmd} ${args.join(' ')}` + ) + ); + } + resolve(capture ? stdout : true); + }); + }); +} diff --git a/test/test.mjs b/test/test.mjs index d963910..6989e31 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -2,8 +2,8 @@ import 'dotenv/config'; import path from 'path'; import fs from 'fs'; -import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; +import { runCommand } from "./runCommand.mjs"; const DIST_CLI = path.resolve('./dist/cli/index.js'); const CHAIN_ID = '11155111'; @@ -20,44 +20,6 @@ if (!PASSWORD) { } -/* -------- exec: capture stdout -------- */ -export function runCommand(cmd, args = [], options = {}) { - const { - capture = false, - env = {}, - cwd, - } = options; - - return new Promise((resolve, reject) => { - const child = spawn(cmd, args, { - shell: false, // ⭐ - stdio: capture ? 'pipe' : 'inherit', - env: { ...process.env, ...env }, - cwd, - }); - - let stdout = ''; - let stderr = ''; - - if (capture) { - child.stdout.on('data', d => (stdout += d)); - child.stderr.on('data', d => (stderr += d)); - } - - child.on('error', reject); - - child.on('close', (code) => { - if (code !== 0) { - return reject( - new Error( - stderr.trim() || `Command failed: ${cmd} ${args.join(' ')}` - ) - ); - } - resolve(capture ? stdout : true); - }); - }); -} /* ---------------- utils ---------------- */ @@ -234,8 +196,8 @@ async function gitFetchUpdate(goe) { process.chdir(clonePath); await runCommand('git', ['fetch', 'origin']); - const success = await runCommand('git', ['log', 'origin/main', '--oneline', '-1'], { capture: true }); - if (!success) throw new Error('Fetch did not update origin/main'); + const result = await runCommand('git', ['log', 'origin/main', '--oneline', '-1'], { capture: true }); + if (!result.includes('fetch-update')) throw new Error('Fetch did not update origin/main'); } From 58ad78a11582bcbeb86a5c31f8f0967c6dfbdf88 Mon Sep 17 00:00:00 2001 From: iteye Date: Wed, 21 Jan 2026 15:14:40 +0800 Subject: [PATCH 11/15] fix comment --- .github/workflows/wallet-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/wallet-ci.yml b/.github/workflows/wallet-ci.yml index 2d64760..54a207d 100644 --- a/.github/workflows/wallet-ci.yml +++ b/.github/workflows/wallet-ci.yml @@ -2,8 +2,6 @@ name: goe-wallet-ci on: workflow_dispatch: - push: - branches: [main] jobs: wallet-create: From 1c105af5da101eaf250861f8c35d283ea237deba Mon Sep 17 00:00:00 2001 From: iteye Date: Wed, 21 Jan 2026 15:27:46 +0800 Subject: [PATCH 12/15] fix ci --- .github/workflows/wallet-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/wallet-ci.yml b/.github/workflows/wallet-ci.yml index 54a207d..96d6138 100644 --- a/.github/workflows/wallet-ci.yml +++ b/.github/workflows/wallet-ci.yml @@ -1,6 +1,9 @@ name: goe-wallet-ci on: + push: + branches: + - 'test' workflow_dispatch: jobs: From 6ff321fc91d023999679573bc1d123d4fe76d925 Mon Sep 17 00:00:00 2001 From: iteye Date: Wed, 21 Jan 2026 15:30:56 +0800 Subject: [PATCH 13/15] fix ci --- .github/workflows/wallet-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wallet-ci.yml b/.github/workflows/wallet-ci.yml index 96d6138..81a732e 100644 --- a/.github/workflows/wallet-ci.yml +++ b/.github/workflows/wallet-ci.yml @@ -16,10 +16,11 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + cache: 'yarn' - - run: npm ci + - run: yarn install - - run: npm run build + - run: yarn build - name: Run Wallet Create CI Test run: node test/createWalletCI.mjs From 64600f96119ea4972349f3c8f8de424f914b64bf Mon Sep 17 00:00:00 2001 From: iteye Date: Thu, 22 Jan 2026 10:01:44 +0800 Subject: [PATCH 14/15] fix Windows error --- test/runCommand.mjs | 11 ++++++++++- test/test.mjs | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/test/runCommand.mjs b/test/runCommand.mjs index 950e807..fa4f644 100644 --- a/test/runCommand.mjs +++ b/test/runCommand.mjs @@ -1,6 +1,14 @@ /* -------- exec: capture stdout -------- */ import {spawn} from "child_process"; +const SHELL_CMDS = new Set([ + 'npm', + 'pnpm', + 'yarn', + 'npx', +]); + + export function runCommand(cmd, args = [], options = {}) { const { capture = false, @@ -8,9 +16,10 @@ export function runCommand(cmd, args = [], options = {}) { cwd, } = options; + const useShell = SHELL_CMDS.has(cmd); return new Promise((resolve, reject) => { const child = spawn(cmd, args, { - shell: false, // ⭐ + shell: useShell, // ⭐ stdio: capture ? 'pipe' : 'inherit', env: { ...process.env, ...env }, cwd, diff --git a/test/test.mjs b/test/test.mjs index 6989e31..555b3d8 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -208,7 +208,13 @@ function cleanup() { } /* ------------------- main ------------------- */ -async function npmLink() { +async function npmPrepareAndLink() { + console.log('\nPreparing local goe-cli...'); + + // 1. build + await runCommand('npm', ['run', 'build']); + + // 2. link console.log('\nLinking local goe-cli...'); await runCommand('npm', ['link']); } @@ -217,7 +223,7 @@ async function npmLink() { fs.mkdirSync(TEMP_DIR, { recursive: true }); // goe - await npmLink(); + await npmPrepareAndLink(); await checkWalletExists(); await unlockWallet(); const {name, address} = await createRepo(); From 7780ed4edfbf77b6e20d6aafada08137507fecc7 Mon Sep 17 00:00:00 2001 From: iteye Date: Thu, 22 Jan 2026 10:04:34 +0800 Subject: [PATCH 15/15] fix text --- test/test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.mjs b/test/test.mjs index 555b3d8..0e3d682 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -15,7 +15,7 @@ const TEMP_DIR = path.join(PROJECT_ROOT, '/.tmp'); const PASSWORD = process.env.GOE_TEST_PASSWORD; if (!PASSWORD) { - console.error('Please set GOE_TEST_PK and GOE_TEST_PASSWORD in .env'); + console.error('Please set "GOE_TEST_PASSWORD" in .env'); process.exit(1); }