From ff1627f61a2c88d8a903f7614c23ca98598b28e8 Mon Sep 17 00:00:00 2001 From: Edgadafi <203378643+Edgadafi@users.noreply.github.com> Date: Tue, 13 Oct 2026 09:51:54 -0600 Subject: [PATCH] ci(sdk): pack the SDK and import it from a clean install (#107) Adds a clean-install job to sdk-test.yml that runs packages/sdk/scripts/clean-install-check.mjs: npm pack, install the tarball into an empty temp project, then ESM import, CJS require and a tsc --noEmit type-check against the shipped .d.ts, constructing Agent#initX402() and x402Serve() with dummy config (no network). Also stops publishing compiled tests (dist/*.test.js/.d.ts) via a negated files entry; the job asserts no *.test.* file is in the tarball. Closes #107 AI disclosure: drafted with AI assistance under my direction and reviewed by hand. --- .github/workflows/sdk-test.yml | 18 +++++ packages/sdk/package.json | 1 + packages/sdk/scripts/clean-install-check.mjs | 76 ++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 packages/sdk/scripts/clean-install-check.mjs diff --git a/.github/workflows/sdk-test.yml b/.github/workflows/sdk-test.yml index f25a914..97c6b26 100644 --- a/.github/workflows/sdk-test.yml +++ b/.github/workflows/sdk-test.yml @@ -33,3 +33,21 @@ jobs: - run: npm install --no-audit --no-fund --legacy-peer-deps - run: npm run build - run: npm test + # Issue #107: tests above run from the source tree, so they cannot see + # packaging bugs (0.10.0 require() in ESM, 0.13.0 ERR_MODULE_NOT_FOUND, the + # undeclared dependency fixed in #68). This packs the SDK, installs the + # tarball into an empty project and imports it as ESM, CJS and TypeScript, + # and fails if any *.test.* file would be published. No network beyond npm. + clean-install: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/sdk + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm install --no-audit --no-fund --legacy-peer-deps + - run: npm run build + - run: node scripts/clean-install-check.mjs diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 2cfe122..9559856 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -21,6 +21,7 @@ }, "files": [ "dist", + "!dist/**/*.test.*", "README.md" ], "engines": { diff --git a/packages/sdk/scripts/clean-install-check.mjs b/packages/sdk/scripts/clean-install-check.mjs new file mode 100644 index 0000000..f5cc52c --- /dev/null +++ b/packages/sdk/scripts/clean-install-check.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node +// Packs the SDK and installs the tarball into an empty temp project, then +// imports it the way a user would: ESM `import`, CJS `require`, and a +// TypeScript type-check against the shipped .d.ts. No network beyond npm. +// +// Catches the class of bug that tests from the source tree cannot see: +// 0.10.0 (`require()` inside ESM), 0.13.0 (ERR_MODULE_NOT_FOUND), and the +// undeclared dependency fixed in #68. See issue #107. +// +// Run from packages/sdk after `npm run build`: node scripts/clean-install-check.mjs +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; + +const sdkDir = resolve(import.meta.dirname, '..'); +const tmp = mkdtempSync(join(tmpdir(), 'nirium-clean-install-')); +const run = (cmd, args, cwd) => + execFileSync(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'inherit'], encoding: 'utf8' }); + +try { + // 1. Pack. --json lists every file that would be published. + const [pack] = JSON.parse(run('npm', ['pack', '--json', '--pack-destination', tmp], sdkDir)); + const files = pack.files.map((f) => f.path); + const tests = files.filter((p) => /\.test\./.test(p)); + if (tests.length) throw new Error(`tarball ships test files:\n ${tests.join('\n ')}`); + for (const required of ['dist/index.js', 'dist/index.d.ts', 'package.json']) { + if (!files.includes(required)) throw new Error(`tarball is missing ${required}`); + } + console.log(`packed ${pack.filename}: ${files.length} files, no *.test.* files`); + + // 2. Install into an empty project, outside the monorepo. + const app = join(tmp, 'app'); + run('mkdir', ['-p', app]); + writeFileSync(join(app, 'package.json'), JSON.stringify({ name: 'clean-install', private: true })); + run('npm', ['install', '--no-audit', '--no-fund', '--legacy-peer-deps', join(tmp, pack.filename)], app); + + // 3. Use it. Dummy config only: initX402 builds the client locally and + // x402Serve defers every facilitator call to the first request. + const body = (load) => ` +${load} +const { Agent, x402Serve } = sdk; +if (typeof Agent !== 'function' || typeof x402Serve !== 'function') throw new Error('missing exports'); +const agent = new Agent({ apiKey: 'nrm_dummy' }); +agent.initX402({ secretKey: Keypair.random().secret(), network: 'stellar:testnet' }); +const mw = x402Serve({ + payTo: Keypair.random().publicKey(), + routes: { '/ping': '$0.01' }, + network: 'stellar:testnet', + facilitatorApiKey: 'dummy', +}); +if (typeof mw !== 'function') throw new Error('x402Serve did not return a middleware'); +console.log('ok:', process.argv[1].split('/').pop()); +`; + writeFileSync(join(app, 'esm.mjs'), body( + "import * as sdk from 'nirium';\nimport { Keypair } from '@stellar/stellar-sdk';")); + writeFileSync(join(app, 'cjs.cjs'), body( + "const sdk = require('nirium');\nconst { Keypair } = require('@stellar/stellar-sdk');")); + writeFileSync(join(app, 'types.ts'), ` +import Agent, { x402Serve, type X402Config, type X402ServeConfig } from 'nirium'; +const cfg: X402Config = { secretKey: 'S...', network: 'stellar:testnet' }; +const serve: X402ServeConfig = { payTo: 'G...', routes: { '/ping': '$0.01' } }; +new Agent({ apiKey: 'x' }).initX402(cfg); +x402Serve(serve); +`); + console.log(run('node', ['esm.mjs'], app).trim()); + console.log(run('node', ['cjs.cjs'], app).trim()); + run(join(sdkDir, 'node_modules', '.bin', 'tsc'), [ + '--noEmit', '--strict', '--skipLibCheck', '--module', 'nodenext', + '--moduleResolution', 'nodenext', '--target', 'es2022', '--types', 'node', + '--typeRoots', join(sdkDir, 'node_modules', '@types'), 'types.ts', + ], app); + console.log('ok: types.ts (tsc --noEmit against the shipped .d.ts)'); +} finally { + rmSync(tmp, { recursive: true, force: true }); +}