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
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
pnpm install --frozen-lockfile
# See the same step's comment in the `test` job above.
pnpm --filter carbon-components-ember exec playwright install chromium
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate

- name: Test stories
run: pnpm --filter carbon-components-ember test:storybook --shard=${{ matrix.shard }}/${{ strategy.job-total }}
Expand All @@ -73,7 +73,7 @@ jobs:
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate

- name: Build Storybook
run: pnpm build:storybook
Expand All @@ -91,7 +91,7 @@ jobs:
# Packing the addon writes its agent skill's references from this
# build; fail here, not at release, if the manifest stops fitting.
- name: Write the skill's component references
run: pnpm --filter carbon-components-ember create-skill-references
run: pnpm --filter carbon-components-ember generate:skill-references

# The deploy below publishes this build rather than building again.
- name: Upload the site
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate

- name: Lint
env:
Expand Down Expand Up @@ -110,7 +110,7 @@ jobs:
pnpm install --frozen-lockfile
# pnpm's side-effects cache can skip playwright-chromium's install script on a store cache hit, even though the browsers it downloads live outside the store and don't persist across runners.
pnpm --filter carbon-components-ember exec playwright install chromium
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate

- name: Run tests
run: pnpm run test
Expand Down Expand Up @@ -150,7 +150,7 @@ jobs:
pnpm install --no-frozen-lockfile
# See the same step's comment in the `test` job above.
pnpm --filter carbon-components-ember exec playwright install chromium
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate

- name: Run tests
run: pnpm test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate

- name: Build Storybook
run: pnpm build:storybook
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/templates/agent-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ component synced and push that update (see Phase 4, step 3).
- If a prop/story implies an icon-rendering slot, use the icon components from `carbon-components-ember/icons` (see AGENTS.md pitfall on icon registration below — the same registration step applies here)

3. **Export Component**
- Run `pnpm --filter carbon-components-ember create-autogenerated-files`;
- Run `pnpm --filter carbon-components-ember generate`;
it adds the component to the generated barrel, named after its file

4. **Create Tests**
Expand Down Expand Up @@ -195,7 +195,7 @@ Use this path only when the component genuinely doesn't belong as a standalone E
`{{COMPONENT_NAME_KEBAB}}/item.gts`. The barrel names each export after
its file, and this fails if two files would export the same name:
```bash
pnpm --filter carbon-components-ember create-autogenerated-files
pnpm --filter carbon-components-ember generate
```

