@starci/hfs 5.2.0 → 5.4.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 +43 -0
- package/README.md +2 -1
- package/cache/hfs-family.d.mts +2 -3
- package/cache/hfs-family.mjs +4 -9
- package/cache/secret-records.d.mts +7 -0
- package/cache/secret-records.mjs +12 -0
- package/cache/session.mjs +4 -2
- package/codemod/account-doors.d.mts +36 -0
- package/codemod/account-doors.mjs +134 -0
- package/codemod/app-prettier.d.mts +5 -0
- package/codemod/app-prettier.mjs +27 -0
- package/codemod/explicit-surface.mjs +1 -25
- package/codemod/main.mjs +35 -1
- package/codemod/module-definition.d.mts +59 -0
- package/codemod/module-definition.mjs +257 -0
- package/codemod/quality-binding.d.mts +33 -0
- package/codemod/quality-binding.mjs +38 -0
- package/codemod/secrets-cache.d.mts +35 -0
- package/codemod/secrets-cache.mjs +70 -0
- package/codemod/sops-rules.d.mts +34 -0
- package/codemod/sops-rules.mjs +69 -0
- package/codemod/theme-tokens.mjs +48 -2
- package/lint/run.d.mts +6 -0
- package/lint/run.mjs +63 -10
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +43 -10
- package/runtime/knowledge/hfs/fe-stack.yaml +3 -3
- package/runtime/knowledge/hfs/rules.yaml +13 -9
- package/runtime/knowledge/hfs/slots.yaml +105 -12
- package/runtime/modules/kernel/failure-codes.yaml +5 -4
- package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
- package/runtime/scripts/hfs/architecture/config.mjs +25 -1
- package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
- package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
- package/runtime/scripts/hfs/architecture/type-context.d.mts +4 -1
- package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
- package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
- package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
- package/runtime/scripts/hfs/rules/database-access.mjs +39 -1
- package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
- package/runtime/scripts/hfs/rules/secrets.mjs +11 -0
- package/runtime/scripts/hfs/secret-errors.d.mts +3 -0
- package/runtime/scripts/hfs/secret-errors.mjs +8 -0
- package/runtime/scripts/hfs/secret-init.d.mts +6 -0
- package/runtime/scripts/hfs/secret-init.mjs +23 -0
- package/runtime/scripts/hfs/secret-sops.mjs +9 -1
- package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
- package/runtime/scripts/hfs/secret-standard.mjs +106 -0
- package/runtime/scripts/hfs/secret.mjs +5 -1
- package/runtime/scripts/hfs/view.d.mts +23 -0
- package/runtime/scripts/hfs/view.mjs +68 -0
- package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
- package/runtime/scripts/lib/declaration-surface.mjs +55 -1
- package/runtime/scripts/lib/secret-records.d.mts +13 -0
- package/runtime/scripts/lib/secret-records.mjs +83 -0
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-queue-lite.mjs +1 -3
- package/scaffold/app.d.mts +2 -2
- package/scaffold/app.mjs +10 -12
- package/scaffold/lite-exports.mjs +1 -15
- package/scripts/sync-bundles.d.mts +8 -0
- package/stack/images/openclaw-runtime/Dockerfile +18 -0
- package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
- package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
- package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -0
- package/stack/local-up.mjs +3 -0
- package/stack/runtime-layers.d.mts +21 -0
- package/stack/runtime-layers.mjs +67 -0
- package/sync/compile-guard.d.mts +36 -0
- package/sync/compile-guard.mjs +189 -0
- package/sync/hygiene.mjs +3 -1
- package/sync/index.d.mts +11 -1
- package/sync/index.mjs +61 -10
- package/templates/app/auth-doors.sql.partial +115 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
- package/templates/app/package-scripts/core.json +1 -1
- package/templates/app/package-scripts/package.json +1 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +9 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +3 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +19 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +3 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +5 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +4 -0
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +27 -5
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +33 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +3 -10
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +6 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +3 -10
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +56 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/resend.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +30 -30
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +7 -16
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +113 -49
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +42 -53
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +4 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +3 -10
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +6 -3
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +6 -1
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +17 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +6 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +29 -33
- package/templates/be/inbox/module-definition.ts.tpl +2 -9
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
- package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +3 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +2 -9
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +11 -0
- package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/table/module-definition.ts.tpl +2 -9
- package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
- package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/confirm/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppConfirmPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +0 -38
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +0 -54
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# catalog READ this file through scripts/hfs/slots.mjs; none of them hardcodes a path.
|
|
8
8
|
# Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
|
|
9
9
|
schema: starci/hfs-slots@3
|
|
10
|
-
version: 3.
|
|
10
|
+
version: 3.17.0
|
|
11
11
|
|
|
12
12
|
versioning:
|
|
13
13
|
# MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 3).
|
|
@@ -58,6 +58,9 @@ versioning:
|
|
|
58
58
|
# 3.13.0 (rulings lite-pgmq-bus-bullmq-jobs and lite-db-image-in-stacks, 2026-10-29) is a change for lite only: ruleParams.db.extensions
|
|
59
59
|
# admits pgmq, the event bus, ruleParams.db.queueOutbox is retired with the pg_cron half of R258, the lite event-bus functions replace
|
|
60
60
|
# event_bus_write in ruleParams.be.dbFunctions, and app.db.image moves to .starcistacks/images/db/Dockerfile (db/ keeps schema and data).
|
|
61
|
+
# 3.17.0 (ruling auth-oidc-package, 2026-11-03) adds the optional full-edition be.integrations.keycloak, the glue of @starci/auth-oidc (the OIDC client of the
|
|
62
|
+
# identity provider behind the PrincipalVerifier of @starci/auth), and gives @starci/auth-oidc to it in ruleParams.be.infraOwners.
|
|
63
|
+
# 3.17.0 (ruling module-packages, 2026-11-03) adds the optional app.packages and app.module-registry: the workspace packages of the app root (the agent SDK, the module packages, the managed registry).
|
|
61
64
|
# 3.15.0 (ruling app-check-cache, 2026-10-31) adds the optional app.starcistacks.cache, the committed check cache of the app (.starcistacks/cache/, both editions).
|
|
62
65
|
# 3.14.0 (ruling app-secrets-one-standard, 2026-10-29) adds the optional app.ci-stack-key-restore, the managed workflow that writes the
|
|
63
66
|
# deploy server's age identity back from its GitHub Environment backup.
|
|
@@ -256,6 +259,8 @@ ruleParams:
|
|
|
256
259
|
"@aws-sdk/s3-request-presigner": []
|
|
257
260
|
"@starci/object-storage": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3 and Spaces (R331)
|
|
258
261
|
minio: [] # storage goes through @starci/object-storage
|
|
262
|
+
nodemailer: [] # the SMTP client is a dependency of @starci/mail alone; no app file imports it
|
|
263
|
+
"@starci/mail": [integrations/mail] # outgoing mail: the Mailer port, parsed and registered by its one glue
|
|
259
264
|
"@supabase": [] # no Supabase client in a back end
|
|
260
265
|
"@nestjs/passport": [] # the identity libraries are @starci/auth's dependencies, never an app's (R328)
|
|
261
266
|
passport: []
|
|
@@ -264,8 +269,15 @@ ruleParams:
|
|
|
264
269
|
"@nestjs/jwt": []
|
|
265
270
|
jose: []
|
|
266
271
|
bcryptjs: []
|
|
272
|
+
jsonwebtoken: [] # no other JWT or OIDC client library in a back end: the tokens of an identity provider are verified by @starci/auth-oidc (R328)
|
|
273
|
+
jwks-rsa: []
|
|
274
|
+
jwt-decode: []
|
|
275
|
+
passport-jwt: []
|
|
276
|
+
openid-client: []
|
|
267
277
|
"@starci/auth": [integrations/passport] # the lite sign-in package: its server entry is reached only through integrations/passport (R328)
|
|
268
278
|
"@starci/auth/testing": [integrations/passport] # its test doubles, for the glue's specs
|
|
279
|
+
"@starci/auth-oidc": [integrations/keycloak] # the full sign-in package (jose is its dependency, imported by no app file): its server entry is reached only through integrations/keycloak (R328)
|
|
280
|
+
"@starci/auth-oidc/testing": [integrations/keycloak] # its fake issuer, for the glue's specs
|
|
269
281
|
bcrypt: []
|
|
270
282
|
cache-manager: [integrations/cache, integrations/redis]
|
|
271
283
|
"@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability
|
|
@@ -479,6 +491,14 @@ ruleParams:
|
|
|
479
491
|
- {name: public.event_bus_bury, reason: "platform/event-bus (add webhook, lite): the move of an exhausted or malformed message to its consumer's dead-letter queue"}
|
|
480
492
|
- {name: public.event_bus_dead_letters, reason: "platform/event-bus (add webhook, lite): the operator read of a consumer's dead-letter queue"}
|
|
481
493
|
- {name: public.event_bus_requeue, reason: "platform/event-bus (add webhook, lite): the operator requeue of one dead letter"}
|
|
494
|
+
- {name: public.be_auth_user_by_email, reason: "domain/identity (lite sign-in): the account of an exact sign-in address with its bcrypt hash, through the account-doors definer of the baseline"}
|
|
495
|
+
- {name: public.be_auth_user_by_id, reason: "domain/identity (lite sign-in): the standing of one account by id (a refresh), through the account-doors definer"}
|
|
496
|
+
- {name: public.be_auth_user_by_google, reason: "domain/identity (lite sign-in): the account a Google subject is linked to, through the account-doors definer"}
|
|
497
|
+
- {name: public.be_auth_create_user, reason: "domain/identity (lite sign-in): the account a first Google sign-in creates, through the account-doors definer"}
|
|
498
|
+
- {name: public.be_auth_link_google, reason: "domain/identity (lite sign-in): the link of a Google subject to an account, through the account-doors definer"}
|
|
499
|
+
- {name: public.be_auth_touch_user, reason: "domain/identity (lite sign-in): the sign-in record of an account and its profile keys, through the account-doors definer"}
|
|
500
|
+
- {name: public.be_auth_confirm_user, reason: "domain/identity (lite sign-in): the confirmation stamp of a mailbox whose link was spent, through the account-doors definer"}
|
|
501
|
+
- {name: public.be_auth_set_password, reason: "domain/identity (lite sign-in): the new password hash of a spent reset link (and the mailbox it proved), through the account-doors definer"}
|
|
482
502
|
# R334 (lite): the only `.query(` outside platform/database's composer and the test world is the pass-through of a file listed here
|
|
483
503
|
# (or in hfs.json sides.be.sqlPassThrough), whose SQL text is not the app's own. Paths are relative to the side folder.
|
|
484
504
|
sqlPassThrough:
|
|
@@ -606,13 +626,24 @@ slots:
|
|
|
606
626
|
- id: app.tool-config-local
|
|
607
627
|
since: 3.1.0
|
|
608
628
|
profiles: [app]
|
|
609
|
-
path: "{tsconfig.json,.eslintrc,.eslintrc.*,.eslintignore,eslint.config
|
|
629
|
+
path: "{tsconfig.json,.eslintrc,.eslintrc.*,.eslintignore,eslint.config.js,eslint.config.cjs,eslint.config.ts,eslint.config.mts,eslint.config.cts,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.*,.prettierrc.*,prettier.config.*,jest.config.*,lint-staged.config.*,.lintstagedrc*}"
|
|
610
630
|
presence: forbidden
|
|
611
631
|
tracked: external
|
|
612
632
|
tier: none
|
|
613
633
|
tests: none
|
|
614
|
-
goesTo: "nowhere at the app root: each side holds exactly its managed tool configuration (be.tool-config, fe.tool-config) and the root only the formatter (app.format-config)"
|
|
634
|
+
goesTo: "nowhere at the app root: each side holds exactly its managed tool configuration (be.tool-config, fe.tool-config) and the root only the formatter (app.format-config) and the eslint config of its root packages (app.packages-lint)"
|
|
615
635
|
rules: [HFS_TOOL_CONFIG_LOCAL]
|
|
636
|
+
- id: app.packages-lint
|
|
637
|
+
since: 3.17.0
|
|
638
|
+
profiles: [app]
|
|
639
|
+
# The one eslint config at the app root: `starci app sync` renders it when the app has root workspace packages (app.packages), and it lints their
|
|
640
|
+
# source by subpath (the back-end canon, the front-end canon and the Grammar family each over their own subpaths). No other tool config lives here.
|
|
641
|
+
path: eslint.config.mjs
|
|
642
|
+
presence: optional
|
|
643
|
+
tracked: tracked
|
|
644
|
+
tier: none
|
|
645
|
+
tests: none
|
|
646
|
+
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
616
647
|
- id: app.quality-config
|
|
617
648
|
since: 3.1.0
|
|
618
649
|
profiles: [app]
|
|
@@ -857,6 +888,31 @@ slots:
|
|
|
857
888
|
rules: [HFS_PLAINTEXT_SECRET, HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK]
|
|
858
889
|
exemplifiedBy:
|
|
859
890
|
- {path: packages/hfs/src/hfs-slot-ownership.spec.mjs, editions: [lite, full]}
|
|
891
|
+
- id: app.packages
|
|
892
|
+
since: 3.17.0
|
|
893
|
+
profiles: [app]
|
|
894
|
+
editionWhy: Lite and full share the slot; a product's agent SDK and its agent modules are workspace packages in both.
|
|
895
|
+
# A workspace package of the app ROOT (packages/agent-sdk, packages/module-<name>): first-party source that both sides import through its
|
|
896
|
+
# package entry points and never past them (ARCH_INTERNAL_IMPORT_OUTSIDE), linted by the root eslint config by subpath.
|
|
897
|
+
path: "packages/{agent-sdk,module-<name>}/"
|
|
898
|
+
presence: optional
|
|
899
|
+
tracked: tracked
|
|
900
|
+
tier: none
|
|
901
|
+
requires: [package.json, tsconfig.json, src/index.ts]
|
|
902
|
+
tests: unit-beside
|
|
903
|
+
rules: [HFS_UNUSED_EXPORT]
|
|
904
|
+
- id: app.module-registry
|
|
905
|
+
since: 3.17.0
|
|
906
|
+
profiles: [app]
|
|
907
|
+
editionWhy: Lite and full share the slot; the registry lists the module packages of the app.
|
|
908
|
+
path: packages/modules/
|
|
909
|
+
presence: optional
|
|
910
|
+
tracked: tracked
|
|
911
|
+
tier: none
|
|
912
|
+
requires: [package.json, src/index.ts]
|
|
913
|
+
allows: [package.json, tsconfig.json, tsconfig.build.json, src/index.ts, src/template/index.ts, src/manifest/index.ts]
|
|
914
|
+
tests: none
|
|
915
|
+
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
860
916
|
- id: app.sides
|
|
861
917
|
since: 3.1.0
|
|
862
918
|
profiles: [app]
|
|
@@ -2055,8 +2111,8 @@ slots:
|
|
|
2055
2111
|
profiles: [be]
|
|
2056
2112
|
editions: [lite]
|
|
2057
2113
|
editionWhy: "lite signs people in with the package @starci/auth, so its sign-in glue is rendered, not written (owner ruling auth-package); full uses its identity provider."
|
|
2058
|
-
# The identity capability's sign-in glue over @starci/auth: the entities of the session, attempt and
|
|
2059
|
-
path: "{src/modules/domain/identity/account-directory.service.spec.ts,src/modules/domain/identity/account-directory.service.ts,src/modules/domain/identity/authenticator.service.spec.ts,src/modules/domain/identity/authenticator.service.ts,src/modules/domain/identity/
|
|
2114
|
+
# The identity capability's sign-in glue over @starci/auth: the entities of the session, attempt, refresh-token and email-token tables, the account doors over auth.users and auth.identities, the stores the package asks for (sessions, accounts, rate counter), the authenticator and the service the doors call, with their specs. `starci app sync` renders them; a hand edit is drift.
|
|
2115
|
+
path: "{src/modules/domain/identity/account-directory.service.spec.ts,src/modules/domain/identity/account-directory.service.ts,src/modules/domain/identity/authenticator.service.spec.ts,src/modules/domain/identity/authenticator.service.ts,src/modules/domain/identity/identity-email.log-events.ts,src/modules/domain/identity/identity-email.service.spec.ts,src/modules/domain/identity/identity-email.service.ts,src/modules/domain/identity/identity-sign-in.log-events.ts,src/modules/domain/identity/persistence/account-doors.port.ts,src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts,src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts,src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts,src/modules/domain/identity/persistence/entities/identity-session.entity.ts,src/modules/domain/identity/persistence/entities/index.ts,src/modules/domain/identity/rate-counter.service.spec.ts,src/modules/domain/identity/rate-counter.service.ts,src/modules/domain/identity/session-store.service.spec.ts,src/modules/domain/identity/session-store.service.ts,src/modules/domain/identity/sign-in.service.spec.ts,src/modules/domain/identity/sign-in.service.ts,src/tests/fixtures/builders/identity.builder.ts}"
|
|
2060
2116
|
presence: optional
|
|
2061
2117
|
tracked: tracked
|
|
2062
2118
|
tier: inherit
|
|
@@ -2071,7 +2127,7 @@ slots:
|
|
|
2071
2127
|
editions: [lite]
|
|
2072
2128
|
editionWhy: "lite signs people in with the package @starci/auth, so its sign-in glue is rendered, not written (owner ruling auth-package); full uses its identity provider."
|
|
2073
2129
|
# The auth feature: the REST doors under /auth (login, refresh, session, logout, Google start and callback, jwks) over IdentitySignInService, one command or query and handler per door.
|
|
2074
|
-
path: "{src/features/api/auth/application/auth.contracts.ts,src/features/api/auth/application/get-jwks.handler.ts,src/features/api/auth/application/get-jwks.query.ts,src/features/api/auth/application/google-begin.command.ts,src/features/api/auth/application/google-begin.handler.ts,src/features/api/auth/application/google-complete.command.ts,src/features/api/auth/application/google-complete.handler.ts,src/features/api/auth/application/login.command.ts,src/features/api/auth/application/login.handler.ts,src/features/api/auth/application/logout.command.ts,src/features/api/auth/application/logout.handler.ts,src/features/api/auth/application/refresh.command.ts,src/features/api/auth/application/refresh.handler.ts,src/features/api/auth/application/session.command.ts,src/features/api/auth/application/session.handler.ts,src/features/api/auth/auth.module-definition.ts,src/features/api/auth/auth.module.ts,src/features/api/auth/auth.options.ts,src/features/api/auth/index.ts,src/features/api/auth/transport/http/auth-http.module-definition.ts,src/features/api/auth/transport/http/auth-http.module.ts,src/features/api/auth/transport/http/auth-http.options.ts,src/features/api/auth/transport/http/auth.controller.ts,src/features/api/auth/transport/http/dto/google-start.request.ts,src/features/api/auth/transport/http/dto/login.request.ts}"
|
|
2130
|
+
path: "{src/features/api/auth/application/auth.contracts.ts,src/features/api/auth/application/confirm.command.ts,src/features/api/auth/application/confirm.handler.ts,src/features/api/auth/application/forgot.command.ts,src/features/api/auth/application/forgot.handler.ts,src/features/api/auth/application/get-jwks.handler.ts,src/features/api/auth/application/get-jwks.query.ts,src/features/api/auth/application/google-begin.command.ts,src/features/api/auth/application/google-begin.handler.ts,src/features/api/auth/application/google-complete.command.ts,src/features/api/auth/application/google-complete.handler.ts,src/features/api/auth/application/login.command.ts,src/features/api/auth/application/login.handler.ts,src/features/api/auth/application/logout.command.ts,src/features/api/auth/application/logout.handler.ts,src/features/api/auth/application/refresh.command.ts,src/features/api/auth/application/refresh.handler.ts,src/features/api/auth/application/resend.command.ts,src/features/api/auth/application/resend.handler.ts,src/features/api/auth/application/reset.command.ts,src/features/api/auth/application/reset.handler.ts,src/features/api/auth/application/session.command.ts,src/features/api/auth/application/session.handler.ts,src/features/api/auth/application/signup.command.ts,src/features/api/auth/application/signup.handler.ts,src/features/api/auth/auth.module-definition.ts,src/features/api/auth/auth.module.ts,src/features/api/auth/auth.options.ts,src/features/api/auth/index.ts,src/features/api/auth/transport/http/auth-http.module-definition.ts,src/features/api/auth/transport/http/auth-http.module.ts,src/features/api/auth/transport/http/auth-http.options.ts,src/features/api/auth/transport/http/auth.controller.ts,src/features/api/auth/transport/http/dto/forgot.request.ts,src/features/api/auth/transport/http/dto/google-start.request.ts,src/features/api/auth/transport/http/dto/login.request.ts,src/features/api/auth/transport/http/dto/resend.request.ts,src/features/api/auth/transport/http/dto/reset.request.ts,src/features/api/auth/transport/http/dto/signup.request.ts,src/features/api/auth/transport/http/dto/token.request.ts}"
|
|
2075
2131
|
presence: optional
|
|
2076
2132
|
tracked: tracked
|
|
2077
2133
|
tier: inherit
|
|
@@ -2080,6 +2136,25 @@ slots:
|
|
|
2080
2136
|
managedBy: auth-lite
|
|
2081
2137
|
managedOnly: true # lists files sync renders; the slot owning the path judges them
|
|
2082
2138
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
2139
|
+
- id: be.integrations.mail
|
|
2140
|
+
profiles: [be]
|
|
2141
|
+
editions: [lite]
|
|
2142
|
+
exemplifiedBy:
|
|
2143
|
+
- packages/hfs/templates/be/skeleton-lite/src/modules/integrations/mail/
|
|
2144
|
+
# The glue of @starci/mail, the owner of the SMTP client (nodemailer): this folder parses the settings (the server, the sender and the
|
|
2145
|
+
# link lifetimes of the confirmation and reset mails), registers the module and names the injectors; no app file imports nodemailer,
|
|
2146
|
+
# and no other module imports the package. The words and layout of each mail belong to the domain that sends it.
|
|
2147
|
+
path: "src/modules/integrations/mail/"
|
|
2148
|
+
presence: required
|
|
2149
|
+
tracked: tracked
|
|
2150
|
+
tier: integrations
|
|
2151
|
+
owner: true
|
|
2152
|
+
requires: [index.ts, mail.config.ts, mail.options.ts, mail.decorators.ts, mail.module-definition.ts, mail.module.ts]
|
|
2153
|
+
allows: [index.ts, mail.config.ts, mail.options.ts, mail.decorators.ts, mail.module-definition.ts, mail.module.ts, errors/]
|
|
2154
|
+
tests: unit-beside
|
|
2155
|
+
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER]
|
|
2156
|
+
coverage: required
|
|
2157
|
+
since: 5.3.0
|
|
2083
2158
|
- id: be.integrations.object-storage
|
|
2084
2159
|
profiles: [be]
|
|
2085
2160
|
exemplifiedBy:
|
|
@@ -2099,6 +2174,24 @@ slots:
|
|
|
2099
2174
|
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_STORAGE_OWNER]
|
|
2100
2175
|
coverage: required
|
|
2101
2176
|
since: 3.9.0
|
|
2177
|
+
- id: be.integrations.keycloak
|
|
2178
|
+
profiles: [be]
|
|
2179
|
+
editions: [full]
|
|
2180
|
+
# The glue of @starci/auth-oidc, the OIDC client of the full edition's identity provider (Keycloak): discovery, the provider's JWKS,
|
|
2181
|
+
# algorithm-pinned token checks and the authorization code flow with PKCE and nonce behind the PrincipalVerifier of @starci/auth.
|
|
2182
|
+
# This folder parses the typed options, hands the package the app's bounded HTTP client and clock, and names the injectors;
|
|
2183
|
+
# no app file imports jose, and no other module imports the package (ruleParams.be.infraOwners).
|
|
2184
|
+
path: "src/modules/integrations/keycloak/"
|
|
2185
|
+
presence: optional
|
|
2186
|
+
tracked: tracked
|
|
2187
|
+
tier: integrations
|
|
2188
|
+
owner: true
|
|
2189
|
+
requires: [index.ts, keycloak.config.ts, keycloak.options.ts, keycloak.decorators.ts, keycloak.registration.ts]
|
|
2190
|
+
allows: [index.ts, keycloak.config.ts, keycloak.options.ts, keycloak.decorators.ts, keycloak.registration.ts, errors/, messages/]
|
|
2191
|
+
tests: unit-beside
|
|
2192
|
+
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_AUTH_PASSPORT_OWNER, BE_INTEGRATION_SPEC_MISSING]
|
|
2193
|
+
coverage: required
|
|
2194
|
+
since: 3.17.0
|
|
2102
2195
|
|
|
2103
2196
|
# ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
|
|
2104
2197
|
# lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
|
|
@@ -2572,9 +2665,9 @@ slots:
|
|
|
2572
2665
|
presence: required
|
|
2573
2666
|
tracked: tracked
|
|
2574
2667
|
tier: foundation
|
|
2575
|
-
# The app's
|
|
2576
|
-
#
|
|
2577
|
-
#
|
|
2668
|
+
# The app's description of its languages (defineI18n of @starci/next-i18n), its request config and catalogs, or its adapter to the
|
|
2669
|
+
# shared i18n workspace package. A single app builds its navigation (createI18nNavigation) in fe.modules.routes, keeping this
|
|
2670
|
+
# public entry safe for its proxy. A description shared by several apps belongs to fe.package.i18n.
|
|
2578
2671
|
requires: [index.ts]
|
|
2579
2672
|
tests: none
|
|
2580
2673
|
rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
|
|
@@ -2643,8 +2736,8 @@ slots:
|
|
|
2643
2736
|
profiles: [fe]
|
|
2644
2737
|
editions: [lite]
|
|
2645
2738
|
editionWhy: "lite signs people in with the package @starci/auth, so its auth client is rendered, not written (owner ruling auth-package); full wires its identity provider."
|
|
2646
|
-
# The AuthClient every edition shares, implemented by one @starci/auth/client instance over the app's own back-end auth doors.
|
|
2647
|
-
path: "apps/web/src/modules/auth/client.ts"
|
|
2739
|
+
# The AuthClient every edition shares, implemented by one @starci/auth/client instance over the app's own back-end auth doors. email-doors.ts is the lite-only call of the five email doors (sign up, resend, confirm, forgot, reset), kept off the shared AuthClient interface because the full edition identity provider owns its own recovery flows; index.ts is the module entry that exports both.
|
|
2740
|
+
path: "apps/web/src/modules/auth/{client.ts,email-doors.ts,index.ts}"
|
|
2648
2741
|
presence: optional
|
|
2649
2742
|
tracked: tracked
|
|
2650
2743
|
tier: inherit
|
|
@@ -2726,7 +2819,7 @@ slots:
|
|
|
2726
2819
|
owner: true
|
|
2727
2820
|
requires: [package.json, src/index.ts, tsconfig.json]
|
|
2728
2821
|
tests: none
|
|
2729
|
-
why: the
|
|
2822
|
+
why: the product's languages (defineI18n of @starci/next-i18n) written once and composed into the createAppI18n factory every app calls; the next-intl stack itself is the package's
|
|
2730
2823
|
rules: [FE_I18N_PLACEMENT, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
|
|
2731
2824
|
|
|
2732
2825
|
# Checks that read this manifest (all ship from .claude; none keeps its own path list)
|
|
@@ -980,7 +980,7 @@ BE_MODULE_DEFINITION_MISSING:
|
|
|
980
980
|
- "Có <x>.module.ts nhưng thiếu <x>.module-definition.ts hoặc <x>.options.ts bên cạnh"
|
|
981
981
|
- "Lớp module không kế thừa ConfigurableModuleClass lấy từ ./<x>.module-definition"
|
|
982
982
|
- "Module sản phẩm nằm trong mảng imports (hoặc nhóm compose của app) như một tên lớp trần thay vì XModule.register({...})"
|
|
983
|
-
nextStep_vi: "Tạo <x>.module-definition.ts (
|
|
983
|
+
nextStep_vi: "Tạo <x>.module-definition.ts (export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineConfigurableModule<XOptions>() từ @starci/nest-kit; capability dùng defineCapabilityModule) và <x>.options.ts (export type XOptions = Record<string, never> nếu chưa có tùy chọn), cho lớp kế thừa ConfigurableModuleClass, và đăng ký bằng XModule.register({}); op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra."
|
|
984
984
|
owner: op-retry
|
|
985
985
|
kind: check-finding
|
|
986
986
|
|
|
@@ -1013,7 +1013,7 @@ BE_MODULE_SHAPE:
|
|
|
1013
1013
|
- "@Global() trên bất kỳ module nào (kể cả trong spec)"
|
|
1014
1014
|
- "isGlobal: true ở ngoài app.module.ts của app"
|
|
1015
1015
|
- "Module của một capability khác bị import vào imports thay vì dùng qua Inject<Thing>()"
|
|
1016
|
-
- "new ConfigurableModuleBuilder() không có kiểu options"
|
|
1016
|
+
- "defineConfigurableModule() hay new ConfigurableModuleBuilder() không có kiểu options"
|
|
1017
1017
|
- "register không static, hoặc có forRoot, forFeature, registerAsync"
|
|
1018
1018
|
- "new XService() hoặc let ở cấp module tạo trạng thái toàn cục"
|
|
1019
1019
|
- "Feature có hai module trong một thư mục transport (một module cho mỗi thao tác), module-definition không nằm cạnh module của nó, hoặc @Module ngoài `<f>.module.ts` và `<f>-<protocol>.module.ts`; app import module ứng dụng của feature hoặc module transport không thuộc loại app cho phép (api: graphql/http/websocket; worker: schedule/message; cli: cli)"
|
|
@@ -1797,11 +1797,12 @@ CLI_ONLY_ENTRY:
|
|
|
1797
1797
|
DB_APP_ROLE:
|
|
1798
1798
|
title: "The back-end database role has explicit least privileges"
|
|
1799
1799
|
title_vi: "Role cơ sở dữ liệu của back end có quyền quá rộng"
|
|
1800
|
-
meaning_vi: "Migration tại <path> tạo hoặc sửa role của ứng dụng với superuser, bypassrls hay thuộc tính thiếu an toàn, hoặc cấp all thay cho các quyền cần thiết; role mặc định là app_be và có thể khai báo bằng supabase.appRole."
|
|
1800
|
+
meaning_vi: "Migration tại <path> tạo hoặc sửa role của ứng dụng với superuser, bypassrls hay thuộc tính thiếu an toàn, hoặc cấp all thay cho các quyền cần thiết, hoặc cấp cho role quyền trên schema auth (schema, bảng hay sequence trong đó, hay default privileges của nó) mà không có migration sau đó thu hồi; role mặc định là app_be và có thể khai báo bằng supabase.appRole."
|
|
1801
1801
|
causes_vi:
|
|
1802
1802
|
- "Vi phạm R255: app role vượt qua RLS hoặc được cấp quyền rộng hơn công việc của back end."
|
|
1803
1803
|
- "Role không khai báo rõ noinherit và nobypassrls, hoặc nhận grant all trên schema hay bảng."
|
|
1804
|
-
|
|
1804
|
+
- "Role nhận grant trên schema auth: tài khoản chỉ được truy cập qua các hàm SECURITY DEFINER be_auth_* (account doors) của baseline."
|
|
1805
|
+
nextStep_vi: "Khai báo app role với noinherit, nobypassrls và các thuộc tính không đặc quyền; bỏ superuser, bypassrls và grant all, rồi cấp từng quyền cần thiết. Với grant trên schema auth: chạy `starci app codemod account-doors --execute`, `starci app sync --execute`, rồi `starci app codemod account-doors --contract --execute`."
|
|
1805
1806
|
owner: op-retry
|
|
1806
1807
|
kind: check-finding
|
|
1807
1808
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { slash } from '../../lib/path-key.mjs';
|
|
2
2
|
import { isInside } from '../../lib/walk.mjs';
|
|
3
3
|
import { openHfs } from '../slots.mjs';
|
|
4
|
+
import type { RootPackage } from './root-packages.mjs';
|
|
4
5
|
/** The compiler API the checkers receive as their `ts` argument (the target repository's own installed TypeScript). */
|
|
5
6
|
export type TypeScriptApi = typeof import('typescript');
|
|
6
7
|
/** One owner derived from the slot manifest: an owner directory with its entry file. */
|
|
@@ -28,6 +29,8 @@ export interface ArchitectureConfig {
|
|
|
28
29
|
kinds: string[];
|
|
29
30
|
projects: string[];
|
|
30
31
|
workspaces: string[];
|
|
32
|
+
/** The app's own root workspace packages (empty at the app root itself, whose workspaces already hold them). */
|
|
33
|
+
rootPackages?: RootPackage[];
|
|
31
34
|
apps: {
|
|
32
35
|
name: string;
|
|
33
36
|
kind: string;
|
|
@@ -72,3 +75,4 @@ export declare function loadArchitectureConfig(repositoryRoot: string, { hfs }?:
|
|
|
72
75
|
hfs?: ArchitectureConfig['hfs'] | undefined;
|
|
73
76
|
}): ArchitectureConfig;
|
|
74
77
|
export { canonical, exactKeys, isInside, slash };
|
|
78
|
+
export type { RootPackage };
|
|
@@ -3,7 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import { slash } from '../../lib/path-key.mjs';
|
|
4
4
|
import { readJsonFile as readJson } from '../../lib/json.mjs';
|
|
5
5
|
import { isInside } from '../../lib/walk.mjs';
|
|
6
|
-
import { openHfs } from '../slots.mjs';
|
|
6
|
+
import { createSlotResolver, openHfs, readRepoDeclaration } from '../slots.mjs';
|
|
7
|
+
import { ROOT_PACKAGE_SLOTS, rootPackageRecord, rootWorkspaceDirectories } from './root-packages.mjs';
|
|
7
8
|
import { byCodeUnit } from '../../lib/list.mjs';
|
|
8
9
|
import { grammarContractFor } from '../../lib/grammar-lint.mjs';
|
|
9
10
|
/** A file's identity: its resolved real path, or the resolved path when it does not exist. */
|
|
@@ -182,6 +183,28 @@ function workspaceDirectories(root, { packageRoot: appPackageRoot = root, side =
|
|
|
182
183
|
}
|
|
183
184
|
return [...directories].sort(byCodeUnit);
|
|
184
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* The workspace packages of the app root that no side owns: the `workspaces` patterns of the app's one package.json that do not start
|
|
188
|
+
* with a side folder (`packages/*`), and the directories the slots `app.packages` and `app.module-registry` place under `packages/`
|
|
189
|
+
* even when the manifest forgot to list them. A side imports them as first-party packages (through their entry points), never as
|
|
190
|
+
* "outside the side". A side of the app is judged here; the app root itself already counts them among its workspaces.
|
|
191
|
+
*/
|
|
192
|
+
function rootPackageRecords(root, packageRoot, side, opened) {
|
|
193
|
+
if (side === null)
|
|
194
|
+
return [];
|
|
195
|
+
const candidates = new Set(rootWorkspaceDirectories(packageRoot));
|
|
196
|
+
if (opened.manifest && existingDirectory(packageRoot, 'packages')) {
|
|
197
|
+
const resolver = createSlotResolver(opened.manifest, readRepoDeclaration(opened.manifest, packageRoot));
|
|
198
|
+
const container = path.join(packageRoot, 'packages');
|
|
199
|
+
for (const entry of fs.readdirSync(container, { withFileTypes: true }).filter(item => item.isDirectory())) {
|
|
200
|
+
const slot = resolver.classifyPath(`packages/${entry.name}`).slot;
|
|
201
|
+
if (slot !== undefined && ROOT_PACKAGE_SLOTS.has(slot))
|
|
202
|
+
candidates.add(path.join(container, entry.name));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return [...candidates].sort(byCodeUnit).filter(candidate => !isInside(root, candidate))
|
|
206
|
+
.flatMap(candidate => rootPackageRecord(packageRoot, candidate, canonical) ?? []);
|
|
207
|
+
}
|
|
185
208
|
function discoveredProjects(root, workspaces) {
|
|
186
209
|
const projects = [];
|
|
187
210
|
if (fs.existsSync(path.join(root, 'tsconfig.json')))
|
|
@@ -373,6 +396,7 @@ export function loadArchitectureConfig(repositoryRoot, { hfs } = {}) {
|
|
|
373
396
|
kinds,
|
|
374
397
|
projects,
|
|
375
398
|
workspaces,
|
|
399
|
+
rootPackages: rootPackageRecords(root, packageRoot, side, opened),
|
|
376
400
|
apps: apps.map(app => ({ name: app.name, kind: app.kind })),
|
|
377
401
|
hfs: opened,
|
|
378
402
|
owners: derivedOwners(root, opened),
|
|
@@ -9,7 +9,7 @@ import type { MachineContext } from './machine-ast.mjs';
|
|
|
9
9
|
*
|
|
10
10
|
* - a `*.module.ts` file below a transport folder other than `<f>-<protocol>.module.ts`, a `*.module-definition.ts` that is
|
|
11
11
|
* not the sibling of the application module or of the transport module of its own folder, and an `@Module` class (or a
|
|
12
|
-
* `ConfigurableModuleBuilder`/`ConfigurableModuleClass` use) in a feature file that is neither the application module, the
|
|
12
|
+
* `ConfigurableModuleBuilder`/`ConfigurableModuleClass` use or a `defineConfigurableModule`/`defineCapabilityModule` call of `@starci/nest-kit`) in a feature file that is neither the application module, the
|
|
13
13
|
* transport module of its own folder nor that module's definition are refused;
|
|
14
14
|
* - apps import only transport modules, of the kinds their app kind allows: the `composedBy` list of each transport slot
|
|
15
15
|
* in knowledge/hfs/slots.yaml (api: graphql, http, websocket; worker: schedule, message). An app root (app.module.ts
|
|
@@ -11,7 +11,7 @@ import { trimTrailingSlashes } from '../trailing-slashes.mjs';
|
|
|
11
11
|
*
|
|
12
12
|
* - a `*.module.ts` file below a transport folder other than `<f>-<protocol>.module.ts`, a `*.module-definition.ts` that is
|
|
13
13
|
* not the sibling of the application module or of the transport module of its own folder, and an `@Module` class (or a
|
|
14
|
-
* `ConfigurableModuleBuilder`/`ConfigurableModuleClass` use) in a feature file that is neither the application module, the
|
|
14
|
+
* `ConfigurableModuleBuilder`/`ConfigurableModuleClass` use or a `defineConfigurableModule`/`defineCapabilityModule` call of `@starci/nest-kit`) in a feature file that is neither the application module, the
|
|
15
15
|
* transport module of its own folder nor that module's definition are refused;
|
|
16
16
|
* - apps import only transport modules, of the kinds their app kind allows: the `composedBy` list of each transport slot
|
|
17
17
|
* in knowledge/hfs/slots.yaml (api: graphql, http, websocket; worker: schedule, message). An app root (app.module.ts
|
|
@@ -99,6 +99,8 @@ function reportModuleClassLocation(file, name, place, composedModuleOf, report)
|
|
|
99
99
|
if (file.rel !== own)
|
|
100
100
|
report(file, name, `${name.text} is a Nest module declared in ${file.rel}; a feature declares exactly its application module (${place.feature}.module.ts) and one module per transport folder (${place.feature}-<protocol>.module.ts), never a module per operation.`, { feature: place.feature });
|
|
101
101
|
}
|
|
102
|
+
/** The module-definition helpers of @starci/nest-kit: each builds the definition of one module, like `ConfigurableModuleBuilder`. */
|
|
103
|
+
const DEFINITION_HELPERS = ['defineConfigurableModule', 'defineCapabilityModule'];
|
|
102
104
|
function reportConfigurableModuleUses(file, checker, kit, ts, place, env) {
|
|
103
105
|
const own = ownModuleOf(place, file.rel, env.composedModuleOf);
|
|
104
106
|
const isDefinition = file.rel === definitionOf(own);
|
|
@@ -106,6 +108,9 @@ function reportConfigurableModuleUses(file, checker, kit, ts, place, env) {
|
|
|
106
108
|
if (ts.isNewExpression(node) && kit.isImportOf(checker, node.expression, 'ConfigurableModuleBuilder', '@nestjs/common') && !isDefinition) {
|
|
107
109
|
env.report(file, node, `ConfigurableModuleBuilder builds the module definition of one module; in feature ${place.feature} it is written only in ${path.posix.basename(definitionOf(own))}, beside ${path.posix.basename(own)}.`, { feature: place.feature });
|
|
108
110
|
}
|
|
111
|
+
else if (ts.isCallExpression(node) && !isDefinition && DEFINITION_HELPERS.some(name => kit.isImportOf(checker, node.expression, name, '@starci/nest-kit'))) {
|
|
112
|
+
env.report(file, node, `${node.expression.getText(file.sourceFile)} builds the module definition of one module; in feature ${place.feature} it is written only in ${path.posix.basename(definitionOf(own))}, beside ${path.posix.basename(own)}.`, { feature: place.feature });
|
|
113
|
+
}
|
|
109
114
|
else if (ts.isHeritageClause(node) && file.rel !== own) {
|
|
110
115
|
for (const type of node.types)
|
|
111
116
|
if (kit.isImportOf(checker, type.expression, 'ConfigurableModuleClass', '@nestjs/common')) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A workspace package of the app root: a first-party package that both sides import through its package entry points and never past
|
|
3
|
+
* them. `root` is the real path of its folder, `relative` the folder from the app root, `exports` the `exports` of its manifest.
|
|
4
|
+
*/
|
|
5
|
+
export interface RootPackage {
|
|
6
|
+
root: string;
|
|
7
|
+
relative: string;
|
|
8
|
+
name: string;
|
|
9
|
+
exports: unknown;
|
|
10
|
+
manifest: Record<string, unknown>;
|
|
11
|
+
workspace: true;
|
|
12
|
+
app: false;
|
|
13
|
+
}
|
|
14
|
+
/** The slots whose directories at the app root are first-party packages of the app (agent SDK, module packages, the managed registry). */
|
|
15
|
+
export declare const ROOT_PACKAGE_SLOTS: ReadonlySet<string>;
|
|
16
|
+
/** A package folder with a regular package.json (a link is not one). */
|
|
17
|
+
export declare function packageDirectory(absolute: string): boolean;
|
|
18
|
+
/** The folders the `workspaces` of the app root's package.json name that no side owns, as absolute paths in code-unit order. */
|
|
19
|
+
export declare function rootWorkspaceDirectories(packageRoot: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The record of one package folder of the app root, or null when it is no named package inside the app root.
|
|
22
|
+
* `canonical` is the file-identity function of the caller (the real path), so a record agrees with the files the graph reads.
|
|
23
|
+
*/
|
|
24
|
+
export declare function rootPackageRecord(packageRoot: string, directory: string, canonical: (file: string) => string): RootPackage | null;
|
|
25
|
+
/** The names of the workspace packages of the app root: what the back-end canon must not read as third-party libraries. */
|
|
26
|
+
export declare function rootPackageNames(packageRoot: string): Set<string>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// root-packages.mjs - the workspace packages of an app ROOT (packages/agent-sdk, packages/module-<name>, the managed packages/modules).
|
|
2
|
+
// They lie outside both side folders and belong to no side; both sides import them by package name, through their package entry points
|
|
3
|
+
// (the `exports` of the manifest), so the import graph reads them as first-party packages and the back-end canon never reads them as
|
|
4
|
+
// a third-party library. The app root's one package.json lists them in `workspaces`; the patterns that start with a side folder
|
|
5
|
+
// (`fe/apps/*`) are that side's own workspaces and are not read here.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { readJsonFile as readJson } from '../../lib/json.mjs';
|
|
9
|
+
import { byCodeUnit } from '../../lib/list.mjs';
|
|
10
|
+
import { isInside } from '../../lib/walk.mjs';
|
|
11
|
+
import { slash } from '../../lib/path-key.mjs';
|
|
12
|
+
/** The slots whose directories at the app root are first-party packages of the app (agent SDK, module packages, the managed registry). */
|
|
13
|
+
export const ROOT_PACKAGE_SLOTS = new Set(['app.packages', 'app.module-registry']);
|
|
14
|
+
const SIDE_PREFIX = /^(?:be|fe)\//;
|
|
15
|
+
/** A workspace pattern this reader expands (a literal path, or `*` as a whole segment); the others are judged by the workspace rules. */
|
|
16
|
+
const expandablePattern = (written) => !path.isAbsolute(written) && written.split('/').every(segment => segment !== '..' && (!segment.includes('*') || segment === '*'));
|
|
17
|
+
function expandSegment(base, segment) {
|
|
18
|
+
if (segment !== '*')
|
|
19
|
+
return [path.join(base, segment)];
|
|
20
|
+
if (!fs.existsSync(base) || !fs.lstatSync(base).isDirectory())
|
|
21
|
+
return [];
|
|
22
|
+
return fs.readdirSync(base, { withFileTypes: true }).filter(item => item.isDirectory()).sort((a, b) => byCodeUnit(a.name, b.name)).map(item => path.join(base, item.name));
|
|
23
|
+
}
|
|
24
|
+
/** A package folder with a regular package.json (a link is not one). */
|
|
25
|
+
export function packageDirectory(absolute) {
|
|
26
|
+
try {
|
|
27
|
+
if (!fs.lstatSync(absolute).isDirectory())
|
|
28
|
+
return false;
|
|
29
|
+
const manifest = fs.lstatSync(path.join(absolute, 'package.json'));
|
|
30
|
+
return manifest.isFile() && !manifest.isSymbolicLink();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The folders the `workspaces` of the app root's package.json name that no side owns, as absolute paths in code-unit order. */
|
|
37
|
+
export function rootWorkspaceDirectories(packageRoot) {
|
|
38
|
+
const manifest = readJson(path.join(packageRoot, 'package.json'));
|
|
39
|
+
const declared = manifest?.workspaces;
|
|
40
|
+
const patterns = Array.isArray(declared) ? declared : declared?.packages;
|
|
41
|
+
const found = new Set();
|
|
42
|
+
for (const pattern of Array.isArray(patterns) ? patterns : []) {
|
|
43
|
+
if (typeof pattern !== 'string' || !pattern.trim())
|
|
44
|
+
continue;
|
|
45
|
+
const written = slash(pattern.trim()).replace(/^\.\//, '');
|
|
46
|
+
if (SIDE_PREFIX.test(written) || !expandablePattern(written))
|
|
47
|
+
continue;
|
|
48
|
+
let bases = [packageRoot];
|
|
49
|
+
for (const segment of written.split('/'))
|
|
50
|
+
bases = bases.flatMap(base => expandSegment(base, segment));
|
|
51
|
+
for (const base of bases)
|
|
52
|
+
found.add(path.resolve(base));
|
|
53
|
+
}
|
|
54
|
+
return [...found].sort(byCodeUnit);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The record of one package folder of the app root, or null when it is no named package inside the app root.
|
|
58
|
+
* `canonical` is the file-identity function of the caller (the real path), so a record agrees with the files the graph reads.
|
|
59
|
+
*/
|
|
60
|
+
export function rootPackageRecord(packageRoot, directory, canonical) {
|
|
61
|
+
if (!packageDirectory(directory) || !isInside(packageRoot, directory))
|
|
62
|
+
return null;
|
|
63
|
+
const manifest = readJson(path.join(directory, 'package.json'));
|
|
64
|
+
const name = manifest?.['name'];
|
|
65
|
+
if (manifest === null || typeof name !== 'string' || !name)
|
|
66
|
+
return null;
|
|
67
|
+
return { root: canonical(directory), relative: slash(path.relative(packageRoot, directory)), name, exports: manifest['exports'], manifest, workspace: true, app: false };
|
|
68
|
+
}
|
|
69
|
+
/** The names of the workspace packages of the app root: what the back-end canon must not read as third-party libraries. */
|
|
70
|
+
export function rootPackageNames(packageRoot) {
|
|
71
|
+
const names = new Set();
|
|
72
|
+
for (const directory of rootWorkspaceDirectories(packageRoot)) {
|
|
73
|
+
const record = rootPackageRecord(packageRoot, directory, (file) => file);
|
|
74
|
+
if (record !== null)
|
|
75
|
+
names.add(record.name);
|
|
76
|
+
}
|
|
77
|
+
return names;
|
|
78
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CompilerOptions, CreateProgramOptions, Diagnostic, ModuleResolutionCache, ModuleResolutionHost, Node, Program, ProjectReference, SourceFile, TypeChecker } from 'typescript';
|
|
2
|
-
import type { ArchitectureConfig, TypeScriptApi } from './config.mjs';
|
|
2
|
+
import type { ArchitectureConfig, RootPackage, TypeScriptApi } from './config.mjs';
|
|
3
3
|
import type { ProjectRead } from '../typescript-programs.mjs';
|
|
4
4
|
import type { ArchitectureError, ImportEdge, LoadedTypeScript, ModuleReferences, WorkspaceRecord } from './typescript.mjs';
|
|
5
5
|
/** One parsed tsconfig with its compiler program. */
|
|
@@ -56,5 +56,8 @@ export interface TypeContextHelpers {
|
|
|
56
56
|
relativePath(root: string, fileName: string): string;
|
|
57
57
|
packageExported(ts: TypeScriptApi, workspace: WorkspaceRecord, specifier: string, actualTarget: string): boolean;
|
|
58
58
|
boundaryViolation(root: string, edge: ImportEdge, fromWorkspace: WorkspaceRecord | null, toWorkspace: WorkspaceRecord | null, ruleId: string, message: string): ArchitectureError;
|
|
59
|
+
rootPackageOf(config: Pick<ArchitectureConfig, 'rootPackages'>, specifier: string): RootPackage | null;
|
|
60
|
+
rootPackageEntryDeclared(item: RootPackage, specifier: string): boolean;
|
|
61
|
+
rootPackageEntries(item: RootPackage): string[];
|
|
59
62
|
}
|
|
60
63
|
export declare function contextFromProjects(config: ArchitectureConfig, loaded: LoadedTypeScript, paths: string[], helpers: TypeContextHelpers): TypeContext;
|
|
@@ -241,8 +241,38 @@ function appendEdge(from, sourceFile, project, owningWorkspace, reference, actua
|
|
|
241
241
|
errors.push(helpers.boundaryViolation(config.root, edge, owningWorkspace, targetWorkspace, 'ARCH_PACKAGE_EXPORT_BYPASS', 'Cross-package imports must use the target package name and a declared package export.'));
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* An import of one of the app's root workspace packages (`@scope/agent-sdk`, `@scope/agent-sdk/workbench`). The package lies outside the side
|
|
246
|
+
* folder, yet it is first-party source both sides may import: through its package entry points (the `exports` of its manifest) and never past
|
|
247
|
+
* them. An entry point is no import that leaves the side and adds no edge (the package is not part of this side's graph; the package's own
|
|
248
|
+
* lint judges its files); a path into the package's source is refused. Returns true when the reference was settled here.
|
|
249
|
+
*/
|
|
250
|
+
function rootPackageReference(sourceFile, project, reference, state) {
|
|
251
|
+
const { config, errors, helpers, host, ts } = state;
|
|
252
|
+
if (!config.rootPackages?.length)
|
|
253
|
+
return false;
|
|
254
|
+
const item = helpers.rootPackageOf(config, reference.specifier);
|
|
255
|
+
if (item === null)
|
|
256
|
+
return false;
|
|
257
|
+
if (!helpers.rootPackageEntryDeclared(item, reference.specifier)) {
|
|
258
|
+
errors.push({
|
|
259
|
+
ruleId: 'ARCH_INTERNAL_IMPORT_OUTSIDE',
|
|
260
|
+
project: project.relative,
|
|
261
|
+
path: helpers.relativePath(config.root, sourceFile.fileName),
|
|
262
|
+
...helpers.sourceLocation(sourceFile, reference.node),
|
|
263
|
+
specifier: reference.specifier,
|
|
264
|
+
message: `Internal import ${reference.specifier} reaches into the package ${item.name} past its package entry points; import ${helpers.rootPackageEntries(item).join(', ')} instead.`,
|
|
265
|
+
});
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
// An entry point that a path alias sends outside the package is no entry point: the ordinary boundary judges it.
|
|
269
|
+
const resolved = helpers.resolveTypeScriptModule(ts, reference.specifier, sourceFile.fileName, project.options, host, resolutionCache(state, project));
|
|
270
|
+
return resolved === undefined || helpers.isInside(item.root, helpers.canonical(resolved));
|
|
271
|
+
}
|
|
244
272
|
function processReference(from, sourceFile, project, owningWorkspace, reference, state) {
|
|
245
273
|
const { config, ts, workspaceNames, workspaces, host, errors, helpers } = state;
|
|
274
|
+
if (rootPackageReference(sourceFile, project, reference, state))
|
|
275
|
+
return;
|
|
246
276
|
// A workspace package is read at its source, never at its build: the same import resolves whether or not dist exists.
|
|
247
277
|
const workspaceTarget = workspaces.filter(item => item.root !== helpers.canonical(config.root)).map(item => helpers.workspaceSourceEntry(ts, item, reference.specifier)).find(Boolean);
|
|
248
278
|
const resolvedName = workspaceTarget ?? helpers.resolveTypeScriptModule(ts, reference.specifier, sourceFile.fileName, project.options, host, resolutionCache(state, project));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TypeContext } from './type-context.mjs';
|
|
2
2
|
import type { Node, SourceFile, TypeChecker } from 'typescript';
|
|
3
|
-
import type { ArchitectureConfig, TypeScriptApi } from './config.mjs';
|
|
3
|
+
import type { ArchitectureConfig, RootPackage, TypeScriptApi } from './config.mjs';
|
|
4
4
|
export { unwrapExpression } from '../../lib/ts-analysis.mjs';
|
|
5
5
|
/** The target repository's loaded TypeScript: the API, where it resolved from and its version. */
|
|
6
6
|
export interface LoadedTypeScript {
|
|
@@ -82,6 +82,12 @@ export declare function exportTargetStrings(value: unknown): string[];
|
|
|
82
82
|
export declare function workspaceSourceEntry(ts: TypeScriptApi, workspace: WorkspaceRecord, specifier: string): string | null;
|
|
83
83
|
/** Every source file the package.json `exports` of a workspace package maps (each non-wildcard subpath, dist back to source): its public entries. */
|
|
84
84
|
export declare function workspaceExportSources(ts: TypeScriptApi, workspace: WorkspaceRecord): string[];
|
|
85
|
+
/** The root workspace package of the app (`packages/agent-sdk`) a specifier names, its name or a subpath of it; the longest name when several match. */
|
|
86
|
+
export declare function rootPackageOf(config: Pick<ArchitectureConfig, 'rootPackages'>, specifier: string): RootPackage | null;
|
|
87
|
+
/** Whether a specifier into a root package names one of its entry points: a subpath its `exports` declare, or its root through `exports`, `main`/`types` or `src/index`. A path into its source is not an entry point. */
|
|
88
|
+
export declare function rootPackageEntryDeclared(item: RootPackage, specifier: string): boolean;
|
|
89
|
+
/** The subpaths a root package declares for import (`.` first), for the message of a refused deep import. */
|
|
90
|
+
export declare function rootPackageEntries(item: RootPackage): string[];
|
|
85
91
|
/** Parse configured tsconfigs, then build only selected programs and their project-reference closure. */
|
|
86
92
|
export declare function buildTypeScriptContext(config: ArchitectureConfig, injectedTypeScript?: TypeScriptApi | null, paths?: string[]): TypeContext;
|
|
87
93
|
export declare function relativePath(root: string, fileName: string): string;
|
|
@@ -385,6 +385,26 @@ function packageExported(ts, workspace, specifier, actualTarget) {
|
|
|
385
385
|
return expected === canonical(actualTarget) || sourceOfTarget(ts, workspace, target) === canonical(actualTarget);
|
|
386
386
|
});
|
|
387
387
|
}
|
|
388
|
+
/** The root workspace package of the app (`packages/agent-sdk`) a specifier names, its name or a subpath of it; the longest name when several match. */
|
|
389
|
+
export function rootPackageOf(config, specifier) {
|
|
390
|
+
let found = null;
|
|
391
|
+
for (const item of config.rootPackages ?? [])
|
|
392
|
+
if (sameOrUnder(specifier, item.name) && (found === null || item.name.length > found.name.length))
|
|
393
|
+
found = item;
|
|
394
|
+
return found;
|
|
395
|
+
}
|
|
396
|
+
/** Whether a specifier into a root package names one of its entry points: a subpath its `exports` declare, or its root through `exports`, `main`/`types` or `src/index`. A path into its source is not an entry point. */
|
|
397
|
+
export function rootPackageEntryDeclared(item, specifier) {
|
|
398
|
+
if (exportCandidates(item, specifier).length > 0)
|
|
399
|
+
return true;
|
|
400
|
+
return specifier === item.name && !item.exports && (manifestEntryTargets(item.manifest).length > 0 || packageIndexSource(item) !== null);
|
|
401
|
+
}
|
|
402
|
+
/** The subpaths a root package declares for import (`.` first), for the message of a refused deep import. */
|
|
403
|
+
export function rootPackageEntries(item) {
|
|
404
|
+
const declaration = item.exports;
|
|
405
|
+
const keys = declaration && typeof declaration === 'object' && !Array.isArray(declaration) ? Object.keys(declaration).filter(key => key.startsWith('.')) : ['.'];
|
|
406
|
+
return (keys.length ? keys : ['.']).map(key => (key === '.' ? item.name : `${item.name}${key.slice(1)}`));
|
|
407
|
+
}
|
|
388
408
|
function boundaryViolation(root, edge, fromWorkspace, toWorkspace, ruleId, message) {
|
|
389
409
|
return {
|
|
390
410
|
ruleId,
|
|
@@ -413,7 +433,7 @@ function typeScriptContext(config, loaded, paths) {
|
|
|
413
433
|
canonical, compilerError, isInside, slash, readTypeScriptProject, typeScriptProjectReferencePath,
|
|
414
434
|
sameOrUnder, createTypeScriptProgram, isProductionSource, sourceLocation, workspaceMetadata, workspaceOf,
|
|
415
435
|
pathAliasMatches, moduleReferences, workspaceSourceEntry, resolveTypeScriptModule, crossesSide, assetExtension: ASSET_EXTENSION,
|
|
416
|
-
relativePath, packageExported, boundaryViolation,
|
|
436
|
+
relativePath, packageExported, boundaryViolation, rootPackageOf, rootPackageEntryDeclared, rootPackageEntries,
|
|
417
437
|
});
|
|
418
438
|
}
|
|
419
439
|
export function relativePath(root, fileName) {
|
|
@@ -6,7 +6,7 @@ const HINTS = {
|
|
|
6
6
|
'parameter type': 'Give the parameter a type: `(value: Input)`.',
|
|
7
7
|
'property type': 'Give the property a type: `name: Result = ...`.',
|
|
8
8
|
'accessor type': 'Give the accessor a type: `get name(): Result` (a setter takes it from its parameter).',
|
|
9
|
-
'destructured export': 'A destructuring pattern cannot carry a type per name: export each name as its own variable with a type, `export const name: Type = source.name
|
|
9
|
+
'destructured export': 'A destructuring pattern cannot carry a type per name: export each name as its own variable with a type, `export const name: Type = source.name`; a module definition is `export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<XOptions>()` (or `defineConfigurableModule`) of @starci/nest-kit, which states the types (`starci app codemod module-definition` writes it).',
|
|
10
10
|
'exported value': 'Declare the type (an interface or a type alias) and annotate the export with it: `export const name: Shape = ...`.',
|
|
11
11
|
};
|
|
12
12
|
/** The `--isolatedDeclarations` flag name TypeScript ends its reasons with says nothing to the reader. */
|