bktide
Version:
Command-line interface for Buildkite CI/CD workflows with rich shell completions (Fish, Bash, Zsh) and Alfred workflow integration for macOS power users
590 lines • 24.5 kB
JavaScript
/**
* PatternMockGenerator uses extracted data patterns to generate realistic mock data
*/
import { faker } from '@faker-js/faker';
import { MockList } from '@graphql-tools/mock';
import { promises as fs } from 'fs';
export class PatternMockGenerator {
patterns = null;
patternsPath;
useDefaultPatterns = false;
constructor(patternsPath = './test/fixtures/data-patterns.json') {
this.patternsPath = patternsPath;
}
async loadPatterns() {
try {
const data = await fs.readFile(this.patternsPath, 'utf-8');
this.patterns = JSON.parse(data);
console.log(`✓ Loaded data patterns from ${this.patternsPath}`);
}
catch (error) {
console.warn(`⚠️ Could not load patterns from ${this.patternsPath}, using defaults`);
this.useDefaultPatterns = true;
}
}
generateMocks() {
if (!this.patterns && !this.useDefaultPatterns) {
throw new Error('Patterns not loaded. Call loadPatterns() first.');
}
return {
// Core types
Build: () => ({
id: faker.string.uuid(),
number: this.generateBuildNumber(),
state: this.generateBuildState(),
branch: this.generateBranch(),
message: this.generateCommitMessage(),
commit: faker.git.commitSha(),
createdAt: faker.date.recent({ days: 30 }).toISOString(),
scheduledAt: faker.date.recent({ days: 30 }).toISOString(),
startedAt: faker.date.recent({ days: 30 }).toISOString(),
finishedAt: this.generateFinishedAt(),
canceledAt: null,
url: `https://buildkite.com/${faker.word.noun()}/${faker.word.noun()}/builds/${faker.number.int({ min: 1, max: 9999 })}`,
webUrl: `https://buildkite.com/${faker.word.noun()}/${faker.word.noun()}/builds/${faker.number.int({ min: 1, max: 9999 })}`,
pullRequest: this.generatePullRequest(),
rebuiltFrom: null,
jobs: this.generateJobs(),
annotations: this.generateAnnotations(),
createdBy: this.generateCreator(),
pipeline: this.generatePipelineRef(),
organization: this.generateOrganizationRef()
}),
Pipeline: () => ({
id: faker.string.uuid(),
graphqlId: faker.string.uuid(),
slug: this.generatePipelineSlug(),
name: this.generatePipelineName(),
description: faker.datatype.boolean({ probability: 0.7 })
? faker.lorem.sentence()
: null,
url: `https://buildkite.com/${faker.word.noun()}/${faker.word.noun()}`,
webUrl: `https://buildkite.com/${faker.word.noun()}/${faker.word.noun()}`,
repository: this.generateRepository(),
defaultBranch: this.generateDefaultBranch(),
visibility: this.generateVisibility(),
archived: faker.datatype.boolean({ probability: 0.05 }),
createdAt: faker.date.past({ years: 2 }).toISOString(),
createdBy: this.generateCreator(),
builds: () => new MockList([0, 10]),
organization: this.generateOrganizationRef()
}),
Job: () => this.generateJob(),
JobTypeCommand: () => ({
...this.generateJobBase(),
__typename: 'JobTypeCommand',
command: faker.helpers.arrayElement([
'npm test',
'yarn build',
'make test',
'./scripts/test.sh',
'docker build .',
'pytest',
'cargo test'
]),
exitStatus: this.generateExitStatus(),
passed: faker.datatype.boolean({ probability: 0.85 }),
softFailed: faker.datatype.boolean({ probability: 0.02 }),
parallelGroupIndex: this.generateParallelGroupIndex(),
parallelGroupTotal: this.generateParallelGroupTotal(),
retriedAutomatically: faker.datatype.boolean({
probability: this.patterns?.jobs.retryRates.automatic || 0.05
}),
retriedManually: faker.datatype.boolean({
probability: this.patterns?.jobs.retryRates.manual || 0.02
}),
retryType: null,
agent: this.generateAgent(),
agentQueryRules: [],
artifacts: () => new MockList([0, 5])
}),
JobTypeWait: () => ({
...this.generateJobBase(),
__typename: 'JobTypeWait',
continueOnFailure: faker.datatype.boolean({ probability: 0.1 })
}),
JobTypeTrigger: () => ({
...this.generateJobBase(),
__typename: 'JobTypeTrigger',
triggered: faker.datatype.boolean({ probability: 0.9 }),
triggeredBuild: faker.datatype.boolean({ probability: 0.9 })
? { id: faker.string.uuid() }
: null
}),
Organization: () => ({
id: faker.string.uuid(),
graphqlId: faker.string.uuid(),
slug: this.generateOrganizationSlug(),
name: this.generateOrganizationName(),
url: `https://buildkite.com/${faker.word.noun()}`,
webUrl: `https://buildkite.com/${faker.word.noun()}`,
pipelines: () => new MockList(this.selectByDistribution(this.patterns?.organizations.pipelineCount || {
values: [{ value: 10, frequency: 0.5, count: 1 }]
})),
members: () => new MockList(this.selectByDistribution(this.patterns?.organizations.memberCount || {
values: [{ value: 5, frequency: 0.5, count: 1 }]
})),
teams: () => new MockList([1, 5]),
createdAt: faker.date.past({ years: 3 }).toISOString()
}),
Annotation: () => ({
id: faker.string.uuid(),
context: this.generateAnnotationContext(),
style: this.generateAnnotationStyle(),
bodyHtml: this.generateAnnotationBody(),
createdAt: faker.date.recent({ days: 1 }).toISOString(),
updatedAt: faker.date.recent({ days: 1 }).toISOString()
}),
User: () => ({
id: faker.string.uuid(),
uuid: faker.string.uuid(),
name: this.generateUserName(),
email: this.generateUserEmail(),
avatar: {
url: faker.image.avatar()
},
bot: faker.datatype.boolean({
probability: this.patterns?.builds.creatorPatterns.botUsers || 0.1
})
}),
Agent: () => this.generateAgent(),
Viewer: () => ({
id: faker.string.uuid(),
user: {
id: faker.string.uuid(),
uuid: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email()
},
organizations: () => new MockList([1, 5])
}),
// Scalar types
DateTime: () => faker.date.recent().toISOString(),
ISO8601Date: () => faker.date.recent().toISOString().split('T')[0],
JSON: () => JSON.stringify({ key: faker.word.noun(), value: faker.word.verb() }),
YAML: () => `key: ${faker.word.noun()}\nvalue: ${faker.word.verb()}`,
Int: () => faker.number.int({ min: 0, max: 1000 }),
Float: () => faker.number.float({ min: 0, max: 1000, multipleOf: 0.01 }),
Boolean: () => faker.datatype.boolean(),
String: () => faker.lorem.word()
};
}
selectByDistribution(distribution) {
if (!distribution.values || distribution.values.length === 0) {
// Return a default value based on type
return 0;
}
const random = Math.random();
let cumulative = 0;
for (const item of distribution.values) {
cumulative += item.frequency;
if (random <= cumulative) {
return item.value;
}
}
return distribution.values[0].value;
}
generateBuildNumber() {
if (this.patterns?.builds.numberRange) {
const { min, max } = this.patterns.builds.numberRange;
return faker.number.int({ min, max });
}
return faker.number.int({ min: 1, max: 9999 });
}
generateBuildState() {
if (this.patterns?.builds.states) {
return this.selectByDistribution(this.patterns.builds.states);
}
return faker.helpers.arrayElement([
'PASSED', 'FAILED', 'RUNNING', 'SCHEDULED',
'CANCELED', 'CANCELING', 'BLOCKED', 'NOT_RUN'
]);
}
generateBranch() {
if (!this.patterns?.builds.branches) {
return faker.git.branch();
}
const formats = this.patterns.builds.branches.formats;
const random = Math.random();
if (random < formats.feature) {
return `feature/${faker.word.verb()}-${faker.word.noun()}`;
}
else if (random < formats.feature + formats.bugfix) {
return `bugfix/${faker.word.verb()}-${faker.word.noun()}`;
}
else if (random < formats.feature + formats.bugfix + formats.release) {
return `release/${faker.system.semver()}`;
}
else if (random < formats.feature + formats.bugfix + formats.release + formats.main) {
return faker.helpers.arrayElement(['main', 'master', 'develop']);
}
else {
return faker.git.branch();
}
}
generateCommitMessage() {
if (!this.patterns?.builds.messagePatterns) {
return faker.git.commitMessage();
}
const patterns = this.patterns.builds.messagePatterns;
const useConventional = Math.random() < patterns.conventionalCommits;
const useEmoji = Math.random() < patterns.hasEmoji;
const useGithubRef = Math.random() < patterns.githubRefs;
const useJiraRef = Math.random() < patterns.jiraRefs;
const useMultiline = Math.random() < patterns.multiline;
let message = '';
if (useConventional) {
const type = faker.helpers.arrayElement([
'feat', 'fix', 'docs', 'style', 'refactor',
'test', 'chore', 'perf', 'ci', 'build'
]);
const scope = Math.random() < 0.3 ? `(${faker.word.noun()})` : '';
message = `${type}${scope}: `;
}
if (useEmoji) {
message += faker.helpers.arrayElement(['🎉', '🚀', '✨', '🔧', '📦', '👷', '🐛', '⚡️', '✅', '💚']) + ' ';
}
message += faker.git.commitMessage();
if (useGithubRef) {
message += ` (#${faker.number.int({ min: 1, max: 9999 })})`;
}
if (useJiraRef) {
const project = faker.helpers.arrayElement(['PROJ', 'TEAM', 'BUG', 'FEAT']);
message += ` ${project}-${faker.number.int({ min: 1, max: 9999 })}`;
}
if (useMultiline) {
message += '\n\n' + faker.lorem.paragraph();
}
return message;
}
generateFinishedAt() {
const shouldBeFinished = Math.random() < 0.9;
if (shouldBeFinished) {
return faker.date.recent({ days: 30 }).toISOString();
}
return null;
}
generatePullRequest() {
const hasPR = Math.random() < 0.3;
if (!hasPR)
return null;
return {
id: faker.number.int({ min: 1, max: 9999 }).toString(),
base: faker.git.branch(),
repository: `https://github.com/${faker.word.noun()}/${faker.word.noun()}`
};
}
generateJobs() {
const count = this.patterns?.builds.jobCounts
? this.selectByDistribution(this.patterns.builds.jobCounts)
: faker.number.int({ min: 1, max: 10 });
return () => new MockList(count);
}
generateAnnotations() {
const count = this.patterns?.builds.annotationCounts
? this.selectByDistribution(this.patterns.builds.annotationCounts)
: faker.number.int({ min: 0, max: 3 });
return () => new MockList(count);
}
generateCreator() {
return {
id: faker.string.uuid(),
name: this.generateUserName(),
email: this.generateUserEmail(),
avatar: {
url: faker.image.avatar()
}
};
}
generateUserName() {
if (this.patterns?.builds.creatorPatterns.nameFormats) {
const format = this.selectByDistribution(this.patterns.builds.creatorPatterns.nameFormats);
switch (format) {
case 'full-name':
return faker.person.fullName();
case 'dotted':
return `${faker.person.firstName()}.${faker.person.lastName()}`.toLowerCase();
case 'hyphenated':
return `${faker.person.firstName()}-${faker.person.lastName()}`.toLowerCase();
case 'underscored':
return `${faker.person.firstName()}_${faker.person.lastName()}`.toLowerCase();
case 'lowercase':
return faker.internet.username().toLowerCase();
default:
return faker.person.fullName();
}
}
return faker.person.fullName();
}
generateUserEmail() {
if (this.patterns?.builds.creatorPatterns.domains) {
const domain = this.selectByDistribution(this.patterns.builds.creatorPatterns.domains);
return `${faker.internet.username()}@${domain}`.toLowerCase();
}
return faker.internet.email();
}
generatePipelineSlug() {
const format = this.patterns?.pipelines.slugFormats
? this.selectByDistribution(this.patterns.pipelines.slugFormats)
: 'kebab-case';
const words = [faker.word.noun(), faker.word.verb()];
switch (format) {
case 'kebab-case':
return words.join('-').toLowerCase();
case 'snake_case':
return words.join('_').toLowerCase();
case 'camelCase':
return words[0].toLowerCase() + words.slice(1).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
default:
return words.join('').toLowerCase();
}
}
generatePipelineName() {
const length = this.patterns?.pipelines.nameLength?.average || 20;
const variance = 10;
const targetLength = Math.max(5, Math.floor(length + (Math.random() - 0.5) * variance));
let name = faker.company.name();
while (name.length < targetLength) {
name += ' ' + faker.word.noun();
}
return name.substring(0, targetLength);
}
generateRepository() {
const provider = this.patterns?.pipelines.repositoryProviders
? this.selectByDistribution(this.patterns.pipelines.repositoryProviders)
: 'github';
const org = faker.word.noun();
const repo = faker.word.noun();
switch (provider) {
case 'github':
return {
url: `https://github.com/${org}/${repo}`,
provider: { name: 'GitHub' }
};
case 'gitlab':
return {
url: `https://gitlab.com/${org}/${repo}`,
provider: { name: 'GitLab' }
};
case 'bitbucket':
return {
url: `https://bitbucket.org/${org}/${repo}`,
provider: { name: 'Bitbucket' }
};
default:
return {
url: `git@git.example.com:${org}/${repo}.git`,
provider: { name: 'Git' }
};
}
}
generateDefaultBranch() {
if (this.patterns?.pipelines.defaultBranches) {
return this.selectByDistribution(this.patterns.pipelines.defaultBranches);
}
return faker.helpers.arrayElement(['main', 'master', 'develop']);
}
generateVisibility() {
if (this.patterns?.pipelines.visibility) {
return this.selectByDistribution(this.patterns.pipelines.visibility);
}
return faker.helpers.arrayElement(['PRIVATE', 'PUBLIC']);
}
generateJobBase() {
return {
id: faker.string.uuid(),
uuid: faker.string.uuid(),
label: this.generateJobLabel(),
state: this.generateJobState(),
runAt: faker.date.recent({ days: 1 }).toISOString(),
scheduledAt: faker.date.recent({ days: 1 }).toISOString(),
startedAt: faker.date.recent({ days: 1 }).toISOString(),
finishedAt: faker.date.recent({ days: 1 }).toISOString(),
canceledAt: null,
unblockable: faker.datatype.boolean({ probability: 0.1 }),
unblockUrl: null,
url: `https://buildkite.com/${faker.word.noun()}/${faker.word.noun()}/builds/${faker.number.int()}/jobs/${faker.string.uuid()}`,
webUrl: `https://buildkite.com/${faker.word.noun()}/${faker.word.noun()}/builds/${faker.number.int()}#${faker.string.uuid()}`,
build: { id: faker.string.uuid() }
};
}
generateJob() {
const type = faker.helpers.arrayElement(['command', 'wait', 'trigger']);
switch (type) {
case 'wait':
return { __typename: 'JobTypeWait' };
case 'trigger':
return { __typename: 'JobTypeTrigger' };
default:
return { __typename: 'JobTypeCommand' };
}
}
generateJobLabel() {
if (this.patterns?.jobs.labelPatterns) {
const pattern = this.selectByDistribution(this.patterns.jobs.labelPatterns);
return pattern
.replace(':version', faker.system.semver())
.replace(':os', faker.helpers.arrayElement(['linux', 'ubuntu', 'macos', 'windows']))
.replace(':test', faker.helpers.arrayElement(['unit', 'integration', 'e2e', 'smoke']))
.replace(':sha', faker.git.commitSha().substring(0, 7));
}
return faker.helpers.arrayElement([
`Test on ${faker.helpers.arrayElement(['Node', 'Python', 'Ruby'])} ${faker.system.semver()}`,
'Build Docker Image',
'Deploy to Staging',
'Run Integration Tests',
'Lint and Format',
'Security Scan',
'Performance Tests'
]);
}
generateJobState() {
return faker.helpers.arrayElement([
'PENDING', 'WAITING', 'BLOCKED', 'UNBLOCKED', 'LIMITING',
'LIMITED', 'SCHEDULED', 'ASSIGNED', 'ACCEPTED', 'RUNNING',
'FINISHED', 'CANCELING', 'CANCELED', 'TIMING_OUT',
'TIMED_OUT', 'SKIPPED', 'BROKEN'
]);
}
generateExitStatus() {
if (this.patterns?.jobs.exitStatusDistribution) {
return this.selectByDistribution(this.patterns.jobs.exitStatusDistribution);
}
const hasExitStatus = Math.random() < 0.95;
if (!hasExitStatus)
return null;
// Most jobs succeed (0), some fail (1), rare other codes
const random = Math.random();
if (random < 0.85)
return 0;
if (random < 0.95)
return 1;
return faker.helpers.arrayElement([2, 127, 128, 130, 137]);
}
generateParallelGroupIndex() {
const hasParallel = Math.random() < 0.2;
if (!hasParallel)
return null;
return faker.number.int({ min: 0, max: 9 });
}
generateParallelGroupTotal() {
const hasParallel = Math.random() < 0.2;
if (!hasParallel)
return null;
return faker.number.int({ min: 2, max: 10 });
}
generateAgent() {
return {
id: faker.string.uuid(),
uuid: faker.string.uuid(),
name: faker.helpers.arrayElement([
`agent-${faker.word.noun()}-${faker.number.int({ min: 1, max: 99 })}`,
`buildkite-agent-${faker.string.alphanumeric(8)}`,
`${faker.helpers.arrayElement(['linux', 'macos', 'windows'])}-${faker.number.int({ min: 1, max: 20 })}`
]),
hostname: faker.internet.domainName(),
version: faker.system.semver(),
isRunningJob: faker.datatype.boolean({ probability: 0.3 })
};
}
generateOrganizationSlug() {
const format = this.patterns?.organizations.slugFormats
? this.selectByDistribution(this.patterns.organizations.slugFormats)
: 'kebab-case';
const name = faker.company.name().toLowerCase();
switch (format) {
case 'kebab-case':
return name.replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
case 'snake_case':
return name.replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
default:
return name.replace(/[^a-z0-9]/g, '');
}
}
generateOrganizationName() {
const length = this.patterns?.organizations.nameLength?.average || 15;
const variance = 10;
const targetLength = Math.max(3, Math.floor(length + (Math.random() - 0.5) * variance));
let name = faker.company.name();
while (name.length < targetLength) {
name += ' ' + faker.company.buzzNoun();
}
return name.substring(0, targetLength);
}
generatePipelineRef() {
return {
id: faker.string.uuid(),
slug: this.generatePipelineSlug(),
name: this.generatePipelineName()
};
}
generateOrganizationRef() {
return {
id: faker.string.uuid(),
slug: this.generateOrganizationSlug(),
name: this.generateOrganizationName()
};
}
generateAnnotationContext() {
return faker.helpers.arrayElement([
'test-results',
'coverage',
'lint',
'security',
'performance',
'deployment',
'release-notes'
]);
}
generateAnnotationStyle() {
return faker.helpers.arrayElement(['info', 'warning', 'error', 'success']);
}
generateAnnotationBody() {
const useMarkdown = Math.random() < 0.6;
if (useMarkdown) {
const style = faker.helpers.arrayElement(['summary', 'detailed', 'table']);
switch (style) {
case 'summary':
return `<h3>${faker.lorem.sentence()}</h3>
<p>${faker.lorem.paragraph()}</p>
<ul>
<li>✅ ${faker.lorem.sentence()}</li>
<li>⚠️ ${faker.lorem.sentence()}</li>
<li>❌ ${faker.lorem.sentence()}</li>
</ul>`;
case 'detailed':
return `<details>
<summary>${faker.lorem.sentence()}</summary>
<p>${faker.lorem.paragraph()}</p>
<pre><code>${faker.hacker.phrase()}</code></pre>
</details>`;
case 'table':
return `<table>
<thead>
<tr>
<th>Test</th>
<th>Status</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>${faker.hacker.noun()}</td>
<td>✅ Passed</td>
<td>${faker.number.float({ min: 0.1, max: 10, multipleOf: 0.1 })}s</td>
</tr>
<tr>
<td>${faker.hacker.noun()}</td>
<td>❌ Failed</td>
<td>${faker.number.float({ min: 0.1, max: 10, multipleOf: 0.1 })}s</td>
</tr>
</tbody>
</table>`;
default:
return `<p>${faker.lorem.paragraph()}</p>`;
}
}
return `<p>${faker.lorem.paragraph()}</p>`;
}
}
//# sourceMappingURL=PatternMockGenerator.js.map