@starci/hfs 2.0.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +33 -33
- package/bin/hfs.mjs +94 -68
- package/lint/run.mjs +161 -0
- package/package.json +3 -2
- package/report/sonar.mjs +14 -29
- package/runtime/engine/admission.mjs +3 -3
- package/runtime/engine/ledger-db.mjs +2 -2
- package/runtime/engine/machine-db.mjs +90 -9
- package/runtime/engine/migrations/machine/0002-worktrees-no-workflow-kind.sql +13 -0
- package/runtime/engine/migrations/runtime/0005-ended-workflow-views.sql +93 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +32 -13
- package/runtime/knowledge/hfs/peer-integrations.yaml +18 -0
- package/runtime/knowledge/hfs/slots.yaml +193 -128
- package/runtime/knowledge/patterns/fe/folder.yaml +36 -36
- package/runtime/knowledge/sonar-gate.yaml +8 -7
- package/runtime/modules/kernel/failure-codes.yaml +31 -52
- package/runtime/scripts/checks/architecture/backend.mjs +1 -1
- package/runtime/scripts/checks/architecture/config.mjs +31 -11
- package/runtime/scripts/checks/architecture/contracts.mjs +4 -4
- package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +7 -3
- package/runtime/scripts/checks/architecture/framework-pinned.mjs +5 -47
- package/runtime/scripts/checks/architecture/frontend.mjs +6 -4
- package/runtime/scripts/checks/architecture/hfs-graph.mjs +1 -1
- package/runtime/scripts/checks/architecture/hfs.mjs +105 -67
- package/runtime/scripts/checks/architecture/index.mjs +18 -13
- package/runtime/scripts/checks/architecture/next-data.mjs +3 -2
- package/runtime/scripts/checks/architecture/owners.mjs +12 -6
- package/runtime/scripts/checks/architecture/registration.mjs +1 -1
- package/runtime/scripts/checks/architecture/surface.mjs +91 -0
- package/runtime/scripts/checks/architecture/symbols.mjs +13 -2
- package/runtime/scripts/checks/architecture/test-world-files.mjs +83 -45
- package/runtime/scripts/checks/architecture/typescript.mjs +127 -29
- package/runtime/scripts/checks/typescript-programs.mjs +2 -2
- package/runtime/scripts/lib/hfs-check.mjs +160 -209
- package/runtime/scripts/lib/hfs-path-findings.mjs +95 -0
- package/runtime/scripts/lib/hfs-rules/contract.mjs +15 -42
- package/runtime/scripts/lib/hfs-rules/frontend.mjs +37 -36
- package/runtime/scripts/lib/hfs-rules/peer-integrations.mjs +44 -0
- package/runtime/scripts/lib/hfs-rules/pipeline.mjs +12 -13
- package/runtime/scripts/lib/hfs-rules/proof-commands.mjs +11 -8
- package/runtime/scripts/lib/hfs-rules/spec-placement.mjs +1 -3
- package/runtime/scripts/lib/hfs-slots.mjs +244 -61
- package/runtime/scripts/lib/hfs-view.mjs +9 -7
- package/runtime/scripts/lib/language.mjs +11 -1
- package/runtime/scripts/lib/safe-remove.mjs +95 -10
- package/scaffold/app.mjs +179 -0
- package/scaffold/service.mjs +26 -16
- package/sync/cli.mjs +1 -1
- package/sync/hygiene.mjs +11 -8
- package/sync/index.mjs +109 -111
- package/sync/managed.mjs +9 -8
- package/sync/sonar-key.mjs +20 -22
- package/templates/{be → app}/ci-workflows/github/workflows/ci.yml +5 -11
- package/templates/app/gitignore +6 -0
- package/templates/app/hooks/husky/pre-commit +25 -0
- package/templates/app/hooks/husky/pre-push +7 -0
- package/templates/app/package-scripts/package.json +22 -0
- package/templates/{be → app}/quality-config/sonar-project.properties +4 -3
- package/templates/app/skeleton/.editorconfig +15 -0
- package/templates/app/skeleton/.gitattributes +2 -0
- package/templates/app/skeleton/.nvmrc +1 -0
- package/templates/app/skeleton/.starciwork/features/index.yaml +7 -0
- package/templates/app/skeleton/.starciwork/workspace.yaml +9 -0
- package/templates/app/skeleton/README.md +36 -0
- package/templates/app/skeleton/scripts/codegen.mjs +4 -0
- package/templates/{fe → app}/tool-config/prettierignore +4 -1
- package/templates/be/skeleton/.sops.yaml +2 -0
- package/templates/be/skeleton/.starcistacks/application-stacks.yaml +10 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +27 -6
- package/templates/be/skeleton/apps/__app__/src/main.ts +4 -1
- package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +11 -0
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +25 -0
- package/templates/be/skeleton/src/modules/domain/identity/errors/identity.error.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +11 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +14 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/identity/index.ts +6 -0
- package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +8 -1
- package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +71 -23
- package/templates/be/skeleton/src/modules/platform/config/errors/config.error.ts +14 -16
- package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/config/server.config.ts +2 -12
- package/templates/be/skeleton/src/modules/platform/errors/domain.error.ts +16 -7
- package/templates/be/skeleton/src/modules/platform/errors/errors/errors.error.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.contracts.ts +33 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.decorators.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +32 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.log-events.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.options.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.service.spec.ts +94 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.service.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/errors/http-status.policy.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/errors/index.ts +4 -1
- package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +17 -0
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +31 -0
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +68 -0
- package/templates/be/skeleton/src/modules/platform/i18n/bundle-message-catalog.service.spec.ts +66 -0
- package/templates/be/skeleton/src/modules/platform/i18n/bundle-message-catalog.service.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.contracts.ts +18 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.decorators.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module.ts +24 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.options.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.port.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/i18n/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/i18n/request-locale.service.spec.ts +45 -0
- package/templates/be/skeleton/src/modules/platform/i18n/request-locale.service.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +85 -68
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +14 -10
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.port.ts +0 -1
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.contracts.ts +25 -0
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.ts +24 -0
- package/templates/fe/skeleton/apps/__app__/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/error.tsx +5 -17
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +11 -22
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +3 -12
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +6 -24
- package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +4 -18
- package/templates/fe/skeleton/apps/__app__/src/app/globals.css +5 -0
- package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +28 -0
- package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +35 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +21 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +17 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +26 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +25 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +27 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +32 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/config/index.ts +12 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +2 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +6 -0
- package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/request.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +4 -0
- package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/proxy.ts +1 -1
- package/runtime/scripts/checks/architecture/size-growth.mjs +0 -73
- package/sync/skeleton.mjs +0 -76
- package/templates/be/gitignore +0 -2
- package/templates/be/hooks/husky/pre-commit +0 -13
- package/templates/be/hooks/husky/pre-push +0 -7
- package/templates/be/package-scripts/package.json +0 -22
- package/templates/be/skeleton/scripts/.gitkeep +0 -0
- package/templates/be/skeleton/src/modules/platform/config/config.decorators.ts +0 -9
- package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +0 -20
- package/templates/be/tool-config/prettierignore +0 -8
- package/templates/fe/ci-workflows/github/workflows/ci.yml +0 -54
- package/templates/fe/gitignore +0 -3
- package/templates/fe/hooks/husky/pre-commit +0 -16
- package/templates/fe/hooks/husky/pre-push +0 -6
- package/templates/fe/package-scripts/package.json +0 -17
- package/templates/fe/parts/api-client.ts +0 -44
- package/templates/fe/parts/api-outcome.ts +0 -7
- package/templates/fe/quality-config/sonar-project.properties +0 -8
- package/templates/fe/skeleton/scripts/.gitkeep +0 -0
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/client.ts +0 -1
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/index.ts +0 -3
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/outcome.ts +0 -1
- package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/index.ts +0 -4
- package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/navigation.ts +0 -5
- package/templates/fe/skeleton-shared/apps/__app__/next.config.ts +0 -12
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/api/index.ts +0 -2
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/index.ts +0 -9
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/request.ts +0 -5
- package/templates/fe/skeleton-shared/apps/__app__/src/proxy.ts +0 -5
- package/templates/fe/skeleton-shared/packages/__family__-api/package.json +0 -12
- package/templates/fe/skeleton-shared/packages/__family__-api/src/client.ts +0 -1
- package/templates/fe/skeleton-shared/packages/__family__-api/src/index.ts +0 -3
- package/templates/fe/skeleton-shared/packages/__family__-api/src/outcome.ts +0 -1
- package/templates/fe/skeleton-shared/packages/__family__-api/tsconfig.json +0 -5
- package/templates/fe/skeleton-shared/packages/__family__-i18n/package.json +0 -18
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/app.ts +0 -19
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/index.ts +0 -2
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/proxy.ts +0 -12
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/request.ts +0 -15
- package/templates/fe/skeleton-shared/packages/__family__-i18n/tsconfig.json +0 -5
- package/templates/fe/tool-config/prettierrc +0 -1
- /package/templates/{be → app}/ci-workflows/github/workflows/e2e.yml +0 -0
- /package/templates/{be → app}/starciwork.gitignore +0 -0
- /package/templates/{be → app}/tool-config/prettierrc +0 -0
- /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/next.config.ts +0 -0
- /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/config.ts +0 -0
- /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/routing.ts +0 -0
|
@@ -1 +1,6 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
|
+
@import "@heroui/styles/css";
|
|
3
|
+
@import "@starci/grammar/common.css";
|
|
4
|
+
|
|
5
|
+
/* The grammar's renderers spell their layout in Tailwind utilities inside node_modules, which Tailwind never scans by default. */
|
|
6
|
+
@source "../../../../../node_modules/@starci/grammar/dist";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Button, GrammarRoot, Heading, PageContainer, WorkspaceShell } from "@starci/grammar/common"
|
|
2
|
+
|
|
3
|
+
/** Props for {@link FailureScreen}. */
|
|
4
|
+
type FailureScreenProps = {
|
|
5
|
+
/** What failed, said to the reader. */
|
|
6
|
+
readonly title: string
|
|
7
|
+
/** The label of the one recovery action. */
|
|
8
|
+
readonly retryLabel: string
|
|
9
|
+
/** Runs the recovery: the boundary re-renders what failed. */
|
|
10
|
+
readonly onRetry: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** A whole-screen failure with its one recovery action: the drawing both error boundaries share. */
|
|
14
|
+
export const FailureScreen = (props: FailureScreenProps) => (
|
|
15
|
+
<GrammarRoot>
|
|
16
|
+
<WorkspaceShell
|
|
17
|
+
primaryLabel={props.title}
|
|
18
|
+
primary={
|
|
19
|
+
<PageContainer measure="reading">
|
|
20
|
+
<Heading level={1}>{props.title}</Heading>
|
|
21
|
+
<Button variant="danger" onPress={props.onRetry}>
|
|
22
|
+
{props.retryLabel}
|
|
23
|
+
</Button>
|
|
24
|
+
</PageContainer>
|
|
25
|
+
}
|
|
26
|
+
/>
|
|
27
|
+
</GrammarRoot>
|
|
28
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import { notFound } from "next/navigation"
|
|
3
|
+
import { hasLocale, NextIntlClientProvider } from "next-intl"
|
|
4
|
+
import { getMessages, getTranslations, setRequestLocale } from "next-intl/server"
|
|
5
|
+
import type { ReactNode } from "react"
|
|
6
|
+
import { siteUrl } from "@/modules/config"
|
|
7
|
+
import { routing } from "@/modules/i18n"
|
|
8
|
+
|
|
9
|
+
type LocaleShellProps = {
|
|
10
|
+
readonly lang: string
|
|
11
|
+
readonly children: ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The document title and the absolute base of every metadata URL, for the language in the address. */
|
|
15
|
+
export const localeMetadata = async (lang: string): Promise<Metadata> => {
|
|
16
|
+
if (!hasLocale(routing.locales, lang)) notFound()
|
|
17
|
+
const t = await getTranslations({ locale: lang, namespace: "app" })
|
|
18
|
+
return { title: t("title"), metadataBase: new URL(siteUrl()) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The document of one route language: `<html lang>` from the address and the catalog handed to client components. */
|
|
22
|
+
export const LocaleShell = async (props: LocaleShellProps) => {
|
|
23
|
+
if (!hasLocale(routing.locales, props.lang)) notFound()
|
|
24
|
+
setRequestLocale(props.lang)
|
|
25
|
+
const messages = await getMessages()
|
|
26
|
+
return (
|
|
27
|
+
<html lang={props.lang}>
|
|
28
|
+
<body className="min-h-dvh">
|
|
29
|
+
<NextIntlClientProvider locale={props.lang} messages={messages}>
|
|
30
|
+
{props.children}
|
|
31
|
+
</NextIntlClientProvider>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FailureScreen } from "@/components/composites/FailureScreen"
|
|
2
|
+
|
|
3
|
+
/** Props for {@link ErrorPageBase}. */
|
|
4
|
+
export type ErrorPageBaseProps = {
|
|
5
|
+
/** Whole-screen situations this surface settles; the error page only ever shows the failure. */
|
|
6
|
+
readonly state: "failed"
|
|
7
|
+
/** The words the failure shows. */
|
|
8
|
+
readonly props: {
|
|
9
|
+
readonly title: string
|
|
10
|
+
readonly retryLabel: string
|
|
11
|
+
}
|
|
12
|
+
/** What the surface reports upward. */
|
|
13
|
+
readonly on: {
|
|
14
|
+
readonly retry: () => void
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Draw the failure of the locale segment with its one recovery action. */
|
|
19
|
+
export const ErrorPageBase = (props: ErrorPageBaseProps) => (
|
|
20
|
+
<FailureScreen title={props.props.title} retryLabel={props.props.retryLabel} onRetry={props.on.retry} />
|
|
21
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useTranslations } from "next-intl"
|
|
2
|
+
import { ErrorPageBase } from "./component"
|
|
3
|
+
|
|
4
|
+
/** The public props of the locale error page: the boundary hands it the retry. */
|
|
5
|
+
type ErrorPageProps = { readonly onRetry: () => void }
|
|
6
|
+
|
|
7
|
+
/** A render failure of the locale segment shows this instead of a blank page; retry re-renders the segment. */
|
|
8
|
+
export const ErrorPage = (props: ErrorPageProps) => {
|
|
9
|
+
const t = useTranslations("errors.page")
|
|
10
|
+
return (
|
|
11
|
+
<ErrorPageBase
|
|
12
|
+
state="failed"
|
|
13
|
+
props={{ title: t("title"), retryLabel: t("retry") }}
|
|
14
|
+
on={{ retry: props.onRetry }}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FailureScreen } from "@/components/composites/FailureScreen"
|
|
2
|
+
|
|
3
|
+
/** Props for {@link GlobalErrorPageBase}. */
|
|
4
|
+
export type GlobalErrorPageBaseProps = {
|
|
5
|
+
/** Whole-screen situations this surface settles; the global error page only ever shows the failure. */
|
|
6
|
+
readonly state: "failed"
|
|
7
|
+
/** The language of the document and the words the failure shows. */
|
|
8
|
+
readonly props: {
|
|
9
|
+
readonly lang: string
|
|
10
|
+
readonly title: string
|
|
11
|
+
readonly retryLabel: string
|
|
12
|
+
}
|
|
13
|
+
/** What the surface reports upward. */
|
|
14
|
+
readonly on: {
|
|
15
|
+
readonly retry: () => void
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Draw the whole document, because this boundary replaces the root layout that would have drawn it. */
|
|
20
|
+
export const GlobalErrorPageBase = (props: GlobalErrorPageBaseProps) => (
|
|
21
|
+
<html lang={props.props.lang}>
|
|
22
|
+
<body>
|
|
23
|
+
<FailureScreen title={props.props.title} retryLabel={props.props.retryLabel} onRetry={props.on.retry} />
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
26
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DEFAULT_LOCALE } from "@/modules/i18n"
|
|
2
|
+
import messages from "@/modules/i18n/messages/vi.json"
|
|
3
|
+
import { GlobalErrorPageBase } from "./component"
|
|
4
|
+
|
|
5
|
+
/** The public props of the last-resort error page: the boundary hands it the retry. */
|
|
6
|
+
type GlobalErrorPageProps = { readonly onRetry: () => void }
|
|
7
|
+
|
|
8
|
+
/** The last-resort boundary above the locale layout: it has no provider, so it reads the default catalog directly. */
|
|
9
|
+
export const GlobalErrorPage = (props: GlobalErrorPageProps) => (
|
|
10
|
+
<GlobalErrorPageBase
|
|
11
|
+
state="failed"
|
|
12
|
+
props={{ lang: DEFAULT_LOCALE, title: messages.errors.global.title, retryLabel: messages.errors.global.retry }}
|
|
13
|
+
on={{ retry: props.onRetry }}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GrammarRoot, Heading, PageContainer, WorkspaceShell } from "@starci/grammar/common"
|
|
2
|
+
|
|
3
|
+
/** Props for {@link HomePageBase}. */
|
|
4
|
+
export type HomePageBaseProps = {
|
|
5
|
+
/** Whole-screen situations this surface settles; the home page only ever shows its title. */
|
|
6
|
+
readonly state: "ready"
|
|
7
|
+
/** The words the page shows. */
|
|
8
|
+
readonly props: { readonly title: string }
|
|
9
|
+
/** What the surface reports upward; the page reports nothing. */
|
|
10
|
+
readonly on: Record<never, never>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Draw the front door of the app. */
|
|
14
|
+
export const HomePageBase = (props: HomePageBaseProps) => (
|
|
15
|
+
<GrammarRoot>
|
|
16
|
+
<WorkspaceShell
|
|
17
|
+
primaryLabel={props.props.title}
|
|
18
|
+
primary={
|
|
19
|
+
<PageContainer measure="reading">
|
|
20
|
+
<Heading level={1}>{props.props.title}</Heading>
|
|
21
|
+
</PageContainer>
|
|
22
|
+
}
|
|
23
|
+
/>
|
|
24
|
+
</GrammarRoot>
|
|
25
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import { getTranslations } from "next-intl/server"
|
|
3
|
+
import { HomePageBase } from "./component"
|
|
4
|
+
|
|
5
|
+
/** The document title of the home page, from the catalog of the requested locale. */
|
|
6
|
+
export const homeMetadata = async (): Promise<Metadata> => {
|
|
7
|
+
const t = await getTranslations("home")
|
|
8
|
+
return { title: t("title") }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The first page of the app: a server component, so no catalog is shipped for it. */
|
|
12
|
+
export const HomePage = async () => {
|
|
13
|
+
const t = await getTranslations("home")
|
|
14
|
+
return <HomePageBase state="ready" props={{ title: t("title") }} on={{}} />
|
|
15
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { GrammarRoot, PageContainer, Text, WorkspaceShell } from "@starci/grammar/common"
|
|
2
|
+
|
|
3
|
+
/** Props for {@link LoadingPageBase}. */
|
|
4
|
+
export type LoadingPageBaseProps = {
|
|
5
|
+
/** Whole-screen situations this surface settles; the loading page only ever waits. */
|
|
6
|
+
readonly state: "loading"
|
|
7
|
+
/** The words the wait announces. */
|
|
8
|
+
readonly props: { readonly message: string }
|
|
9
|
+
/** What the surface reports upward; a wait reports nothing. */
|
|
10
|
+
readonly on: Record<never, never>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Draw the wait as one politely announced line. */
|
|
14
|
+
export const LoadingPageBase = (props: LoadingPageBaseProps) => (
|
|
15
|
+
<GrammarRoot>
|
|
16
|
+
<WorkspaceShell
|
|
17
|
+
primaryLabel={props.props.message}
|
|
18
|
+
primary={
|
|
19
|
+
<PageContainer measure="reading">
|
|
20
|
+
<Text live="polite" tone="muted">
|
|
21
|
+
{props.props.message}
|
|
22
|
+
</Text>
|
|
23
|
+
</PageContainer>
|
|
24
|
+
}
|
|
25
|
+
/>
|
|
26
|
+
</GrammarRoot>
|
|
27
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getTranslations } from "next-intl/server"
|
|
2
|
+
import { LoadingPageBase } from "./component"
|
|
3
|
+
|
|
4
|
+
/** Shown while a route of the locale segment resolves. */
|
|
5
|
+
export const LoadingPage = async () => {
|
|
6
|
+
const t = await getTranslations("loading")
|
|
7
|
+
return <LoadingPageBase state="loading" props={{ message: t("message") }} on={{}} />
|
|
8
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GrammarRoot, Heading, PageContainer, TextAction, WorkspaceShell } from "@starci/grammar/common"
|
|
2
|
+
import { ROUTES } from "@/modules/routes"
|
|
3
|
+
|
|
4
|
+
/** Props for {@link NotFoundPageBase}. */
|
|
5
|
+
export type NotFoundPageBaseProps = {
|
|
6
|
+
/** Whole-screen situations this surface settles; the not-found page only ever says the page is missing. */
|
|
7
|
+
readonly state: "missing"
|
|
8
|
+
/** The words the page shows. */
|
|
9
|
+
readonly props: {
|
|
10
|
+
readonly title: string
|
|
11
|
+
readonly homeLabel: string
|
|
12
|
+
}
|
|
13
|
+
/** What the surface reports upward; the page reports nothing. */
|
|
14
|
+
readonly on: Record<never, never>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Draw the missing-page message and the way back to the front door. */
|
|
18
|
+
export const NotFoundPageBase = (props: NotFoundPageBaseProps) => (
|
|
19
|
+
<GrammarRoot>
|
|
20
|
+
<WorkspaceShell
|
|
21
|
+
primaryLabel={props.props.title}
|
|
22
|
+
primary={
|
|
23
|
+
<PageContainer measure="reading">
|
|
24
|
+
<Heading level={1}>{props.props.title}</Heading>
|
|
25
|
+
<TextAction appearance="inline" href={ROUTES.home}>
|
|
26
|
+
{props.props.homeLabel}
|
|
27
|
+
</TextAction>
|
|
28
|
+
</PageContainer>
|
|
29
|
+
}
|
|
30
|
+
/>
|
|
31
|
+
</GrammarRoot>
|
|
32
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getTranslations } from "next-intl/server"
|
|
2
|
+
import { NotFoundPageBase } from "./component"
|
|
3
|
+
|
|
4
|
+
/** Shown when a route calls `notFound()`. */
|
|
5
|
+
export const NotFoundPage = async () => {
|
|
6
|
+
const t = await getTranslations("notFound")
|
|
7
|
+
return <NotFoundPageBase state="missing" props={{ title: t("title"), homeLabel: t("home") }} on={{}} />
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only reader of `process.env` in this app. A value is read when it is asked for, not when the file loads, and a
|
|
3
|
+
* missing value stops the caller with a named error: there is no localhost fallback.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const required = (name: string, value: string | undefined): string => {
|
|
7
|
+
if (value === undefined || value === "") throw new Error(`${name} is not set`)
|
|
8
|
+
return value
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The public origin of the app, the base of every absolute metadata URL; throws when `NEXT_PUBLIC_SITE_URL` is not set. */
|
|
12
|
+
export const siteUrl = (): string => required("NEXT_PUBLIC_SITE_URL", process.env.NEXT_PUBLIC_SITE_URL)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createMiddleware from "next-intl/middleware"
|
|
2
|
-
import { routing } from "./modules/i18n
|
|
2
|
+
import { routing } from "./modules/i18n"
|
|
3
3
|
|
|
4
4
|
/** Negotiates the locale and redirects; the default locale (vi) is served without a prefix. */
|
|
5
5
|
export default createMiddleware(routing)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { gitOutput } from '../../lib/git.mjs';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* HFS check 6, file size growth (knowledge/hfs/slots.yaml ruleParams.<profile>.fileLines {soft, hardGrowth}):
|
|
6
|
-
* a production source file over `soft` lines may not grow against the merge-base, and a file that is new at the base
|
|
7
|
-
* must stay within `soft`. A file at or under `soft` passes, even when it grows, as long as it does not cross `soft`
|
|
8
|
-
* (crossing is growth of an over-budget file: the base had fewer lines than soft). Files are compared to the base file
|
|
9
|
-
* of the same path only, so a moved file is judged as new. The per-slot budgets (component 300, hook 200 ...) belong to
|
|
10
|
-
* the lint layer and are not judged here.
|
|
11
|
-
*/
|
|
12
|
-
export const SIZE_GROWTH_RULE_IDS = ['HFS_SIZE_GROWTH'];
|
|
13
|
-
|
|
14
|
-
const MAX_BUFFER = 64 * 1024 * 1024;
|
|
15
|
-
|
|
16
|
-
function git(root, args) {
|
|
17
|
-
try {
|
|
18
|
-
return gitOutput(args, { cwd: root, maxBuffer: MAX_BUFFER });
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** The commit the growth is measured against: the given commit-ish, else merge-base with upstream, origin/main, origin/master, else HEAD's first parent. */
|
|
25
|
-
function resolveBase(root, requested) {
|
|
26
|
-
const commit = (rev) => { const out = git(root, ['rev-parse', '--verify', '--quiet', `${rev}^{commit}`]); return out ? out.trim() : null; };
|
|
27
|
-
if (requested) return commit(requested);
|
|
28
|
-
if (!commit('HEAD')) return null;
|
|
29
|
-
for (const other of ['@{upstream}', 'origin/main', 'origin/master']) {
|
|
30
|
-
if (!commit(other)) continue;
|
|
31
|
-
const merged = git(root, ['merge-base', 'HEAD', other]);
|
|
32
|
-
if (merged?.trim()) return merged.trim();
|
|
33
|
-
}
|
|
34
|
-
return commit('HEAD^');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function countLines(text) {
|
|
38
|
-
if (!text) return 0;
|
|
39
|
-
const count = text.split('\n').length;
|
|
40
|
-
return text.endsWith('\n') ? count - 1 : count;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function checkSizeGrowth({ config, graph, base } = {}) {
|
|
44
|
-
const root = config.root;
|
|
45
|
-
const sha = resolveBase(root, base);
|
|
46
|
-
if (!sha) return { violations: [], coverage: { status: 'unavailable', reason: 'no merge-base' } };
|
|
47
|
-
const { soft, hardGrowth } = graph.resolver.ruleParams().fileLines;
|
|
48
|
-
const violations = [];
|
|
49
|
-
let files = 0;
|
|
50
|
-
let overSoft = 0;
|
|
51
|
-
let grown = 0;
|
|
52
|
-
let newOverSoft = 0;
|
|
53
|
-
for (const rel of [...graph.files.keys()].sort()) {
|
|
54
|
-
let text;
|
|
55
|
-
try { text = fs.readFileSync(graph.files.get(rel).abs, 'utf8'); } catch { continue; }
|
|
56
|
-
files += 1;
|
|
57
|
-
const lines = countLines(text);
|
|
58
|
-
if (lines <= soft) continue;
|
|
59
|
-
overSoft += 1;
|
|
60
|
-
const baseText = git(root, ['cat-file', 'blob', `${sha}:${rel}`]);
|
|
61
|
-
const baseLines = baseText === null ? null : countLines(baseText);
|
|
62
|
-
if (baseLines === null) {
|
|
63
|
-
newOverSoft += 1;
|
|
64
|
-
if (hardGrowth) violations.push({ ruleId: 'HFS_SIZE_GROWTH', path: rel, line: 1, lines, baseLines: null, soft,
|
|
65
|
-
message: `${rel} is a new file of ${lines} lines, over the ${soft}-line soft budget; split it into smaller files before adding it.` });
|
|
66
|
-
} else if (lines > baseLines) {
|
|
67
|
-
grown += 1;
|
|
68
|
-
if (hardGrowth) violations.push({ ruleId: 'HFS_SIZE_GROWTH', path: rel, line: 1, lines, baseLines, soft,
|
|
69
|
-
message: `${rel} has ${lines} lines, over the ${soft}-line soft budget, and grew from ${baseLines} lines at the base; a file over budget may only shrink, so split the new code into another file.` });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return { violations, coverage: { status: 'checked', base: sha, files, overSoft, grown, newOverSoft } };
|
|
73
|
-
}
|
package/sync/skeleton.mjs
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// hfs sync --init: the first source tree of a new repository (entrypoint, platform config/logging/errors, the health
|
|
2
|
-
// endpoint; for a front end the next-intl [locale] shell with vi default, as-needed prefix and proxy.ts).
|
|
3
|
-
// Unlike the managed files it is written once and never overwritten: an existing file is skipped, so re-running --init
|
|
4
|
-
// on a repository that already grew its own source changes nothing.
|
|
5
|
-
//
|
|
6
|
-
// A front end's skeleton is the templates/fe/skeleton tree (the app shell, common to every repository) plus ONE of two
|
|
7
|
-
// trees for what a repository writes once: with one app the app keeps its own i18n stack and its own API client
|
|
8
|
-
// (templates/fe/skeleton-app); with two or more apps the stack and the client are written once, as the packages
|
|
9
|
-
// `packages/<project>-i18n` and `packages/<project>-api`, and each app keeps a thin adapter over them
|
|
10
|
-
// (templates/fe/skeleton-shared). The choice is the number of apps in hfs.json, nothing else.
|
|
11
|
-
import fs from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import { TEMPLATES_DIR, SyncError, render, validateHfs } from './index.mjs';
|
|
14
|
-
|
|
15
|
-
const APP_DIR = '__app__';
|
|
16
|
-
const FAMILY_DIR = '__family__';
|
|
17
|
-
/** The slots a multi-app front end must opt into in hfs.json before its shared packages are written. */
|
|
18
|
-
export const SHARED_PACKAGE_SLOTS = Object.freeze(['fe.package.i18n', 'fe.package.api']);
|
|
19
|
-
const pascal = name => name.split('-').map(part => part[0].toUpperCase() + part.slice(1)).join('');
|
|
20
|
-
|
|
21
|
-
function listFiles(dir, base = dir) {
|
|
22
|
-
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
23
|
-
const full = path.join(dir, entry.name);
|
|
24
|
-
return entry.isDirectory() ? listFiles(full, base) : [path.relative(base, full).split(path.sep).join('/')];
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** The apps that get a skeleton: a back end's `api` apps, every front-end app. */
|
|
29
|
-
export const skeletonApps = hfs => hfs.apps.filter(app => hfs.profile === 'fe' || app.kind === 'api');
|
|
30
|
-
|
|
31
|
-
/** True when the repository writes its i18n stack and API client once, as packages: a front end with two or more apps. */
|
|
32
|
-
export const sharesPackages = hfs => hfs.profile === 'fe' && hfs.apps.length > 1;
|
|
33
|
-
|
|
34
|
-
/** The template directories of a repository, in order: the profile's skeleton, then (front end) the one-app or the shared-package tree. */
|
|
35
|
-
export const skeletonDirs = hfs => [path.join(TEMPLATES_DIR, hfs.profile, 'skeleton'), ...(hfs.profile === 'fe' ? [path.join(TEMPLATES_DIR, 'fe', sharesPackages(hfs) ? 'skeleton-shared' : 'skeleton-app')] : [])];
|
|
36
|
-
|
|
37
|
-
/** Every skeleton file for this repository: [{ path, content }], the shared ones once and the per-app ones per app. */
|
|
38
|
-
export function skeletonFiles(hfs, dirs = skeletonDirs(hfs)) {
|
|
39
|
-
validateHfs(hfs);
|
|
40
|
-
if (sharesPackages(hfs)) {
|
|
41
|
-
const missing = SHARED_PACKAGE_SLOTS.filter(id => !(hfs.optionalSlots ?? []).includes(id));
|
|
42
|
-
if (missing.length) throw new SyncError('HFS_SYNC_HFS_INVALID', `a front end with ${hfs.apps.length} apps writes its i18n stack and its API client once, as packages: list ${missing.join(' and ')} in hfs.json optionalSlots before hfs sync --init`);
|
|
43
|
-
}
|
|
44
|
-
const files = [];
|
|
45
|
-
for (const dir of dirs) {
|
|
46
|
-
if (!fs.existsSync(dir)) throw new SyncError('HFS_SYNC_SKELETON_MISSING', `no skeleton templates in ${path.relative(TEMPLATES_DIR, dir)}`);
|
|
47
|
-
for (const rel of listFiles(dir)) {
|
|
48
|
-
const source = fs.readFileSync(path.join(dir, rel), 'utf8').replace(/\r\n/g, '\n');
|
|
49
|
-
const once = { family: hfs.project, project: hfs.project };
|
|
50
|
-
if (!rel.includes(APP_DIR)) {
|
|
51
|
-
files.push({ path: rel.split(FAMILY_DIR).join(hfs.project), content: render(source, once) });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
for (const app of skeletonApps(hfs)) {
|
|
55
|
-
files.push({ path: rel.split(APP_DIR).join(app.name), content: render(source, { ...once, app: app.name, appPascal: pascal(app.name) }) });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return files;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Writes the skeleton under `root`, skipping every file that exists: { created, skipped } path lists. */
|
|
63
|
-
export function initSkeleton(root, hfs) {
|
|
64
|
-
const created = [], skipped = [];
|
|
65
|
-
for (const file of skeletonFiles(hfs)) {
|
|
66
|
-
const target = path.join(root, file.path);
|
|
67
|
-
if (fs.existsSync(target)) {
|
|
68
|
-
skipped.push(file.path);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
72
|
-
fs.writeFileSync(target, file.content);
|
|
73
|
-
created.push(file.path);
|
|
74
|
-
}
|
|
75
|
-
return { created, skipped };
|
|
76
|
-
}
|
package/templates/be/gitignore
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{{header}}
|
|
2
|
-
# Commit gate: the secrets guard and work hygiene of the staged files, types, lint and format of the staged files, the unit specs the staged files touch. Never integration, e2e or contract.
|
|
3
|
-
npx hfs work-hygiene
|
|
4
|
-
npm run typecheck
|
|
5
|
-
sources=$(git diff --cached --name-only --diff-filter=ACMR -- '*.ts' '*.mts' '*.cts' '*.js' '*.mjs' '*.cjs' || true)
|
|
6
|
-
if [ -n "$sources" ]; then
|
|
7
|
-
npx eslint $sources
|
|
8
|
-
npx prettier --check $sources
|
|
9
|
-
fi
|
|
10
|
-
specs=$(git diff --cached --name-only --diff-filter=ACMR -- '*.ts' | grep -v -E '(^|/)src/tests/(world|integration|e2e|contract)/|\.(integration|e2e|contract)-spec\.ts$' || true)
|
|
11
|
-
if [ -n "$specs" ]; then
|
|
12
|
-
npm run test:affected -- --findRelatedTests $specs
|
|
13
|
-
fi
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{{header}}
|
|
2
|
-
# Push gate: types, lint, format, the architecture check on the owners this push touches, unit specs affected since main. Never integration, e2e or contract.
|
|
3
|
-
npm run typecheck
|
|
4
|
-
npm run lint:check
|
|
5
|
-
npm run format:check
|
|
6
|
-
npm run hfs:check -- --fast
|
|
7
|
-
npm run test:affected -- --changedSince=origin/main
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"scripts": {
|
|
3
|
-
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
4
|
-
{{appScripts}}
|
|
5
|
-
"typecheck": "tsc -p tsconfig.json",
|
|
6
|
-
"typecheck:tests": "tsc -p src/tests/tsconfig.json",
|
|
7
|
-
"lint": "eslint --fix .",
|
|
8
|
-
"lint:check": "eslint .",
|
|
9
|
-
"lint:report": "eslint . --format json --output-file reports/eslint.json",
|
|
10
|
-
"format": "prettier --write .",
|
|
11
|
-
"format:check": "prettier --check .",
|
|
12
|
-
"test": "jest --selectProjects unit --coverage",
|
|
13
|
-
"test:affected": "jest --selectProjects unit --passWithNoTests",
|
|
14
|
-
"test:integration": "npm run typecheck:tests && jest --selectProjects integration",
|
|
15
|
-
"test:e2e": "npm run typecheck:tests && jest --selectProjects e2e",
|
|
16
|
-
"test:contract": "npm run typecheck:tests && jest --selectProjects contract",
|
|
17
|
-
"test:stack": "starci-test-stack",
|
|
18
|
-
"contract:emit": "hfs emit-contracts",
|
|
19
|
-
"hfs:check": "hfs check",
|
|
20
|
-
"hfs:report": "hfs check --sonar reports/hfs.sonar.json"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
File without changes
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { injector } from "@modules/platform/composition"
|
|
2
|
-
import type { TypedParameterDecorator } from "@modules/platform/composition"
|
|
3
|
-
import type { ServerOptions } from "./server.options"
|
|
4
|
-
|
|
5
|
-
/** Token under which the app module provides its {@link ServerOptions}. */
|
|
6
|
-
export const SERVER_OPTIONS: unique symbol = Symbol("platform.config.server-options")
|
|
7
|
-
|
|
8
|
-
/** Injects the server options of the app. Parameter type: ServerOptions. */
|
|
9
|
-
export const InjectServerOptions = (): TypedParameterDecorator<ServerOptions> => injector<ServerOptions>(SERVER_OPTIONS)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Catch, HttpException, HttpStatus } from "@nestjs/common"
|
|
2
|
-
import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common"
|
|
3
|
-
import type { Response } from "express"
|
|
4
|
-
import { InjectLogger } from "@modules/platform/logging"
|
|
5
|
-
import type { Logger } from "@modules/platform/logging"
|
|
6
|
-
import { ErrorsLogEvent } from "./errors.log-events"
|
|
7
|
-
|
|
8
|
-
@Catch()
|
|
9
|
-
/** The one filter of an app: logs every failure once and answers with a status and a code that reveal nothing else. */
|
|
10
|
-
export class ErrorFilter implements ExceptionFilter {
|
|
11
|
-
constructor(@InjectLogger() private readonly logger: Logger) {}
|
|
12
|
-
|
|
13
|
-
/** Answers the request; an unknown failure is a 500 whose body names no detail. */
|
|
14
|
-
catch(exception: unknown, host: ArgumentsHost): void {
|
|
15
|
-
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
|
|
16
|
-
this.logger.error(ErrorsLogEvent.RequestFailed, exception, { status })
|
|
17
|
-
const code = status === HttpStatus.INTERNAL_SERVER_ERROR ? "internal_error" : `http_${status}`
|
|
18
|
-
host.switchToHttp().getResponse<Response>().status(status).json({ code })
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
{{header}}
|
|
2
|
-
name: ci
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
push:
|
|
6
|
-
branches: [main]
|
|
7
|
-
pull_request:
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
ci:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
env:
|
|
16
|
-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
- uses: actions/setup-node@v4
|
|
20
|
-
with:
|
|
21
|
-
node-version: {{nodeMajor}}
|
|
22
|
-
cache: npm
|
|
23
|
-
- run: npm ci
|
|
24
|
-
- name: hfs check
|
|
25
|
-
run: npm run hfs:report
|
|
26
|
-
- name: lint
|
|
27
|
-
run: npm run lint:check
|
|
28
|
-
- name: eslint report
|
|
29
|
-
if: ${{ !cancelled() }}
|
|
30
|
-
run: npm run lint:report
|
|
31
|
-
- name: eslint sonar import
|
|
32
|
-
if: ${{ !cancelled() }}
|
|
33
|
-
run: npx hfs report eslint reports/eslint.json reports/eslint.sonar.json
|
|
34
|
-
- name: stylelint report
|
|
35
|
-
if: ${{ !cancelled() }}
|
|
36
|
-
run: npm run lint:report:css
|
|
37
|
-
- name: stylelint sonar import
|
|
38
|
-
if: ${{ !cancelled() }}
|
|
39
|
-
run: npx hfs report stylelint reports/stylelint.json reports/stylelint.sonar.json
|
|
40
|
-
- name: format
|
|
41
|
-
run: npm run format:check
|
|
42
|
-
- name: typecheck
|
|
43
|
-
run: npm run typecheck
|
|
44
|
-
- name: build
|
|
45
|
-
run: npm run build
|
|
46
|
-
- uses: SonarSource/sonarqube-scan-action@v7
|
|
47
|
-
if: ${{ !cancelled() && env.SONAR_TOKEN != '' }}
|
|
48
|
-
env:
|
|
49
|
-
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
|
|
50
|
-
- uses: SonarSource/sonarqube-quality-gate-action@v1
|
|
51
|
-
if: ${{ !cancelled() && env.SONAR_TOKEN != '' }}
|
|
52
|
-
timeout-minutes: 10
|
|
53
|
-
env:
|
|
54
|
-
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
|
package/templates/fe/gitignore
DELETED