@starci/hfs 5.0.8 → 5.2.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 +30 -0
- package/README.md +10 -1
- package/cache/import-graph.mjs +6 -61
- package/cache/lint-family.d.mts +10 -1
- package/cache/lint-family.mjs +12 -5
- package/codemod/directive-first.d.mts +48 -0
- package/codemod/directive-first.mjs +136 -0
- package/codemod/explicit-surface-edit.d.mts +14 -0
- package/codemod/explicit-surface-edit.mjs +57 -0
- package/codemod/explicit-surface-names.d.mts +30 -0
- package/codemod/explicit-surface-names.mjs +135 -0
- package/codemod/explicit-surface-render.d.mts +29 -0
- package/codemod/explicit-surface-render.mjs +197 -0
- package/codemod/explicit-surface.d.mts +50 -0
- package/codemod/explicit-surface.mjs +374 -0
- package/codemod/main.d.mts +7 -5
- package/codemod/main.mjs +37 -16
- package/codemod/source-tree.d.mts +8 -0
- package/codemod/source-tree.mjs +23 -0
- package/db/postgres.mjs +9 -1
- package/lint/eslint-list.cjs +29 -0
- package/lint/run.d.mts +19 -5
- package/lint/run.mjs +47 -14
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
- package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
- package/runtime/knowledge/hfs/fork-images.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +107 -8
- package/runtime/knowledge/hfs/slots.yaml +75 -24
- package/runtime/modules/kernel/failure-codes.yaml +62 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
- package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
- package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
- package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
- package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
- package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
- package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
- package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
- package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
- package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
- package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
- package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
- package/runtime/scripts/hfs/secret.mjs +10 -7
- package/runtime/scripts/hfs/slots.mjs +1 -1
- package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
- package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
- package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
- package/runtime/scripts/lib/declaration-surface.mjs +112 -0
- package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
- package/runtime/scripts/lib/swarm-stack.mjs +2 -2
- package/scaffold/add-module.d.mts +16 -0
- package/scaffold/add-module.mjs +232 -0
- package/scaffold/add-queue-lite.mjs +49 -0
- package/scaffold/add.mjs +5 -0
- package/scaffold/app.mjs +7 -7
- package/scaffold/module-registry.d.mts +19 -0
- package/scaffold/module-registry.mjs +84 -0
- package/src/main.mjs +23 -2
- package/stack/cli.mjs +6 -1
- package/stack/context.d.mts +15 -0
- package/stack/context.mjs +1 -0
- package/stack/local-cli.d.mts +25 -0
- package/stack/local-cli.mjs +89 -0
- package/stack/local-config.d.mts +17 -0
- package/stack/local-config.mjs +37 -0
- package/stack/local-custody.d.mts +24 -0
- package/stack/local-custody.mjs +109 -0
- package/stack/local-doctor.d.mts +6 -0
- package/stack/local-doctor.mjs +49 -0
- package/stack/local-down.d.mts +8 -0
- package/stack/local-down.mjs +20 -0
- package/stack/local-host.d.mts +42 -0
- package/stack/local-host.mjs +163 -0
- package/stack/local-logs.d.mts +8 -0
- package/stack/local-logs.mjs +32 -0
- package/stack/local-ports.d.mts +7 -0
- package/stack/local-ports.mjs +39 -0
- package/stack/local-prepare.d.mts +22 -0
- package/stack/local-prepare.mjs +62 -0
- package/stack/local-run.d.mts +71 -0
- package/stack/local-run.mjs +200 -0
- package/stack/local-state.d.mts +35 -0
- package/stack/local-state.mjs +29 -0
- package/stack/local-status.d.mts +6 -0
- package/stack/local-status.mjs +36 -0
- package/stack/local-up.d.mts +11 -0
- package/stack/local-up.mjs +166 -0
- package/stack/local-verify.d.mts +6 -0
- package/stack/local-verify.mjs +21 -0
- package/sync/index.d.mts +13 -12
- package/sync/index.mjs +83 -28
- package/templates/app/module/jest.config.js.tpl +8 -0
- package/templates/app/module/src/index.ts.tpl +2 -0
- package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
- package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
- package/templates/app/module/src/manifest/index.ts.tpl +1 -0
- package/templates/app/module/src/public/index.ts.tpl +2 -0
- package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
- package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
- package/templates/app/module/src/template/index.ts.tpl +1 -0
- package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
- package/templates/app/module/src/workbench/index.ts.tpl +2 -0
- 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 +76 -70
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
- package/templates/be/inbox/entities.index.ts.tpl +1 -1
- package/templates/be/inbox/module-definition.ts.tpl +8 -2
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
- package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
- package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/table/entities.index.ts.tpl +1 -1
- package/templates/be/table/module-definition.ts.tpl +8 -3
- package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
- package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -3
- package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
- package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
- package/templates/fe/common/app-layout.tsx.partial +1 -1
- package/templates/fe/common/site-shell.tsx.partial +3 -3
- package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
- package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
- package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
- package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
- package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
- package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
- package/templates/fe/units/page/index.tsx.tpl +3 -2
- package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -37
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
const EXTENSIONS = ['', '.ts', '.tsx', '.d.ts', '.mts', '.d.mts', '.cts', '.d.cts', '/index.ts', '/index.tsx', '/index.d.ts'];
|
|
3
|
+
const posix = (file) => file.replaceAll('\\', '/');
|
|
4
|
+
/** The source file of the program a module path (absolute, without its extension) names, or undefined. */
|
|
5
|
+
export function moduleFile(c, modulePath) {
|
|
6
|
+
for (const extension of EXTENSIONS) {
|
|
7
|
+
const found = c.program.getSourceFile(posix(modulePath) + extension);
|
|
8
|
+
if (found !== undefined)
|
|
9
|
+
return found;
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
/** The symbol a module exports under `name`, with aliases resolved, or undefined. */
|
|
14
|
+
export function exportedSymbol(c, file, name) {
|
|
15
|
+
const module = c.checker.getSymbolAtLocation(file);
|
|
16
|
+
if (module === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const found = c.checker.getExportsOfModule(module).find(symbol => symbol.name === name);
|
|
19
|
+
if (found === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
return (found.flags & c.ts.SymbolFlags.Alias) === 0 ? found : c.checker.getAliasedSymbol(found);
|
|
22
|
+
}
|
|
23
|
+
/** The declaration `symbol` stands for: an import resolved to what it imports, a local export to its export symbol, so two names of one declaration compare equal. */
|
|
24
|
+
export function declarationOf(c, symbol) {
|
|
25
|
+
const target = (symbol.flags & c.ts.SymbolFlags.Alias) === 0 ? symbol : c.checker.getAliasedSymbol(symbol);
|
|
26
|
+
return c.checker.getExportSymbolOfSymbol(target);
|
|
27
|
+
}
|
|
28
|
+
/** The key that groups candidate types before the expensive assignability test: the sorted property names. */
|
|
29
|
+
const shapeKey = (names) => [...names].sort().join(',');
|
|
30
|
+
const indexes = new WeakMap();
|
|
31
|
+
/** The exported, non-generic object types of the app's own files (never installed packages or declaration files), by their property names. */
|
|
32
|
+
function namedTypes(c) {
|
|
33
|
+
const known = indexes.get(c.program);
|
|
34
|
+
if (known !== undefined)
|
|
35
|
+
return known;
|
|
36
|
+
const found = new Map();
|
|
37
|
+
for (const file of c.program.getSourceFiles()) {
|
|
38
|
+
if (file.isDeclarationFile || c.program.isSourceFileFromExternalLibrary(file) || file.fileName.includes('/node_modules/'))
|
|
39
|
+
continue;
|
|
40
|
+
const module = c.checker.getSymbolAtLocation(file);
|
|
41
|
+
if (module === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
for (const symbol of c.checker.getExportsOfModule(module)) {
|
|
44
|
+
const target = (symbol.flags & c.ts.SymbolFlags.Alias) === 0 ? symbol : c.checker.getAliasedSymbol(symbol);
|
|
45
|
+
if ((target.flags & (c.ts.SymbolFlags.TypeAlias | c.ts.SymbolFlags.Interface)) === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const type = c.checker.getDeclaredTypeOfSymbol(target);
|
|
48
|
+
const generic = type.typeParameters?.length ?? type.aliasTypeArguments?.length ?? 0;
|
|
49
|
+
if (generic > 0 || (type.flags & c.ts.TypeFlags.Object) === 0)
|
|
50
|
+
continue;
|
|
51
|
+
const key = shapeKey(c.checker.getPropertiesOfType(type).map(property => property.name));
|
|
52
|
+
if (key === '')
|
|
53
|
+
continue;
|
|
54
|
+
found.set(key, [...(found.get(key) ?? []), { name: symbol.name, type, file: file.fileName }]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
indexes.set(c.program, found);
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
/** The names that can be written at `enclosing` without an import, as symbols; what a function body declares is not visible from its signature. */
|
|
61
|
+
export function namesInScope(c, enclosing) {
|
|
62
|
+
const flags = c.ts.SymbolFlags.Type | c.ts.SymbolFlags.Namespace | c.ts.SymbolFlags.Alias | c.ts.SymbolFlags.Value;
|
|
63
|
+
const body = enclosing.body;
|
|
64
|
+
const inBody = (symbol) => body !== undefined && symbol.declarations?.length !== 0 && symbol.declarations?.every(declaration => declaration.pos >= body.pos && declaration.end <= body.end) === true;
|
|
65
|
+
return new Map(c.checker.getSymbolsInScope(enclosing, flags).filter(symbol => !inBody(symbol)).map(symbol => [symbol.name, symbol]));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The one exported type of the app that is interchangeable with the inferred object type `type` (each assignable to the other and the same property names),
|
|
69
|
+
* preferring a name the file already has in scope; undefined when none or more than one fits, so a guess never stands for the inference.
|
|
70
|
+
*/
|
|
71
|
+
function matchingName(c, type, enclosing) {
|
|
72
|
+
if ((type.flags & c.ts.TypeFlags.Object) === 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
const key = shapeKey(c.checker.getPropertiesOfType(type).map(property => property.name));
|
|
75
|
+
const fits = (namedTypes(c).get(key) ?? []).filter(named => c.checker.isTypeAssignableTo(type, named.type) && c.checker.isTypeAssignableTo(named.type, type));
|
|
76
|
+
if (fits.length <= 1)
|
|
77
|
+
return fits[0];
|
|
78
|
+
const scope = namesInScope(c, enclosing);
|
|
79
|
+
const local = fits.filter(named => scope.has(named.name));
|
|
80
|
+
return local.length === 1 ? local[0] : undefined;
|
|
81
|
+
}
|
|
82
|
+
/** The type argument nodes of `node` paired with the type arguments the checker holds for `type`. */
|
|
83
|
+
function argumentsOf(c, type) {
|
|
84
|
+
if ((type.flags & c.ts.TypeFlags.Object) !== 0 && (type.objectFlags & c.ts.ObjectFlags.Reference) !== 0)
|
|
85
|
+
return c.checker.getTypeArguments(type);
|
|
86
|
+
return type.aliasTypeArguments ?? [];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* `node` (the type node the checker wrote for `type`) with every anonymous object type that an exported type of the app stands for replaced by a
|
|
90
|
+
* reference to that type. It follows type arguments (`Promise<{...}>`), arrays and unions; any other anonymous part stays for the caller to refuse.
|
|
91
|
+
*/
|
|
92
|
+
export function nameAnonymousParts(c, node, type, enclosing) {
|
|
93
|
+
const { ts } = c;
|
|
94
|
+
const reference = (named) => c.checker.typeToTypeNode(named.type, enclosing, ts.NodeBuilderFlags.NoTruncation);
|
|
95
|
+
if (ts.isTypeLiteralNode(node) && node.members.length > 0) {
|
|
96
|
+
const named = matchingName(c, type, enclosing);
|
|
97
|
+
return (named === undefined ? undefined : reference(named)) ?? node;
|
|
98
|
+
}
|
|
99
|
+
if (ts.isArrayTypeNode(node) || ts.isTypeReferenceNode(node) || ts.isImportTypeNode(node)) {
|
|
100
|
+
const given = ts.isArrayTypeNode(node) ? [node.elementType] : node.typeArguments ?? [];
|
|
101
|
+
const held = argumentsOf(c, type);
|
|
102
|
+
if (!given.length || held.length < given.length)
|
|
103
|
+
return node;
|
|
104
|
+
const named = given.map((child, at) => nameAnonymousParts(c, child, held[at], enclosing));
|
|
105
|
+
if (named.every((child, at) => child === given[at]))
|
|
106
|
+
return node;
|
|
107
|
+
const { factory } = ts;
|
|
108
|
+
if (ts.isArrayTypeNode(node))
|
|
109
|
+
return factory.updateArrayTypeNode(node, named[0]);
|
|
110
|
+
if (ts.isImportTypeNode(node))
|
|
111
|
+
return factory.updateImportTypeNode(node, node.argument, node.attributes, node.qualifier, named, node.isTypeOf);
|
|
112
|
+
return factory.updateTypeReferenceNode(node, node.typeName, factory.createNodeArray(named));
|
|
113
|
+
}
|
|
114
|
+
if (ts.isUnionTypeNode(node) && type.isUnion()) {
|
|
115
|
+
const members = type.types.filter(member => (member.flags & ts.TypeFlags.Object) !== 0);
|
|
116
|
+
const used = new Set();
|
|
117
|
+
const named = node.types.map(child => {
|
|
118
|
+
if (!ts.isTypeLiteralNode(child))
|
|
119
|
+
return child;
|
|
120
|
+
const key = shapeKey(child.members.map(member => (member.name === undefined ? '' : ts.isIdentifier(member.name) || ts.isStringLiteral(member.name) ? member.name.text : '')));
|
|
121
|
+
const match = members.find(member => !used.has(member) && shapeKey(c.checker.getPropertiesOfType(member).map(property => property.name)) === key);
|
|
122
|
+
if (match === undefined)
|
|
123
|
+
return child;
|
|
124
|
+
used.add(match);
|
|
125
|
+
return nameAnonymousParts(c, child, match, enclosing);
|
|
126
|
+
});
|
|
127
|
+
return named.every((child, at) => child === node.types[at]) ? node : ts.factory.updateUnionTypeNode(node, ts.factory.createNodeArray(named));
|
|
128
|
+
}
|
|
129
|
+
return node;
|
|
130
|
+
}
|
|
131
|
+
/** The directory-relative form of `target` from the folder of `from`, without extension, the way a relative import is written. */
|
|
132
|
+
export function relativeSpecifier(from, target) {
|
|
133
|
+
const relative = posix(path.relative(path.dirname(from), target)).replace(/\.(?:d\.)?[cm]?tsx?$/, '');
|
|
134
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
135
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type NamingContext } from './explicit-surface-names.mjs';
|
|
2
|
+
import type * as TsTypes from 'typescript';
|
|
3
|
+
/** An import the written type needs: `name` exported by the module `specifier`. */
|
|
4
|
+
export interface ImportNeed {
|
|
5
|
+
name: string;
|
|
6
|
+
specifier: string;
|
|
7
|
+
}
|
|
8
|
+
/** A type written as source text, with the imports it needs. */
|
|
9
|
+
export interface Rendered {
|
|
10
|
+
ok: true;
|
|
11
|
+
text: string;
|
|
12
|
+
needs: ImportNeed[];
|
|
13
|
+
}
|
|
14
|
+
/** Why a type is not written: a code of the report, the reason, and what a person does about it. */
|
|
15
|
+
export interface Refused {
|
|
16
|
+
ok: false;
|
|
17
|
+
code: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
suggestion: string;
|
|
20
|
+
}
|
|
21
|
+
/** The longest type the codemod writes on one line; a longer one is a type a person names. */
|
|
22
|
+
export declare const MAX_TYPE_LENGTH = 100;
|
|
23
|
+
/**
|
|
24
|
+
* The type Next.js names for a route export (`generateMetadata`: `Promise<Metadata>`, `metadata`: `Metadata`, and the viewport pair), when `next` is installed and the inferred
|
|
25
|
+
* type is assignable to it; undefined for any other export, so the inferred type is written instead.
|
|
26
|
+
*/
|
|
27
|
+
export declare function conventionType(c: NamingContext, name: string, inferred: TsTypes.Type, enclosing: TsTypes.Node, kind: 'function' | 'variable'): Rendered | undefined;
|
|
28
|
+
/** Writes `type`, as it is seen from `enclosing`, as source text: named types by name, the imports they need, or the reason it is left to a person. */
|
|
29
|
+
export declare function renderType(c: NamingContext, type: TsTypes.Type, enclosing: TsTypes.Node, maxLength?: number): Rendered | Refused;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { declarationOf, exportedSymbol, moduleFile, nameAnonymousParts, namesInScope, relativeSpecifier } from './explicit-surface-names.mjs';
|
|
3
|
+
/** The longest type the codemod writes on one line; a longer one is a type a person names. */
|
|
4
|
+
export const MAX_TYPE_LENGTH = 100;
|
|
5
|
+
const refuse = (code, reason, suggestion) => ({ ok: false, code, reason, suggestion });
|
|
6
|
+
const posix = (file) => file.replaceAll('\\', '/');
|
|
7
|
+
const ABSOLUTE = /^(?:[A-Za-z]:)?\//;
|
|
8
|
+
const JSX_ELEMENT = new Set(['JSX.Element', 'React.JSX.Element']);
|
|
9
|
+
/** The text of an entity name (`A.B.C`). */
|
|
10
|
+
function entityText(ts, name) {
|
|
11
|
+
return ts.isIdentifier(name) ? name.text : `${entityText(ts, name.left)}.${name.right.text}`;
|
|
12
|
+
}
|
|
13
|
+
/** The leftmost identifier of an entity name. */
|
|
14
|
+
function rootIdentifier(ts, name) {
|
|
15
|
+
return ts.isIdentifier(name) ? name : rootIdentifier(ts, name.left);
|
|
16
|
+
}
|
|
17
|
+
/** The package specifier of a file under `node_modules` (`@types/react` is `react`, `@types/a__b` is `@a/b`), and the path inside it. */
|
|
18
|
+
function packageOf(file) {
|
|
19
|
+
const at = file.lastIndexOf('/node_modules/');
|
|
20
|
+
if (at < 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
const parts = file.slice(at + '/node_modules/'.length).split('/');
|
|
23
|
+
const scoped = parts[0]?.startsWith('@') === true;
|
|
24
|
+
let name = parts.slice(0, scoped ? 2 : 1).join('/');
|
|
25
|
+
const typed = /^@types\/(.+)$/.exec(name);
|
|
26
|
+
if (typed?.[1] !== undefined)
|
|
27
|
+
name = typed[1].includes('__') ? `@${typed[1].replace('__', '/')}` : typed[1];
|
|
28
|
+
return { name, inner: parts.slice(scoped ? 2 : 1).join('/').replace(/\.(?:d\.)?[cm]?tsx?$/, '').replace(/\/?index$/, '') };
|
|
29
|
+
}
|
|
30
|
+
/** The specifiers an import of `target` from `c.file` can be written with, best first: a package name, a path alias the file already uses, a relative path. */
|
|
31
|
+
function specifierCandidates(c, target) {
|
|
32
|
+
const installed = packageOf(posix(target.fileName));
|
|
33
|
+
if (installed !== undefined)
|
|
34
|
+
return [installed.name, ...(installed.inner ? [`${installed.name}/${installed.inner}`] : [])];
|
|
35
|
+
const relative = relativeSpecifier(c.file, target.fileName);
|
|
36
|
+
const nodeNext = c.options.moduleResolution === c.ts.ModuleResolutionKind.NodeNext || c.options.moduleResolution === c.ts.ModuleResolutionKind.Node16;
|
|
37
|
+
const extension = /\.([cm]?)tsx?$/.exec(target.fileName);
|
|
38
|
+
const written = nodeNext ? `${relative}.${extension?.[1] === 'm' ? 'mjs' : extension?.[1] === 'c' ? 'cjs' : 'js'}` : relative;
|
|
39
|
+
const aliases = [];
|
|
40
|
+
const base = c.options.baseUrl ?? c.options['pathsBasePath'] ?? path.dirname(c.file);
|
|
41
|
+
const bare = posix(target.fileName).replace(/\.(?:d\.)?[cm]?tsx?$/, '');
|
|
42
|
+
for (const [pattern, targets] of Object.entries(c.options.paths ?? {})) {
|
|
43
|
+
for (const to of targets) {
|
|
44
|
+
const [head = '', tail = ''] = to.split('*');
|
|
45
|
+
const prefix = posix(path.resolve(base, head)) + (head.endsWith('/') ? '/' : '');
|
|
46
|
+
if (to.includes('*') && bare.startsWith(prefix) && bare.endsWith(tail))
|
|
47
|
+
aliases.push(pattern.replace('*', bare.slice(prefix.length, bare.length - tail.length)));
|
|
48
|
+
else if (!to.includes('*') && bare === posix(path.resolve(base, to)).replace(/\.(?:d\.)?[cm]?tsx?$/, ''))
|
|
49
|
+
aliases.push(pattern);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const usesAlias = c.source.statements.some(statement => c.ts.isImportDeclaration(statement) && c.ts.isStringLiteral(statement.moduleSpecifier)
|
|
53
|
+
&& Object.keys(c.options.paths ?? {}).some(pattern => statement.moduleSpecifier.getText(c.source).slice(1, -1).startsWith(pattern.replace('*', ''))));
|
|
54
|
+
return usesAlias ? [...aliases, written] : [written, ...aliases];
|
|
55
|
+
}
|
|
56
|
+
/** Whether `specifier`, written in `c.file`, reaches the same declaration of `name` that `target` holds. */
|
|
57
|
+
function reaches(c, specifier, name, target) {
|
|
58
|
+
const resolved = c.ts.resolveModuleName(specifier, c.file, c.options, c.ts.sys).resolvedModule?.resolvedFileName;
|
|
59
|
+
const file = resolved === undefined ? undefined : c.program.getSourceFile(posix(resolved));
|
|
60
|
+
return file !== undefined && exportedSymbol(c, file, name) === target;
|
|
61
|
+
}
|
|
62
|
+
/** The module file a raw specifier of the checker's import type names: an absolute path without extension, or a module name. */
|
|
63
|
+
function targetModule(c, raw) {
|
|
64
|
+
if (ABSOLUTE.test(raw))
|
|
65
|
+
return moduleFile(c, raw);
|
|
66
|
+
const resolved = c.ts.resolveModuleName(raw, c.file, c.options, c.ts.sys).resolvedModule?.resolvedFileName;
|
|
67
|
+
return resolved === undefined ? undefined : c.program.getSourceFile(posix(resolved));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How `name`, exported by the module the checker named with `raw`, is written in the file: an import specifier to add, `null` when the file already has the
|
|
71
|
+
* name bound to that very declaration, or a refusal when the name is taken by something else or no specifier of the file reaches it.
|
|
72
|
+
*/
|
|
73
|
+
function resolveNeed(c, scope, name, raw) {
|
|
74
|
+
const file = targetModule(c, raw);
|
|
75
|
+
const target = file === undefined ? undefined : exportedSymbol(c, file, name);
|
|
76
|
+
if (file === undefined || target === undefined)
|
|
77
|
+
return refuse('TYPE_UNNAMEABLE', `\`${name}\` is not exported by a module the file can import`, `export ${name} from its module, or declare a named type in this file`);
|
|
78
|
+
const taken = scope.get(name);
|
|
79
|
+
if (taken !== undefined) {
|
|
80
|
+
if (declarationOf(c, taken) === declarationOf(c, target))
|
|
81
|
+
return { specifier: null };
|
|
82
|
+
const global = taken.declarations?.every(declaration => !c.ts.isExternalModule(declaration.getSourceFile())) === true;
|
|
83
|
+
if (!global || new RegExp(`\\b${name}\\b`).test(c.text))
|
|
84
|
+
return refuse('TYPE_UNNAMEABLE', `\`${name}\` is already another name in this file`, `rename one of the two, or declare a named type in this file`);
|
|
85
|
+
}
|
|
86
|
+
const specifier = specifierCandidates(c, file).find(candidate => reaches(c, candidate, name, target));
|
|
87
|
+
return specifier === undefined ? refuse('TYPE_UNNAMEABLE', `no import path of this file reaches \`${name}\` (${posix(file.fileName)})`, `export ${name} from a module this file can import, or declare a named type in this file`) : { specifier };
|
|
88
|
+
}
|
|
89
|
+
/** Writes the checker's type node as source: import types become names plus imports, `JSX.Element` becomes `ReactElement`, and anonymous, `any` and non-nameable parts are refused. */
|
|
90
|
+
function rewrite(c, node, scope, needs) {
|
|
91
|
+
const { ts } = c;
|
|
92
|
+
let refused;
|
|
93
|
+
const need = (name, raw) => {
|
|
94
|
+
const resolved = resolveNeed(c, scope, name, raw);
|
|
95
|
+
if ('ok' in resolved)
|
|
96
|
+
refused ??= resolved;
|
|
97
|
+
else if (resolved.specifier !== null && !needs.some(entry => entry.name === name && entry.specifier === resolved.specifier))
|
|
98
|
+
needs.push({ name, specifier: resolved.specifier });
|
|
99
|
+
};
|
|
100
|
+
// The checker names a type by an identifier that carries its symbol: a name the file cannot write is imported from the module that exports it.
|
|
101
|
+
const named = (id) => {
|
|
102
|
+
const symbol = id.symbol;
|
|
103
|
+
if (symbol === undefined)
|
|
104
|
+
return;
|
|
105
|
+
if ((symbol.flags & ts.SymbolFlags.TypeParameter) !== 0)
|
|
106
|
+
return;
|
|
107
|
+
const taken = scope.get(id.text);
|
|
108
|
+
if (taken !== undefined && declarationOf(c, taken) === declarationOf(c, symbol))
|
|
109
|
+
return;
|
|
110
|
+
const file = symbol.declarations?.[0]?.getSourceFile();
|
|
111
|
+
if (file !== undefined && ts.isExternalModule(file))
|
|
112
|
+
need(id.text, file.fileName);
|
|
113
|
+
};
|
|
114
|
+
const result = ts.transform(node, [context => root => {
|
|
115
|
+
const visit = (child) => {
|
|
116
|
+
if (ts.isImportTypeNode(child)) {
|
|
117
|
+
const literal = ts.isLiteralTypeNode(child.argument) && ts.isStringLiteral(child.argument.literal) ? child.argument.literal.text : undefined;
|
|
118
|
+
if (child.isTypeOf || child.qualifier === undefined || literal === undefined) {
|
|
119
|
+
refused ??= refuse('TYPE_UNNAMEABLE', 'the type is a module or a `typeof import(...)`', 'declare a named type and annotate with it');
|
|
120
|
+
return child;
|
|
121
|
+
}
|
|
122
|
+
const args = child.typeArguments?.map(argument => visit(argument));
|
|
123
|
+
if (JSX_ELEMENT.has(entityText(ts, child.qualifier))) {
|
|
124
|
+
need('ReactElement', 'react');
|
|
125
|
+
return context.factory.createTypeReferenceNode('ReactElement');
|
|
126
|
+
}
|
|
127
|
+
need(rootIdentifier(ts, child.qualifier).text, literal);
|
|
128
|
+
return context.factory.createTypeReferenceNode(child.qualifier, args);
|
|
129
|
+
}
|
|
130
|
+
if (ts.isTypeReferenceNode(child) && JSX_ELEMENT.has(entityText(ts, child.typeName))) {
|
|
131
|
+
need('ReactElement', 'react');
|
|
132
|
+
return context.factory.createTypeReferenceNode('ReactElement');
|
|
133
|
+
}
|
|
134
|
+
if (ts.isTypeReferenceNode(child))
|
|
135
|
+
named(rootIdentifier(ts, child.typeName));
|
|
136
|
+
if (ts.isTypeQueryNode(child))
|
|
137
|
+
named(rootIdentifier(ts, child.exprName));
|
|
138
|
+
if (child.kind === ts.SyntaxKind.AnyKeyword) {
|
|
139
|
+
refused ??= refuse('TYPE_ANY', 'the inferred type contains `any`', 'write the type the value really has; an `any` on a surface hides the contract');
|
|
140
|
+
return child;
|
|
141
|
+
}
|
|
142
|
+
if (ts.isTypeLiteralNode(child) && child.members.length > 0) {
|
|
143
|
+
refused ??= refuse('TYPE_ANONYMOUS', 'the inferred type is an anonymous object type', 'declare a named type (`type X = ...`), export it and annotate with it');
|
|
144
|
+
return child;
|
|
145
|
+
}
|
|
146
|
+
if (ts.isMappedTypeNode(child) || ts.isConditionalTypeNode(child) || (ts.isTypeOperatorNode(child) && child.operator === ts.SyntaxKind.UniqueKeyword)) {
|
|
147
|
+
refused ??= refuse('TYPE_ANONYMOUS', 'the inferred type is computed (mapped, conditional or `unique symbol`)', 'declare a named type for it and annotate with it');
|
|
148
|
+
return child;
|
|
149
|
+
}
|
|
150
|
+
return ts.visitEachChild(child, visit, context);
|
|
151
|
+
};
|
|
152
|
+
return visit(root);
|
|
153
|
+
}]);
|
|
154
|
+
return refused ?? { node: result.transformed[0] };
|
|
155
|
+
}
|
|
156
|
+
/** The Next.js export conventions whose type the framework names: the export, its type in `next`, and whether it is a function. */
|
|
157
|
+
const CONVENTIONS = new Map([
|
|
158
|
+
['generateMetadata', { type: 'Metadata', kind: 'function' }], ['metadata', { type: 'Metadata', kind: 'variable' }],
|
|
159
|
+
['generateViewport', { type: 'Viewport', kind: 'function' }], ['viewport', { type: 'Viewport', kind: 'variable' }],
|
|
160
|
+
]);
|
|
161
|
+
/**
|
|
162
|
+
* The type Next.js names for a route export (`generateMetadata`: `Promise<Metadata>`, `metadata`: `Metadata`, and the viewport pair), when `next` is installed and the inferred
|
|
163
|
+
* type is assignable to it; undefined for any other export, so the inferred type is written instead.
|
|
164
|
+
*/
|
|
165
|
+
export function conventionType(c, name, inferred, enclosing, kind) {
|
|
166
|
+
const row = CONVENTIONS.get(name);
|
|
167
|
+
const file = row?.kind === kind ? targetModule(c, 'next') : undefined;
|
|
168
|
+
const symbol = row === undefined || file === undefined ? undefined : exportedSymbol(c, file, row.type);
|
|
169
|
+
if (row === undefined || symbol === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
const awaited = kind === 'function' ? c.checker.getAwaitedType(inferred) : inferred;
|
|
172
|
+
if (awaited === undefined || !c.checker.isTypeAssignableTo(awaited, c.checker.getDeclaredTypeOfSymbol(symbol)))
|
|
173
|
+
return undefined;
|
|
174
|
+
const resolved = resolveNeed(c, namesInScope(c, enclosing), row.type, 'next');
|
|
175
|
+
if ('ok' in resolved)
|
|
176
|
+
return undefined;
|
|
177
|
+
const written = awaited === inferred ? row.type : `Promise<${row.type}>`;
|
|
178
|
+
return { ok: true, text: written, needs: resolved.specifier === null ? [] : [{ name: row.type, specifier: resolved.specifier }] };
|
|
179
|
+
}
|
|
180
|
+
/** Writes `type`, as it is seen from `enclosing`, as source text: named types by name, the imports they need, or the reason it is left to a person. */
|
|
181
|
+
export function renderType(c, type, enclosing, maxLength = MAX_TYPE_LENGTH) {
|
|
182
|
+
const { ts } = c;
|
|
183
|
+
if ((type.flags & ts.TypeFlags.Any) !== 0)
|
|
184
|
+
return refuse('TYPE_ANY', 'the inferred type is `any`', 'write the type the value really has; an `any` on a surface hides the contract');
|
|
185
|
+
const flags = ts.NodeBuilderFlags.NoTruncation;
|
|
186
|
+
const drawn = c.checker.typeToTypeNode(type, enclosing, flags | ts.NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) ?? c.checker.typeToTypeNode(type, enclosing, flags);
|
|
187
|
+
if (drawn === undefined)
|
|
188
|
+
return refuse('TYPE_UNNAMEABLE', 'the compiler cannot write the type', 'declare a named type and annotate with it');
|
|
189
|
+
const needs = [];
|
|
190
|
+
const rewritten = rewrite(c, nameAnonymousParts(c, drawn, type, enclosing), namesInScope(c, enclosing), needs);
|
|
191
|
+
if ('ok' in rewritten)
|
|
192
|
+
return rewritten;
|
|
193
|
+
const text = ts.createPrinter({ removeComments: true }).printNode(ts.EmitHint.Unspecified, rewritten.node, c.source);
|
|
194
|
+
if (text.length > maxLength)
|
|
195
|
+
return refuse('TYPE_TOO_LONG', `the type is ${text.length} characters (${text.slice(0, 60)}...)`, 'declare a named type (`type X = ...`), export it and annotate with it');
|
|
196
|
+
return { ok: true, text, needs };
|
|
197
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** The TypeScript compiler API of the app's own install (loadTypescript), typed by the version this package develops against. */
|
|
2
|
+
type Ts = typeof import('typescript');
|
|
3
|
+
/** One annotation written: the type of an export that was inferred. */
|
|
4
|
+
export interface ExplicitSurfaceChange {
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
code: string;
|
|
9
|
+
name: string;
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
}
|
|
13
|
+
/** One export left for a person, with why and what to do. */
|
|
14
|
+
export interface ExplicitSurfaceFinding {
|
|
15
|
+
file: string;
|
|
16
|
+
line: number;
|
|
17
|
+
column: number;
|
|
18
|
+
code: string;
|
|
19
|
+
name: string;
|
|
20
|
+
annotate: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
suggestion: string;
|
|
23
|
+
}
|
|
24
|
+
/** The report of a migration (`starci app codemod explicit-surface`). */
|
|
25
|
+
export interface ExplicitSurfaceReport {
|
|
26
|
+
schema: string;
|
|
27
|
+
root: string;
|
|
28
|
+
write: boolean;
|
|
29
|
+
maxTypeLength: number;
|
|
30
|
+
filesScanned: number;
|
|
31
|
+
filesOpen: number;
|
|
32
|
+
filesChanged: string[];
|
|
33
|
+
filesStillOpen: number;
|
|
34
|
+
formatted: boolean;
|
|
35
|
+
changes: ExplicitSurfaceChange[];
|
|
36
|
+
findings: ExplicitSurfaceFinding[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Preview or apply the explicit type annotations of one app: for every TypeScript source of each side that the declaration classifier counts as open
|
|
40
|
+
* (R368, HFS_EXPLICIT_EXPORT_SURFACE), the types the app's own checker infers are written where they are short, named and sound. Every other export is kept in the report.
|
|
41
|
+
*/
|
|
42
|
+
export declare function migrateExplicitSurface({ root, write, ts: given, maxTypeLength }: {
|
|
43
|
+
root: string;
|
|
44
|
+
write?: boolean;
|
|
45
|
+
ts?: Ts | null;
|
|
46
|
+
maxTypeLength?: number;
|
|
47
|
+
}): Promise<ExplicitSurfaceReport>;
|
|
48
|
+
/** Render the annotations and what is left for a person as Markdown; JSON consumers read the same report object. */
|
|
49
|
+
export declare function explicitSurfaceMarkdown(report: ExplicitSurfaceReport): string;
|
|
50
|
+
export {};
|