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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
dist/*
projects/cps-ui-kit/src/lib/primeng-temp/*
projects/cps-ui-kit/src/lib/primeuix-temp/*
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ module.exports = {
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true
}
]
},
env: {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check_pr_title_cc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Check PR Title (Conventional Commits)
on:
pull_request:
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
branches: [master]
branches: [master, next-major]

jobs:
check-pr-title:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/cps-shared-ui-checkers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x, 24.x]
node-version: [22.x, 24.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down Expand Up @@ -192,9 +192,9 @@ jobs:
id: generate-api
run: |
npm run generate-json-api
if [[ -n "$(git status --porcelain projects/composition/src/assets/api-data/)" ]]; then
if [[ -n "$(git status --porcelain projects/composition/src/app/api-data/)" ]]; then
echo "API data has changed. Please commit the updated API data.";
git --no-pager diff projects/composition/src/assets/api-data/
git --no-pager diff projects/composition/src/app/api-data/
exit 1;
else
echo "No changes in API data.";
Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/*
projects/cps-ui-kit/src/lib/primeng-temp/*
projects/cps-ui-kit/src/lib/primeuix-temp/*
15 changes: 15 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
This project includes vendored source code from PrimeNG
(https://github.com/primefaces/primeng), version 21.1.9, licensed under the
MIT License, Copyright (c) 2016-2026 PrimeTek.

See projects/cps-ui-kit/src/lib/primeng-temp/NOTICE.md for the full license
text, the list of modifications made to the vendored code, and per-file
provenance.

This project also includes vendored source code from primeuix
(https://github.com/primefaces/primeuix), licensed under the MIT License,
Copyright (c) 2025 PrimeTek.

See projects/cps-ui-kit/src/lib/primeuix-temp/NOTICE.md for the full license
text, the list of modifications made to the vendored code, and per-file
provenance.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,7 @@ The summary variants display:
- Top 10 components with the most issues

Both `test:pa11y` and `test:pa11y:ci` fail (non-zero exit code) if any accessibility error is found on any page.

#### Third-party notices

`cps-ui-kit` vendors source code from [PrimeNG](https://github.com/primefaces/primeng) and [primeuix](https://github.com/primefaces/primeuix) (both MIT License) rather than depending on them as npm packages. See [NOTICE](./NOTICE) for details.
116 changes: 84 additions & 32 deletions api-generator/api-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ const outputPath = path.resolve(
'projects/composition/src/app/api-data/'
);

// Services aren't always documented on their own dedicated page — some are
// only ever embedded into a different component's page via the `[services]`
// input (e.g. CpsCronValidationService is only ever shown on /scheduler/api).
// Linking to `/<service-name>/api` in that case would 404, so only treat a
// service name as linkable if a matching top-level route actually exists.
const getKnownRoutes = () => {
const routingFile = path.resolve(
rootDir,
'projects/composition/src/app/app-routing.module.ts'
);
try {
const content = fs.readFileSync(routingFile, 'utf8');
const matcherRoutes = [
...content.matchAll(/pathMatcher\(\s*'([^']+)'\s*\)/g)
].map((m) => m[1]);
const plainRoutes = [...content.matchAll(/path:\s*'([^']+)'/g)]
.map((m) => m[1])
.filter((p) => p !== '**');
return new Set([...matcherRoutes, ...plainRoutes]);
} catch (_) {
return new Set();
}
};
const knownRoutes = getKnownRoutes();

const staticMessages = {
methods: "Defines methods that can be accessed by the component's reference.",
emits:
Expand All @@ -18,7 +43,8 @@ const staticMessages = {
props: 'Defines the input properties of the component.',
service: 'Defines the service used by the component.',
enums: 'Defines enums used by the component or service.',
classes: 'Defines classes exposed by the component or service.'
classes: 'Defines classes exposed by the component or service.',
tokens: 'Injection tokens exposed by the component or service.'
};

async function main() {
Expand Down Expand Up @@ -191,6 +217,7 @@ async function main() {
comment &&
comment.summary.map((s) => s.text || '').join(' ')
};
typesMap[componentName] = name.replace('cps-', '');

const component_props_group = component.groups.find(
(g) => g.title === 'Props'
Expand Down Expand Up @@ -469,6 +496,10 @@ async function main() {
service.comment &&
service.comment.summary.map((s) => s.text || '').join(' ')
};
const serviceSlug = name.replace('cps-', '');
if (knownRoutes.has(serviceSlug)) {
typesMap[service.name] = serviceSlug;
}
const service_methods_group = service.groups.find(
(g) => g.title === 'Method'
);
Expand Down Expand Up @@ -510,7 +541,7 @@ async function main() {

if (isProcessable(module_tokens_group)) {
const tokens = {
description: 'Injection tokens exposed by the service.',
description: staticMessages.tokens,
values: []
};

Expand Down Expand Up @@ -857,40 +888,61 @@ const allowed = (name) => {
);
};

// Handle `typeof SomeArray[number]` — parse the source file and expand string literals to a union.
// Returns null if `t` isn't that shape, or the source array couldn't be resolved.
const expandIndexedAccessArray = (t, project) => {
if (
t?.type !== 'indexedAccess' ||
t.objectType?.type !== 'query' ||
t.indexType?.type !== 'intrinsic' ||
t.indexType?.name !== 'number'
) {
return null;
}

const refName = t.objectType.queryType?.name;
const variable = refName
? project
?.getReflectionsByKind(TypeDoc.ReflectionKind.Variable)
?.find((r) => r.name === refName)
: null;
const sourceFile = variable?.sources?.[0]?.fullFileName;
if (!sourceFile) return null;

try {
const src = fs.readFileSync(sourceFile, 'utf-8');
const arrayMatch = src.match(
new RegExp(
`(?:export\\s+)?const\\s+${refName}\\s*=\\s*\\[([\\s\\S]*?)\\]`,
'm'
)
);
if (arrayMatch) {
const items = [...arrayMatch[1].matchAll(/'([^']+)'/g)].map(
(m) => `'${m[1]}'`
);
if (items.length) return items.join(' | ');
}
} catch (_) {}

return null;
};

const getTypesValue = (typeobj, project) => {
const { type, children, indexSignature } = typeobj ?? {};

// 1) Handle `typeof SomeArray[number]` — parse the source file and expand string literals to a union.
if (
type?.type === 'indexedAccess' &&
type.objectType?.type === 'query' &&
type.indexType?.type === 'intrinsic' &&
type.indexType?.name === 'number'
// 1) Handle `typeof SomeArray[number]`, whether it's the whole type or a member of a
// top-level union (e.g. `typeof SomeArray[number] | ''`) — expand just that member.
if (type?.type === 'indexedAccess') {
const expanded = expandIndexedAccessArray(type, project);
if (expanded) return expanded;
} else if (
type?.type === 'union' &&
type.types?.some((member) => member.type === 'indexedAccess')
) {
const refName = type.objectType.queryType?.name;
const variable = refName
? project
?.getReflectionsByKind(TypeDoc.ReflectionKind.Variable)
?.find((r) => r.name === refName)
: null;
const sourceFile = variable?.sources?.[0]?.fullFileName;
if (sourceFile) {
try {
const src = fs.readFileSync(sourceFile, 'utf-8');
const arrayMatch = src.match(
new RegExp(
`(?:export\\s+)?const\\s+${refName}\\s*=\\s*\\[([\\s\\S]*?)\\]`,
'm'
)
);
if (arrayMatch) {
const items = [...arrayMatch[1].matchAll(/'([^']+)'/g)].map(
(m) => `'${m[1]}'`
);
if (items.length) return items.join(' | ');
}
} catch (_) {}
}
return type.types
.map((member) => expandIndexedAccessArray(member, project) ?? `${member}`)
.join(' | ');
}

// 2) Handle index signatures (e.g., { [key: string]: number })
Expand Down
9 changes: 7 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ const collectCoverageFrom =
'!projects/**/*.spec.ts',
'!projects/**/testing/**',
'!projects/**/public-api.ts',
'!projects/cps-ui-kit/src/lib/components/cps-scheduler/cps-scheduler.utils.ts'
'!projects/cps-ui-kit/src/lib/components/cps-scheduler/cps-scheduler.utils.ts',
'!projects/cps-ui-kit/src/lib/primeng-temp/**',
'!projects/cps-ui-kit/src/lib/primeuix-temp/**'
]
: [
'projects/cps-ui-kit/src/**/*.ts',
'projects/composition/src/**/*.ts',
'!projects/**/node_modules/**',
'!projects/**/*.spec.ts',
'!projects/**/testing/**',
'!projects/**/public-api.ts'
'!projects/**/public-api.ts',
'!projects/cps-ui-kit/src/lib/primeng-temp/**',
'!projects/cps-ui-kit/src/lib/primeuix-temp/**'
];

const coverageThreshold =
Expand Down Expand Up @@ -44,6 +48,7 @@ const coverageThreshold =

module.exports = {
roots: ['<rootDir>/projects'],
coverageDirectory: '<rootDir>/coverage',
preset: 'jest-preset-angular',
moduleNameMapper: {
'^lodash-es$': 'lodash',
Expand Down
Loading
Loading