Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sixty-cases-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@db-ux/agent-cli": patch
---

chore: generate AmazonQ rule file with @db-ux/agent-cli
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@ showcases/patternhub/public/iframe-resizer/*
/output/**/docs/
/output/**/agent/
/packages/agent-cli/test/.github/copilot-instructions.md
/packages/agent-cli/test/.amazonq/rules/db-ux.md
/core-web.iml
/build-storybooks/
3 changes: 2 additions & 1 deletion packages/agent-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"copy-build:package.json": "cpr package.json ../../build-outputs/agent-cli/package.json --overwrite",
"copy-build:readme": "cpr README.md ../../build-outputs/agent-cli/README.md --overwrite",
"test": "vitest run --config vitest.config.ts",
"test:cli": "tsx src/cli.ts --help"
"test:cli": "tsx src/cli.ts --help",
"test:update": "vitest run --config vitest.config.ts --update"
},
"dependencies": {
"commander": "14.0.1"
Expand Down
48 changes: 48 additions & 0 deletions packages/agent-cli/src/amazonq/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import fs from 'node:fs';
import path from 'node:path';
import { getInstructions } from '../utils';

export const generateAmazonQ = (rootPath: string) => {
const outputFolder = path.resolve(rootPath, '.amazonq', 'rules');

if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder, { recursive: true });
}

const amazonqInstructionsPath = path.join(outputFolder, 'db-ux.md');
if (!fs.existsSync(amazonqInstructionsPath)) {
fs.writeFileSync(amazonqInstructionsPath, '');
}

const amazonqInstructionsContent = getInstructions(rootPath);

if (amazonqInstructionsContent) {
let amazonqFileContent = fs.readFileSync(
amazonqInstructionsPath,
'utf8'
);
const startMarker =
'--- START: DB UX Amazon Q Instructions – do not edit below ---';
const endMarker =
'--- END: DB UX Amazon Q Instructions – do not edit above ---';
const startIndex = amazonqFileContent.indexOf(startMarker);
const endIndex = amazonqFileContent.indexOf(endMarker);
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
amazonqFileContent = (
amazonqFileContent.slice(0, startIndex) +
amazonqFileContent.slice(endIndex + endMarker.length)
).trim();
}

amazonqFileContent += `
${startMarker}

> **NOTE**: Always use "DB UX" components and variables as project's conventions
${amazonqInstructionsContent}

${endMarker}
`;

fs.writeFileSync(amazonqInstructionsPath, amazonqFileContent);
}
};
6 changes: 1 addition & 5 deletions packages/agent-cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
#!/usr/bin/env node

import { program } from 'commander';
import { generateCopilot } from './copilot';

const action = async (rootPath: string = '.') => {
generateCopilot(rootPath);
};
import { action } from './index';

