diff --git a/.changeset/red-roses-speak.md b/.changeset/red-roses-speak.md new file mode 100644 index 000000000..0896dba82 --- /dev/null +++ b/.changeset/red-roses-speak.md @@ -0,0 +1,5 @@ +--- +'@graphprotocol/graph-cli': patch +--- + +handle invalid characters in contract name #1883 diff --git a/packages/cli/src/command-helpers/subgraph.test.ts b/packages/cli/src/command-helpers/subgraph.test.ts new file mode 100644 index 000000000..b3ac994e7 --- /dev/null +++ b/packages/cli/src/command-helpers/subgraph.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; +import { formatContractName, formatSubgraphName, getSubgraphBasename } from './subgraph'; + +describe('getSubgraphBasename', () => { + it('returns the last segment of a subgraph name', () => { + expect(getSubgraphBasename('user/my-subgraph')).toBe('my-subgraph'); + expect(getSubgraphBasename('org/project')).toBe('project'); + }); + + it('returns the full name if no slash is present', () => { + expect(getSubgraphBasename('single-name')).toBe('single-name'); + }); +}); + +describe('formatSubgraphName', () => { + it('converts to lowercase', () => { + expect(formatSubgraphName('MySubGraph')).toBe('mysubgraph'); + }); + + it('replaces spaces with hyphens', () => { + expect(formatSubgraphName('my subgraph name')).toBe('my-subgraph-name'); + expect(formatSubgraphName('multiple spaces')).toBe('multiple-spaces'); + }); + + it('removes special characters', () => { + expect(formatSubgraphName('my$special@subgraph!')).toBe('myspecialsubgraph'); + }); + + it('keeps alphanumeric characters, hyphens and underscores', () => { + expect(formatSubgraphName('my-subgraph_123')).toBe('my-subgraph_123'); + }); +}); + +describe('formatContractName', () => { + it('replaces spaces and dots with underscores', () => { + expect(formatContractName('My Contract')).toBe('My_Contract'); + expect(formatContractName('contract.name')).toBe('contract_name'); + expect(formatContractName('multiple...dots')).toBe('multiple_dots'); + }); + + it('removes special characters but keeps alphanumeric, hyphens and underscores', () => { + expect(formatContractName('Contract$$$Name')).toBe('ContractName'); + expect(formatContractName('My-Contract_123')).toBe('My-Contract_123'); + expect(formatContractName('My Contract $$$$')).toBe('My_Contract_'); + }); + + it('preserves case', () => { + expect(formatContractName('MyContractName')).toBe('MyContractName'); + }); +}); diff --git a/packages/cli/src/command-helpers/subgraph.ts b/packages/cli/src/command-helpers/subgraph.ts index 5ca396db5..2a72b58b3 100644 --- a/packages/cli/src/command-helpers/subgraph.ts +++ b/packages/cli/src/command-helpers/subgraph.ts @@ -9,3 +9,7 @@ export const formatSubgraphName = (slug: string) => { .replace(/\s+/g, '-') .replace(/[^a-z0-9_-]/g, ''); }; + +export const formatContractName = (contractName: string) => { + return contractName.replace(/[\s.]+/g, '_').replace(/[^a-zA-Z0-9_-]/g, ''); +}; diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 9bb408ff9..387a3234b 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -16,7 +16,11 @@ import { retryWithPrompt } from '../command-helpers/retry.js'; import { generateScaffold, writeScaffold } from '../command-helpers/scaffold.js'; import { sortWithPriority } from '../command-helpers/sort.js'; import { withSpinner } from '../command-helpers/spinner.js'; -import { formatSubgraphName, getSubgraphBasename } from '../command-helpers/subgraph.js'; +import { + formatContractName, + formatSubgraphName, + getSubgraphBasename, +} from '../command-helpers/subgraph.js'; import { GRAPH_CLI_SHARED_HEADERS } from '../constants.js'; import debugFactory from '../debug.js'; import EthereumABI from '../protocols/ethereum/abi.js'; @@ -289,7 +293,7 @@ export default class InitCommand extends Command { indexEvents, network, subgraphName, - contractName: contractName || DEFAULT_CONTRACT_NAME, + contractName, node, startBlock, spkgPath, @@ -351,7 +355,7 @@ export default class InitCommand extends Command { network: answers.network, source: answers.source, indexEvents: answers.indexEvents, - contractName: answers.contractName || DEFAULT_CONTRACT_NAME, + contractName: answers.contractName, node, startBlock: answers.startBlock, spkgPath: answers.spkgPath, @@ -872,7 +876,7 @@ async function processInitForm( type: 'input', name: 'contractName', message: 'Contract name', - initial: () => initContractName || contractName || 'Contract', + initial: () => initContractName || contractName || DEFAULT_CONTRACT_NAME, skip: () => initFromExample !== undefined || !protocolInstance.hasContract() || isSubstreams, validate: value => initFromExample !== undefined || @@ -1307,7 +1311,7 @@ async function initSubgraphFromContract( network, source, indexEvents, - contractName, + contractName: formatContractName(contractName || DEFAULT_CONTRACT_NAME), startBlock, node, spkgPath,