import sinon from 'sinon'; import path from 'node:path'; import { promises as fsp } from 'node:fs'; import { randomUUID } from 'node:crypto'; import { expect } from '../../../../test-utils'; import { CollectionModuleDefinitionResponse } from '../../../domain/collection-modules/collection-module-definition'; import { writeCollectionModuleDefinition } from '../write-collection-module-definition'; import * as downloadSourcePackage from '../download-source-package'; const buildDefinition = (): CollectionModuleDefinitionResponse => ({ collectionModuleKey: 'example_module', collectionModuleName: 'Example Collection Module', collectionModuleDefinitionId: randomUUID(), organizationId: randomUUID(), // New format: empty code + empty codeFiles triggers source.zip download branch. code: '', codeFiles: [], rootConfig: { settings: {}, manualTransactions: [], }, workflows: { manualTransactionWorkflows: [], }, }); describe('writeCollectionModuleDefinition — root-placed dev tooling folders', () => { const sandbox = sinon.createSandbox(); const tmpDirectory = path.join(__dirname, 'tmp-root-placed'); let cloneDirectory: string; before(async () => { cloneDirectory = path.join(tmpDirectory, 'clone'); await fsp.mkdir(cloneDirectory, { recursive: true }); sandbox.replace(downloadSourcePackage, 'downloadAndExtractSourcePackage', async () => [ // In-tree source files — should land under `code/`. { pathSegments: ['main.ts'], content: 'export const handler = async () => ({});\n' }, { pathSegments: ['nested', 'util.ts'], content: 'export const x = 1;\n' }, // Dev-tooling folders — should land at the clone root. { pathSegments: ['.claude', 'settings.json'], content: '{"claude":true}\n' }, { pathSegments: ['.cursor', 'rules', 'foo.mdc'], content: 'rule\n' }, { pathSegments: ['.github', 'workflows', 'ci.yml'], content: 'name: ci\n' }, ]); await writeCollectionModuleDefinition({ collectionModuleDefinition: buildDefinition(), directory: cloneDirectory, host: 'https://fake-url.test', apiKey: 'fake-api-key', liveVersion: false, }); }); after(async () => { await fsp.rm(tmpDirectory, { recursive: true, force: true }); sandbox.verifyAndRestore(); }); it('writes in-tree source files under the code/ subdirectory', async () => { const mainTs = await fsp.readFile(path.join(cloneDirectory, 'code', 'main.ts'), 'utf8'); expect(mainTs).to.equal('export const handler = async () => ({});\n'); const nested = await fsp.readFile(path.join(cloneDirectory, 'code', 'nested', 'util.ts'), 'utf8'); expect(nested).to.equal('export const x = 1;\n'); }); it('writes .claude/ at the clone root, not inside code/', async () => { const atRoot = await fsp.readFile(path.join(cloneDirectory, '.claude', 'settings.json'), 'utf8'); expect(atRoot).to.equal('{"claude":true}\n'); const insideCode = fsp.access(path.join(cloneDirectory, 'code', '.claude')); await expect(insideCode).to.be.rejectedWith(/ENOENT/); }); it('writes .cursor/ at the clone root, not inside code/', async () => { const atRoot = await fsp.readFile(path.join(cloneDirectory, '.cursor', 'rules', 'foo.mdc'), 'utf8'); expect(atRoot).to.equal('rule\n'); const insideCode = fsp.access(path.join(cloneDirectory, 'code', '.cursor')); await expect(insideCode).to.be.rejectedWith(/ENOENT/); }); it('writes .github/ at the clone root, not inside code/', async () => { const atRoot = await fsp.readFile(path.join(cloneDirectory, '.github', 'workflows', 'ci.yml'), 'utf8'); expect(atRoot).to.equal('name: ci\n'); const insideCode = fsp.access(path.join(cloneDirectory, 'code', '.github')); await expect(insideCode).to.be.rejectedWith(/ENOENT/); }); });