const startProgram = (
name: string,
Expand Down
96 changes: 11 additions & 85 deletions packages/agent-cli/src/copilot/index.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,12 @@
import fs from 'node:fs';
import path from 'node:path';

function findAllNodeModulesDirectories(
directory: string,
found: string[] = []
): string[] {
if (!fs.existsSync(directory)) {
return found;
}

const entries = fs
.readdirSync(directory, { withFileTypes: true })
.sort((a, b) => a.name.localeCompare(b.name, 'en'));
for (const entry of entries) {
if (entry.isDirectory()) {
if (entry.name === 'node_modules') {
found.push(path.join(directory, entry.name));
} else if (!entry.name.startsWith('.')) {
findAllNodeModulesDirectories(
path.join(directory, entry.name),
found
);
}
}
}

return found;
}
import { getInstructions } from '../utils';

export const generateCopilot = (rootPath: string) => {
const outputFolder = path.resolve(rootPath, '.github');

const nodeModulesDirectories = findAllNodeModulesDirectories(rootPath);
if (nodeModulesDirectories.length === 0) {
console.error('No node_modules folders found.');
return;
}

let copilotInstructionsContent = '';

for (const nodeModulesPath of nodeModulesDirectories) {
const databaseUxPaths = [
path.join(nodeModulesPath, '@db-ux/'),
path.join(nodeModulesPath, '@db-ux-inner-source/')
];

for (const databaseUxPath of databaseUxPaths) {
if (!fs.existsSync(databaseUxPath)) {
continue;
}

const packages = fs.readdirSync(databaseUxPath, {
withFileTypes: true
});
for (const package_ of packages) {
if (package_.isDirectory()) {
const instructionsPath = path.join(
databaseUxPath,
package_.name,
'agent',
'_instructions.md'
);
if (fs.existsSync(instructionsPath)) {
let content = fs.readFileSync(instructionsPath, 'utf8');
const relativePath = path.relative(
rootPath,
path.join(databaseUxPath, package_.name)
);
content = content
.replaceAll(
'__agent-path__',
relativePath.replaceAll('\\', '/')
)
.replaceAll(
'**agent-path**',
relativePath.replaceAll('\\', '/')
);
copilotInstructionsContent += `\n# ${path.basename(databaseUxPath)}/${package_.name}\n${content}\n`;
}
}
}
}
}

if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder);
fs.mkdirSync(outputFolder, { recursive: true });
}