### Phase 4: Documentation (Required)
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Signatures follow the [Ember guides](https://guides.emberjs.com/release/typescri
Template keywords such as `fn`, `on`, `eq`, and `element` resolve to a same-named JavaScript binding in scope. Alias such imports when using the template keyword: import Storybook's spy as `fn as spy`. Never import `fn` or `on` from Ember (`ember/no-legacy-helper-imports`).

### ❌ Pitfall 2: Naming a File Differently from Its Export
File names are kebab-case, and `pnpm create-autogenerated-files` names each export of the `carbon-components-ember/components`, `/ai-chat` and `/charts` barrels after its file: `text-input.gts` exports `TextInput`, and a top-level `ai-` or `ui-` file spells the acronym (`AILabel`, `UIShell`). It fails when two files would export the same name from one barrel. So name a file after its export in full: `toggletip/toggletip-button.gts`, not `toggletip/button.gts`. Two files sharing a basename (`tile/group.gts` and `radio-button/group.gts`) once made the production rollup build mis-name exports.
File names are kebab-case, and `pnpm generate` names each export of the `carbon-components-ember/components`, `/ai-chat` and `/charts` barrels after its file: `text-input.gts` exports `TextInput`, and a top-level `ai-` or `ui-` file spells the acronym (`AILabel`, `UIShell`). It fails when two files would export the same name from one barrel. So name a file after its export in full: `toggletip/toggletip-button.gts`, not `toggletip/button.gts`. Two files sharing a basename (`tile/group.gts` and `radio-button/group.gts`) once made the production rollup build mis-name exports.

### ❌ Pitfall 3: MutationObservers to inspect Child DOM Nodes
Fights the Glimmer render pipeline. Instead, use contextual components so children render with correct classes/attributes declaratively.
Expand Down Expand Up @@ -286,7 +286,7 @@ AI Chat's components live in `src/components/ai-chat/` and are exported from the
- [ ] Create `.gts` file in `carbon-components-ember/src/components/`
- [ ] Define TypeScript signature — fully typed, no `any`
- [ ] Use `cds--` prefix for CSS classes
- [ ] Export it: run `pnpm create-autogenerated-files` to regenerate public barrel exports
- [ ] Export it: run `pnpm generate` to regenerate public barrel exports
- [ ] Match React's props: `pnpm --filter dom-parity args --component <Name>`
- [ ] When the PR closes a `[Parity Check]` issue, run `node scripts/parity-check.mjs --mark-synced <Component>` (with `GITHUB_TOKEN`) and commit `.parity-check-data.json`, or the weekly check reopens it; a React directory with no same-named export needs `--exclude <Name> --reason "..."`
- [ ] Create test file in `tests/components/`
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
pnpm switches to it automatically (or run `corepack enable` once). Node is
pinned in `package.json#devEngines.runtime`: pnpm downloads that version and
runs every script with it, whatever Node you have installed.
- `pnpm --filter carbon-components-ember create-autogenerated-files` – generates the icon components and index files
- `pnpm --filter carbon-components-ember generate` – generates the icon components and the barrels

## Linting

Expand Down
4 changes: 2 additions & 2 deletions carbon-components-ember/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ dist/
dist-tests/
declarations/

# generated by `pnpm create-autogenerated-files`
# generated by `pnpm generate`
src/components/icons/
unpublished-development-types/carbon-icons.d.ts

# generated by `pnpm create-skill-references` from the Storybook build
# generated by `pnpm generate:skill-references` from the Storybook build
skills/carbon-components-ember/references/components.md
skills/carbon-components-ember/references/components/

Expand Down
2 changes: 1 addition & 1 deletion carbon-components-ember/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/declarations/
/storybook-static/

# generated by `pnpm create-autogenerated-files`
# generated by `pnpm generate`
/src/components/icons/
/src/components.ts
/src/ai-chat.ts
Expand Down
2 changes: 1 addition & 1 deletion carbon-components-ember/.storybook/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { readFileSync } from 'node:fs';

import type { Manifests } from 'storybook/internal/types';

// Written by `pnpm create-autogenerated-files`.
// Written by `pnpm generate`.
const BARRELS = {
components: '../src/components.ts',
'ai-chat': '../src/ai-chat.ts',
Expand Down
2 changes: 1 addition & 1 deletion carbon-components-ember/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default defineConfig([
'coverage/',
'storybook-static/',
'!**/.*',
// generated by `pnpm create-autogenerated-files`
// generated by `pnpm generate`
'src/components/icons/',
]),
js.configs.recommended,
Expand Down
10 changes: 6 additions & 4 deletions carbon-components-ember/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@
],
"scripts": {
"build": "rollup --config",
"build:skill": "storybook build --quiet && pnpm create-skill-references",
"build:skill": "storybook build --quiet && pnpm generate:skill-references",
"build:watch": "rollup --config --watch --no-watch.clearScreen",
"create-autogenerated-files": "node ./scripts/create-files.mjs",
"create-skill-references": "node ./scripts/create-skill-references.mjs",
"generate": "pnpm generate:icons && pnpm generate:barrels",
"generate:barrels": "node ./scripts/barrels.mjs",
"generate:icons": "node ./scripts/icons.mjs",
"generate:skill-references": "node ./scripts/skill-references.mjs",
"format": "prettier . --cache --write",
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto",
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format",
Expand All @@ -50,7 +52,7 @@
"lint:js:fix": "eslint . --fix",
"lint:publish": "pnpm build && publint run --level error",
"lint:types": "ember-tsc --noEmit",
"prepack": "pnpm create-autogenerated-files && rollup --config && pnpm build:skill",
"prepack": "pnpm generate && rollup --config && pnpm build:skill",
"start": "vite dev",
"test": "vite build --mode=development --out-dir dist-tests && testem --file testem.cjs ci --port 0",
"test:update-snapshots": "vite build --mode=development --out-dir dist-tests && UPDATE_SNAPSHOTS=true testem --file testem.cjs ci --port 0",
Expand Down
101 changes: 101 additions & 0 deletions carbon-components-ember/scripts/barrels.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Writes the barrels: src/components.ts, src/ai-chat.ts, src/charts.ts and
* src/icons.ts, which apps import as `carbon-components-ember/components`,
* `/ai-chat`, `/charts` and `/icons` (dist/*.js, via the package.json `./*`
* export). Run it after icons.mjs, which writes the icon components;
* `pnpm generate` runs both.
*/

import fs from 'node:fs';
import path from 'node:path';

function createIndexFiles() {
// Sorted, so the generated barrels are the same on every OS.
const files = fs.readdirSync('./src', { recursive: true }).sort();
const groups = {
components: [],
aiChat: [],
charts: [],
icons: [],
};
for (const addonFilename of files) {
if (!addonFilename.includes('.')) continue;
if (addonFilename.includes('.gitkeep')) continue;
if (/\.stories\.[gt]?[jt]s$|\.mdx$/.test(addonFilename)) continue;
if (addonFilename.includes('render-svg-part.ts')) continue;
if (
addonFilename.startsWith('components/icons') &&
!addonFilename.includes('/-')
) {
groups.icons.push(addonFilename);
continue;
}
if (
addonFilename.startsWith('components/') &&
!addonFilename.includes('/-')
) {
// AI Chat and the charts have their own barrels, as upstream's are
// packages of their own (`@carbon/ai-chat-components`,
// `@carbon/charts-react`), so AI Chat keeps upstream's names (`Card`,
// `CodeSnippet`).
const group = addonFilename.startsWith('components/ai-chat/')
? groups.aiChat
: addonFilename.startsWith('components/charts/')
? groups.charts
: groups.components;
group.push(addonFilename);
}
}
if (groups.icons.length === 0) {
throw new Error(
'src/components/icons/ has no icon components; run scripts/icons.mjs first',
);
}
writeBarrel(groups.components, './src/components.ts');
writeBarrel(groups.aiChat, './src/ai-chat.ts');
writeBarrel(groups.charts, './src/charts.ts');
writeBarrel(groups.icons, './src/icons.ts');
}

/**
* Writes a barrel exporting each component under its file name in
* PascalCase (`text-input.gts` is `TextInput`), so name the file after the
* export. A top-level `ai-` or `ui-` file spells the acronym as Carbon React
* does (`AILabel`, `UIShell`).
*/
function writeBarrel(components, file) {
const lines = [];
const exportedFrom = new Map();
for (const comp of components) {
const camelCased = path
.basename(comp)
.replace(/\..*$/, '')
.split('-')
.map((word, i) =>
i === 0 && /^(ai|ui)$/.test(word) && path.dirname(comp) === 'components'
? word.toUpperCase()
: word[0].toUpperCase() + word.slice(1),
)
.join('');
if (exportedFrom.has(camelCased)) {
throw new Error(
`${comp} and ${exportedFrom.get(camelCased)} would both export ${camelCased} from ${file}; rename one`,
);
}
exportedFrom.set(camelCased, comp);
const relativePath = comp.replace('components/', '');
const source = fs
.readFileSync(path.join('./src/components', relativePath))
.toString();
const namedExports = [
...source.matchAll(/^export (?:class|const|function) ([A-Za-z0-9_]+)/gm),
].map((m) => m[1]);
const specifiers = [`default as ${camelCased}`, ...namedExports];
lines.push(
`export { ${specifiers.join(', ')} } from './components/${relativePath}'`,
);
}
fs.writeFileSync(file, lines.join('\n'));
}

createIndexFiles();
Loading
Loading