const copilotInstructionsPath = path.join(
Expand All @@ -95,7 +17,9 @@ export const generateCopilot = (rootPath: string) => {
fs.writeFileSync(copilotInstructionsPath, '');
}

if (copilotInstructionsContent.trim()) {
const copilotInstructionsContent = getInstructions(rootPath);

if (copilotInstructionsContent) {
let copilotFileContent = fs.readFileSync(
copilotInstructionsPath,
'utf8'
Expand All @@ -114,10 +38,12 @@ export const generateCopilot = (rootPath: string) => {
}

copilotFileContent += `
${startMarker}
> **NOTE**: Always use "DB UX" components and variables as project's conventions
${copilotInstructionsContent}
${endMarker}
${startMarker}

> **NOTE**: Always use "DB UX" components and variables as project's conventions
${copilotInstructionsContent}

${endMarker}
`;

fs.writeFileSync(copilotInstructionsPath, copilotFileContent);
Expand Down
23 changes: 23 additions & 0 deletions packages/agent-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from 'node:fs';
import path from 'node:path';
import { generateAmazonQ } from './amazonq';
import { generateCopilot } from './copilot';

export const action = async (rootPath: string = '.') => {
const hasCopilot = fs.existsSync(
path.join(rootPath, '.github', 'copilot-instructions.md')
);
const hasAmazonQ = fs.existsSync(path.join(rootPath, '.amazonq', 'rules'));

if (!hasCopilot && !hasAmazonQ) {
generateCopilot(rootPath);
generateAmazonQ(rootPath);
} else if (hasCopilot && hasAmazonQ) {
generateCopilot(rootPath);
generateAmazonQ(rootPath);
} else if (hasCopilot) {
generateCopilot(rootPath);
} else {
generateAmazonQ(rootPath);
}
};
86 changes: 86 additions & 0 deletions packages/agent-cli/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import fs from 'node:fs';
import path from 'node:path';

function findAllNodeModulesDirectories(
directory: string,
found: string[] = []
): string[] {
if (!fs.existsSync(directory)) {
return found;
}

const entries = fs
.readdirSync(directory, { withFileTypes: true })
.sort((a, b) => a.name.localeCompare(b.name, 'en'));
for (const entry of entries) {
if (entry.isDirectory()) {
if (entry.name === 'node_modules') {
found.push(path.join(directory, entry.name));
} else if (!entry.name.startsWith('.')) {
findAllNodeModulesDirectories(
path.join(directory, entry.name),
found
);
}
}
}

return found;
}

export const getInstructions = (rootPath: string): string => {
let copilotInstructionsContent = '';
const nodeModulesDirectories = findAllNodeModulesDirectories(rootPath);
if (nodeModulesDirectories.length === 0) {
console.error('No node_modules folders found.');
return '';
}

for (const nodeModulesPath of nodeModulesDirectories) {
const databaseUxPaths = [
path.join(nodeModulesPath, '@db-ux/'),
path.join(nodeModulesPath, '@db-ux-inner-source/')
];

for (const databaseUxPath of databaseUxPaths) {
if (!fs.existsSync(databaseUxPath)) {
continue;
}

const packages = fs.readdirSync(databaseUxPath, {
withFileTypes: true
});
for (const package_ of packages) {
if (package_.isDirectory()) {
const instructionsPath = path.join(
databaseUxPath,
package_.name,
'agent',
'_instructions.md'
);
if (fs.existsSync(instructionsPath)) {
let content = fs.readFileSync(instructionsPath, 'utf8');
const relativePath = path.relative(
rootPath,
path.join(databaseUxPath, package_.name)
);
content = content
.replaceAll(
'__agent-path__',
relativePath.replaceAll('\\', '/')
)
.replaceAll(
'**agent-path**',
relativePath.replaceAll('\\', '/')
);
copilotInstructionsContent += `\n# ${path.basename(databaseUxPath)}/${package_.name}\n${content}\n`;
}
}
}
}
}

copilotInstructionsContent = copilotInstructionsContent.trim();

return copilotInstructionsContent;
};
25 changes: 20 additions & 5 deletions packages/agent-cli/test/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,32 @@

exports[`default > check if docs are created 1`] = `
"
--- START: DB UX Copilot Instructions – do not edit below ---
> **NOTE**: Always use "DB UX" components and variables as project's conventions
--- START: DB UX Copilot Instructions – do not edit below ---

> **NOTE**: Always use "DB UX" components and variables as project's conventions
# @db-ux/components
- use y for frontend/node_modules/@db-ux/components/test.md


# @db-ux/foundations
- use x for frontend/node_modules/@db-ux/foundations/test.md


--- END: DB UX Copilot Instructions – do not edit above ---
--- END: DB UX Copilot Instructions – do not edit above ---
"
`;

exports[`default > check if docs are created 2`] = `
"
--- START: DB UX Amazon Q Instructions – do not edit below ---

> **NOTE**: Always use "DB UX" components and variables as project's conventions
# @db-ux/components
- use y for frontend/node_modules/@db-ux/components/test.md


# @db-ux/foundations
- use x for frontend/node_modules/@db-ux/foundations/test.md

--- END: DB UX Amazon Q Instructions – do not edit above ---
"
`;
7 changes: 5 additions & 2 deletions packages/agent-cli/test/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, test } from 'vitest';
import { generateCopilot } from '../src/copilot';
import { action } from '../src';

describe('default', () => {
test('check if docs are created', async () => {
const copilotFile = path.resolve(
'test/.github/copilot-instructions.md'
);
generateCopilot('test');
const amazonqFile = path.resolve('test/.amazonq/rules/db-ux.md');

await action('test');

expect(fs.readFileSync(copilotFile).toString()).toMatchSnapshot();
expect(fs.readFileSync(amazonqFile).toString()).toMatchSnapshot();
});
});
15 changes: 15 additions & 0 deletions packages/components/agent/Best-Practise_Common-AI-Mistakes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Best Practise / Common AI mistakes

## `DBButton` or `db-button`

- available variants are `outlined`, `brand`, `filled`, `ghost`
- always use variant `outlined` as default
- use variant `brand` as CTA or primary action
- if `noText`/`no-text` property is used add a `DBTooltip` or `db-tooltip` inside the Button
- always add a `type` as property as best practise
- there is no `size=large` button

## `DBStack` or `db-stack`

- there is no property `gap="fix-md"`, available values are `small`, `medium`, etc.
- there is no property `direction="horizontal"`, available values are `row`, `column`, etc.
Loading