create-windy 0.2.7 → 0.2.8
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/README.md +6 -0
- package/dist/cli.js +180 -87
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/server/Dockerfile +2 -0
- package/template/apps/server/package.json +10 -8
- package/template/apps/server/src/access-guards.ts +1 -1
- package/template/apps/server/src/admin/routes.test.ts +10 -0
- package/template/apps/server/src/admin/routes.ts +4 -2
- package/template/apps/server/src/agent/routes.test.ts +1 -1
- package/template/apps/server/src/agent/routes.ts +2 -2
- package/template/apps/server/src/application-services.ts +3 -0
- package/template/apps/server/src/audit/drizzle-log-store.ts +2 -2
- package/template/apps/server/src/audit/policy.ts +1 -1
- package/template/apps/server/src/audit/recovery-queue.test.ts +1 -1
- package/template/apps/server/src/audit/recovery-queue.ts +1 -1
- package/template/apps/server/src/audit/redaction.ts +1 -1
- package/template/apps/server/src/audit/reliable-writer.test.ts +1 -1
- package/template/apps/server/src/audit/reliable-writer.ts +1 -1
- package/template/apps/server/src/audit/search-summary.ts +1 -1
- package/template/apps/server/src/auth/authentication-provider.test.ts +1 -1
- package/template/apps/server/src/auth/credential-service.ts +1 -1
- package/template/apps/server/src/auth/drizzle-repository.ts +2 -2
- package/template/apps/server/src/auth/drizzle-session-operations.ts +1 -1
- package/template/apps/server/src/auth/own-session-routes.test.ts +1 -1
- package/template/apps/server/src/auth/password.ts +2 -2
- package/template/apps/server/src/auth/repository.ts +1 -1
- package/template/apps/server/src/auth/routes.test.ts +1 -1
- package/template/apps/server/src/auth/service.ts +1 -1
- package/template/apps/server/src/bulk-data/artifact-store.ts +1 -1
- package/template/apps/server/src/bulk-data/dictionary-handler.ts +1 -1
- package/template/apps/server/src/bulk-data/drizzle-repository.ts +3 -3
- package/template/apps/server/src/bulk-data/repository.ts +1 -1
- package/template/apps/server/src/bulk-data/routes.test.ts +1 -1
- package/template/apps/server/src/bulk-data/routes.ts +1 -1
- package/template/apps/server/src/bulk-data/service.test.ts +1 -1
- package/template/apps/server/src/bulk-data/service.ts +1 -1
- package/template/apps/server/src/bulk-data/types.ts +1 -1
- package/template/apps/server/src/configuration/bootstrap.ts +2 -2
- package/template/apps/server/src/configuration/definition.ts +2 -2
- package/template/apps/server/src/configuration/drizzle-repository.ts +2 -2
- package/template/apps/server/src/configuration/repository.ts +1 -1
- package/template/apps/server/src/configuration/routes.ts +1 -1
- package/template/apps/server/src/configuration/service.ts +1 -1
- package/template/apps/server/src/data-access/context.ts +2 -2
- package/template/apps/server/src/data-access/query-specification.test.ts +1 -1
- package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +1 -1
- package/template/apps/server/src/data-access/scoped-repository.ts +1 -1
- package/template/apps/server/src/data-access/scoped-resource.test.ts +1 -1
- package/template/apps/server/src/data-access/scoped-resource.ts +1 -1
- package/template/apps/server/src/data-governance/export/approval-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -1
- package/template/apps/server/src/data-governance/export/job-runtime.test.ts +2 -2
- package/template/apps/server/src/data-governance/export/job-runtime.ts +2 -3
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +2 -2
- package/template/apps/server/src/data-governance/export/postgres-api.integration.test.ts +1 -1
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +3 -3
- package/template/apps/server/src/data-governance/export/routes.ts +1 -1
- package/template/apps/server/src/data-governance/export/scope-snapshot.ts +1 -1
- package/template/apps/server/src/data-governance/export/service.ts +2 -2
- package/template/apps/server/src/data-governance/export/source.ts +1 -1
- package/template/apps/server/src/data-governance/export/types.ts +1 -1
- package/template/apps/server/src/data-governance/plaintext-access.test.ts +1 -1
- package/template/apps/server/src/data-governance/plaintext-access.ts +1 -1
- package/template/apps/server/src/data-governance/plaintext-postgres.integration.test.ts +1 -1
- package/template/apps/server/src/data-governance/user-plaintext-routes.ts +1 -1
- package/template/apps/server/src/data-governance/user-projection.ts +2 -2
- package/template/apps/server/src/example-modules.ts +25 -1
- package/template/apps/server/src/feature/manifest-feature-repository.test.ts +1 -1
- package/template/apps/server/src/feature/manifest-feature-repository.ts +1 -1
- package/template/apps/server/src/feature/routes.ts +3 -6
- package/template/apps/server/src/foundation-access.test.ts +1 -1
- package/template/apps/server/src/foundation-access.ts +1 -1
- package/template/apps/server/src/foundation.ts +21 -7
- package/template/apps/server/src/guards.test.ts +1 -1
- package/template/apps/server/src/guards.ts +3 -3
- package/template/apps/server/src/index.ts +27 -3
- package/template/{packages/modules → apps/server}/src/installed-business-modules.ts +2 -2
- package/template/apps/server/src/jobs/artifact-service.test.ts +1 -1
- package/template/apps/server/src/jobs/artifact-service.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-artifact.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-claim.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-execution-repository.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-lease.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-mappers.ts +3 -3
- package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-repository.integration.test.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-repository.ts +3 -3
- package/template/apps/server/src/jobs/execution-repository.ts +1 -1
- package/template/apps/server/src/jobs/execution.test.ts +2 -2
- package/template/apps/server/src/jobs/in-memory-claim.ts +1 -1
- package/template/apps/server/src/jobs/in-memory-execution.ts +1 -1
- package/template/apps/server/src/jobs/in-memory-lease.ts +1 -1
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -1
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +1 -1
- package/template/apps/server/src/jobs/legacy-adapter.ts +1 -1
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +4 -2
- package/template/apps/server/src/jobs/repository.ts +2 -2
- package/template/apps/server/src/jobs/service.ts +2 -2
- package/template/apps/server/src/jobs/worker.ts +3 -2
- package/template/apps/server/src/license/legacy-license-resolver.ts +1 -1
- package/template/apps/server/src/license/license-resolver.test.ts +1 -1
- package/template/apps/server/src/license/license-resolver.ts +3 -3
- package/template/apps/server/src/license/routes-offline-code.test.ts +2 -2
- package/template/apps/server/src/license/routes.ts +1 -1
- package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -2
- package/template/apps/server/src/license/runtime-reminder.test.ts +2 -2
- package/template/apps/server/src/license/runtime-service.test.ts +2 -2
- package/template/apps/server/src/license/runtime-service.ts +2 -2
- package/template/apps/server/src/module-composition.test.ts +1 -1
- package/template/apps/server/src/module-composition.ts +1 -1
- package/template/apps/server/src/module-host.test.ts +145 -0
- package/template/apps/server/src/module-host.ts +110 -0
- package/template/apps/server/src/notification/drizzle-repository.ts +2 -2
- package/template/apps/server/src/notification/repository.ts +1 -1
- package/template/apps/server/src/notification/routes.ts +1 -1
- package/template/apps/server/src/pagination.ts +1 -1
- package/template/apps/server/src/persistence.integration.test.ts +1 -1
- package/template/apps/server/src/persistence.ts +1 -1
- package/template/apps/server/src/profile/routes.ts +1 -1
- package/template/apps/server/src/route-guards.test.ts +1 -1
- package/template/apps/server/src/route-guards.ts +1 -1
- package/template/apps/server/src/runtime-feature.ts +125 -0
- package/template/apps/server/src/runtime-license.ts +1 -1
- package/template/apps/server/src/runtime.test.ts +48 -1
- package/template/apps/server/src/runtime.ts +17 -64
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/definitions.test.ts +1 -1
- package/template/apps/server/src/scheduler/definitions.ts +2 -2
- package/template/apps/server/src/scheduler/drizzle-durable-links.ts +1 -1
- package/template/apps/server/src/scheduler/drizzle-mappers.ts +1 -1
- package/template/apps/server/src/scheduler/drizzle-repository.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +1 -1
- package/template/apps/server/src/scheduler/history.ts +1 -1
- package/template/apps/server/src/scheduler/overview.ts +1 -1
- package/template/apps/server/src/scheduler/recovery-service.test.ts +1 -1
- package/template/apps/server/src/scheduler/recovery-service.ts +1 -1
- package/template/apps/server/src/scheduler/repository.ts +1 -1
- package/template/apps/server/src/scheduler/routes.ts +1 -1
- package/template/apps/server/src/scheduler/service-idle.test.ts +1 -1
- package/template/apps/server/src/scheduler/service.test.ts +1 -1
- package/template/apps/server/src/scheduler/service.ts +1 -1
- package/template/apps/server/src/scheduler/types.ts +1 -1
- package/template/apps/server/src/search/contracts.ts +2 -2
- package/template/apps/server/src/search/host.ts +1 -1
- package/template/apps/server/src/search/registry.ts +1 -1
- package/template/apps/server/src/search/routes.test.ts +1 -1
- package/template/apps/server/src/search/routes.ts +1 -1
- package/template/apps/server/src/search/service.ts +2 -2
- package/template/apps/server/src/search/test-support.ts +2 -2
- package/template/apps/server/src/session/routes.ts +1 -1
- package/template/apps/server/src/settings/drizzle-repository.ts +2 -2
- package/template/apps/server/src/settings/repository.ts +1 -1
- package/template/apps/server/src/settings/routes.test.ts +1 -1
- package/template/apps/server/src/settings/routes.ts +1 -1
- package/template/apps/server/src/settings/runtime.ts +1 -1
- package/template/apps/server/src/storage/blob-reference-registry.test.ts +1 -1
- package/template/apps/server/src/storage/blob-reference-registry.ts +1 -1
- package/template/apps/server/src/storage/drizzle-upload-repository.integration.test.ts +1 -1
- package/template/apps/server/src/storage/drizzle-upload-repository.ts +2 -2
- package/template/apps/server/src/storage/route-support.ts +1 -1
- package/template/apps/server/src/storage/routes.test.ts +1 -1
- package/template/apps/server/src/storage/routes.ts +2 -2
- package/template/apps/server/src/storage/runtime.ts +6 -4
- package/template/apps/server/src/system/audit-routes.ts +1 -1
- package/template/apps/server/src/system/department-routes.ts +1 -1
- package/template/apps/server/src/system/drizzle-repository.ts +1 -1
- package/template/apps/server/src/system/drizzle-scoped-repository.ts +1 -1
- package/template/apps/server/src/system/drizzle-system.ts +1 -1
- package/template/apps/server/src/system/entities.ts +1 -1
- package/template/apps/server/src/system/identity-route-config.ts +1 -1
- package/template/apps/server/src/system/interface-catalog.test.ts +3 -3
- package/template/apps/server/src/system/interface-catalog.ts +2 -2
- package/template/apps/server/src/system/menu-registry.ts +1 -1
- package/template/apps/server/src/system/menu-routes.ts +1 -1
- package/template/apps/server/src/system/operations.test.ts +1 -1
- package/template/apps/server/src/system/operations.ts +1 -1
- package/template/apps/server/src/system/redis-health.ts +1 -1
- package/template/apps/server/src/system/repository.ts +1 -1
- package/template/apps/server/src/system/resource-route-support.ts +1 -1
- package/template/apps/server/src/system/route-helpers.ts +1 -1
- package/template/apps/server/src/system/route-types.ts +1 -1
- package/template/apps/server/src/system/routes.test.ts +1 -1
- package/template/apps/server/src/system/routes.ts +1 -1
- package/template/apps/server/src/system/user-projection.ts +1 -1
- package/template/apps/server/src/work-order/data-scope.integration.test.ts +1 -1
- package/template/apps/server/src/work-order/drizzle-repository.integration.test.ts +1 -1
- package/template/apps/server/src/work-order/drizzle-repository.ts +2 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +15 -5
- package/template/apps/server/src/work-order/repository.ts +1 -1
- package/template/apps/server/src/work-order/routes.test.ts +2 -2
- package/template/apps/server/src/work-order/routes.ts +2 -2
- package/template/apps/server/src/work-order/search-api.test.ts +3 -3
- package/template/apps/server/src/work-order/search-provider.test.ts +3 -3
- package/template/apps/server/src/work-order/service.ts +1 -1
- package/template/apps/server/src/work-order/task.test.ts +1 -1
- package/template/apps/server/src/work-order/task.ts +2 -2
- package/template/apps/server/src/workflow/drizzle-repository.integration.test.ts +2 -2
- package/template/apps/server/src/workflow/drizzle-repository.ts +3 -3
- package/template/apps/server/src/workflow/effects.test.ts +1 -1
- package/template/apps/server/src/workflow/effects.ts +1 -1
- package/template/apps/server/src/workflow/repository.ts +2 -2
- package/template/apps/server/src/workflow/routes.ts +3 -3
- package/template/apps/server/src/workflow/service.ts +1 -1
- package/template/apps/server/src/workflow/timeout-runtime.test.ts +1 -1
- package/template/apps/server/src/workflow/timeout-runtime.ts +2 -2
- package/template/apps/web/Dockerfile +2 -0
- package/template/apps/web/package.json +4 -4
- package/template/apps/web/runtime-server.ts +1 -1
- package/template/apps/web/src/app/layouts/AppLayout.vue +6 -0
- package/template/apps/web/src/components/auth/PasswordChangeForm.vue +1 -1
- package/template/apps/web/src/components/common/DataPagination.vue +1 -1
- package/template/apps/web/src/components/license/LicenseActivationCodeForm.vue +1 -1
- package/template/apps/web/src/components/license/LicenseFlow.webtest.ts +2 -2
- package/template/apps/web/src/composables/useModuleConfigurations.ts +1 -1
- package/template/apps/web/src/composables/useWatermarkSettings.ts +1 -0
- package/template/apps/web/src/composables/watermark-settings.ts +6 -0
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +27 -1
- package/template/apps/web/src/layout/AppSidebar.vue +12 -3
- package/template/apps/web/src/layout/AppSidebar.webtest.ts +11 -0
- package/template/apps/web/src/layout/GlobalWatermark.vue +25 -8
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +109 -0
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +1 -1
- package/template/apps/web/src/layout/NotificationMenu.vue +1 -1
- package/template/apps/web/src/layout/navigation.ts +5 -1
- package/template/apps/web/src/layout/navigation.webtest.ts +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigRollbackDialog.vue +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.webtest.ts +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +1 -1
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.webtest.ts +1 -1
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +21 -0
- package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +2 -2
- package/template/apps/web/src/pages/system/components/AuditActionCell.vue +7 -2
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +7 -2
- package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerOperationsPanel.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerOperationsPanel.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerRunTable.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerRunTable.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerStaleRecoveryForm.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerStaleRecoveryForm.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +1 -1
- package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +10 -3
- package/template/apps/web/src/pages/system/components/role-policy/RolePermissionTree.vue +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/RolePermissionTree.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/RolePolicyForm.vue +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/permission-tree.ts +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/permission-tree.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/composables/useSchedulerOperations.ts +2 -2
- package/template/apps/web/src/pages/system/composables/useSchedulerOperations.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +1 -1
- package/template/apps/web/src/router/manifest-routes.ts +1 -1
- package/template/apps/web/src/router/manifest-routes.webtest.ts +1 -1
- package/template/apps/web/src/services/access-api.ts +10 -1
- package/template/apps/web/src/services/configuration-api.ts +1 -1
- package/template/apps/web/src/services/license-api.ts +1 -1
- package/template/apps/web/src/services/notification-api.ts +2 -2
- package/template/apps/web/src/services/operations-api.ts +1 -1
- package/template/apps/web/src/services/platform-settings-api.ts +2 -2
- package/template/apps/web/src/services/profile-api.ts +2 -2
- package/template/docs/architecture/durable-jobs.md +5 -0
- package/template/docs/architecture/module-manifest-composition.md +14 -7
- package/template/docs/architecture/object-storage.md +1 -1
- package/template/docs/development/crud-generator.md +4 -1
- package/template/docs/platform/audit-reliability.md +1 -1
- package/template/package.json +2 -1
- package/template/packages/config/package.json +15 -4
- package/template/packages/config/src/platform.ts +1 -1
- package/template/packages/crud-generator/package.json +1 -1
- package/template/packages/crud-generator/src/generator.test.ts +16 -0
- package/template/packages/crud-generator/src/templates/domain.ts +1 -1
- package/template/packages/crud-generator/src/templates/manifest.ts +2 -2
- package/template/packages/crud-generator/src/templates/project.ts +3 -3
- package/template/packages/crud-generator/src/templates/repository.ts +1 -1
- package/template/packages/crud-generator/src/templates/routes.ts +2 -2
- package/template/packages/crud-generator/src/validation.ts +22 -1
- package/template/packages/database/package.json +15 -5
- package/template/packages/database/src/bundle-validation.ts +218 -0
- package/template/packages/database/src/dialects.ts +1 -1
- package/template/packages/database/src/runner.test.ts +160 -13
- package/template/packages/database/src/runner.ts +95 -77
- package/template/packages/database/src/schema/governed-exports.ts +1 -1
- package/template/packages/database/src/schema/identity.ts +1 -1
- package/template/packages/database/src/schema/license-v1.ts +1 -1
- package/template/packages/database/src/schema/notifications.ts +1 -1
- package/template/packages/database/src/schema/platform-settings.ts +1 -1
- package/template/packages/database/src/schema/workflow.ts +1 -1
- package/template/packages/database/src/sql-store.ts +1 -1
- package/template/packages/database/src/store.ts +1 -1
- package/template/packages/example-work-order/index.ts +1 -0
- package/template/packages/example-work-order/package.json +4 -3
- package/template/packages/example-work-order/src/contract.ts +1 -1
- package/template/packages/example-work-order/src/manifest.ts +8 -3
- package/template/packages/example-work-order/src/migration-bundle.test.ts +43 -0
- package/template/packages/example-work-order/src/migration-bundle.ts +64 -0
- package/template/packages/jobs/index.ts +2 -0
- package/template/packages/jobs/package.json +29 -0
- package/template/packages/jobs/src/handlers.test.ts +72 -0
- package/template/{apps/server/src/jobs/handler-registry.ts → packages/jobs/src/handlers.ts} +4 -4
- package/template/{apps/server/src/jobs → packages/jobs/src}/types.ts +7 -1
- package/template/packages/{license-sdk → jobs}/tsconfig.json +0 -1
- package/template/packages/modules/index.ts +1 -1
- package/template/packages/modules/package.json +15 -5
- package/template/packages/modules/src/catalog-validation.ts +123 -0
- package/template/packages/modules/src/composition.test.ts +75 -2
- package/template/packages/modules/src/composition.ts +4 -2
- package/template/packages/modules/src/manifest.ts +7 -12
- package/template/packages/modules/src/menu-composition.ts +1 -1
- package/template/packages/modules/src/system-api-binding.ts +1 -1
- package/template/packages/modules/src/system-api-data-governance.ts +1 -1
- package/template/packages/modules/src/system-api-permissions.ts +1 -1
- package/template/packages/modules/src/system-data-governance.ts +1 -1
- package/template/packages/modules/src/system-features.ts +1 -1
- package/template/packages/modules/src/system-governed-export-bindings.ts +1 -1
- package/template/packages/modules/src/system-module-catalog.ts +1 -1
- package/template/packages/server-sdk/index.ts +5 -0
- package/template/packages/server-sdk/package.json +25 -0
- package/template/packages/server-sdk/src/module-host.ts +29 -0
- package/template/packages/server-sdk/tsconfig.json +16 -0
- package/template/packages/shared/index.ts +2 -0
- package/template/packages/shared/package.json +14 -3
- package/template/packages/shared/src/audit.ts +22 -1
- package/template/packages/shared/src/license-catalog.test.ts +17 -0
- package/template/packages/shared/src/license-catalog.ts +33 -0
- package/template/packages/shared/src/migration.ts +39 -0
- package/template/packages/storage/package.json +14 -3
- package/template/packages/license-sdk/index.ts +0 -13
- package/template/packages/license-sdk/package.json +0 -18
- package/template/packages/license-sdk/src/bounded-json-body.test.ts +0 -65
- package/template/packages/license-sdk/src/bounded-json-body.ts +0 -135
- package/template/packages/license-sdk/src/canonical-json.test.ts +0 -28
- package/template/packages/license-sdk/src/canonical-json.ts +0 -104
- package/template/packages/license-sdk/src/client.test.ts +0 -82
- package/template/packages/license-sdk/src/client.ts +0 -86
- package/template/packages/license-sdk/src/device.ts +0 -46
- package/template/packages/license-sdk/src/file-storage.test.ts +0 -80
- package/template/packages/license-sdk/src/file-storage.ts +0 -77
- package/template/packages/license-sdk/src/installation.test.ts +0 -241
- package/template/packages/license-sdk/src/installation.ts +0 -150
- package/template/packages/license-sdk/src/license-parser.ts +0 -56
- package/template/packages/license-sdk/src/offline-code-base32.ts +0 -57
- package/template/packages/license-sdk/src/offline-code-decompression.ts +0 -55
- package/template/packages/license-sdk/src/offline-code-limits.ts +0 -10
- package/template/packages/license-sdk/src/offline-code-wire.ts +0 -72
- package/template/packages/license-sdk/src/offline-code.test.ts +0 -172
- package/template/packages/license-sdk/src/offline-code.ts +0 -319
- package/template/packages/license-sdk/src/payload.test.ts +0 -72
- package/template/packages/license-sdk/src/payload.ts +0 -201
- package/template/packages/license-sdk/src/storage.ts +0 -64
- package/template/packages/license-sdk/src/test-fixtures.ts +0 -59
- package/template/packages/license-sdk/src/v1-test-vector.ts +0 -49
- package/template/packages/license-sdk/src/validation.test.ts +0 -62
- package/template/packages/license-sdk/src/validation.ts +0 -143
- package/template/packages/license-sdk/src/verification.test.ts +0 -97
- package/template/packages/license-sdk/src/verification.ts +0 -199
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export interface DeviceCodeParts {
|
|
2
|
-
hostname?: string;
|
|
3
|
-
platform?: string;
|
|
4
|
-
arch?: string;
|
|
5
|
-
tenant?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function createDeviceCode(parts: DeviceCodeParts): string {
|
|
9
|
-
const stableParts = [
|
|
10
|
-
parts.hostname || "unknown-host",
|
|
11
|
-
parts.platform || "unknown-platform",
|
|
12
|
-
parts.arch || "unknown-arch",
|
|
13
|
-
parts.tenant || "default",
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
return btoaSafe(stableParts.join("|"));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function createDeviceQrPayload(machineCode: string): string {
|
|
20
|
-
return JSON.stringify({
|
|
21
|
-
type: "windy-device-code",
|
|
22
|
-
version: 1,
|
|
23
|
-
machineCode,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function readDeviceCodeFromQrPayload(
|
|
28
|
-
payload: string,
|
|
29
|
-
): string | undefined {
|
|
30
|
-
try {
|
|
31
|
-
const parsed = JSON.parse(payload) as { machineCode?: unknown };
|
|
32
|
-
return typeof parsed.machineCode === "string"
|
|
33
|
-
? parsed.machineCode
|
|
34
|
-
: undefined;
|
|
35
|
-
} catch {
|
|
36
|
-
return payload.trim() || undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function btoaSafe(value: string): string {
|
|
41
|
-
if (typeof btoa === "function") {
|
|
42
|
-
return btoa(value);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return Buffer.from(value, "utf8").toString("base64");
|
|
46
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { WindyLicenseClient } from "./client.js";
|
|
6
|
-
import {
|
|
7
|
-
FileSystemAtomicLicenseStore,
|
|
8
|
-
FixedPathLicenseSource,
|
|
9
|
-
} from "./file-storage.js";
|
|
10
|
-
import { MemoryLicenseStorage } from "./storage.js";
|
|
11
|
-
import { LICENSE_V1_TEST_VECTOR } from "./v1-test-vector.js";
|
|
12
|
-
|
|
13
|
-
const temporaryDirectories: string[] = [];
|
|
14
|
-
|
|
15
|
-
afterEach(async () => {
|
|
16
|
-
await Promise.all(
|
|
17
|
-
temporaryDirectories
|
|
18
|
-
.splice(0)
|
|
19
|
-
.map((directory) => rm(directory, { force: true, recursive: true })),
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("filesystem License adapters", () => {
|
|
24
|
-
test("固定路径挂载复用统一 V1 校验器", async () => {
|
|
25
|
-
const directory = await temporaryDirectory();
|
|
26
|
-
const path = join(directory, "runtime", "current.license.json");
|
|
27
|
-
const store = new FileSystemAtomicLicenseStore(path);
|
|
28
|
-
await store.replaceCurrentAtomically(
|
|
29
|
-
JSON.stringify({
|
|
30
|
-
formatVersion: 1,
|
|
31
|
-
algorithm: "ED25519",
|
|
32
|
-
publicKeyId: LICENSE_V1_TEST_VECTOR.keyId,
|
|
33
|
-
claims: LICENSE_V1_TEST_VECTOR.claims,
|
|
34
|
-
signature: LICENSE_V1_TEST_VECTOR.signature,
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const client = new WindyLicenseClient({
|
|
39
|
-
licenseSource: new FixedPathLicenseSource(path),
|
|
40
|
-
stateStorage: new MemoryLicenseStorage(),
|
|
41
|
-
productCode: "windy-platform",
|
|
42
|
-
machineCode: "machine-vector-001",
|
|
43
|
-
publicKeys: {
|
|
44
|
-
[LICENSE_V1_TEST_VECTOR.keyId]: LICENSE_V1_TEST_VECTOR.publicKeyPem,
|
|
45
|
-
},
|
|
46
|
-
now: () => new Date("2026-07-10T10:00:00.000Z"),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
await expect(client.recordOpen()).resolves.toMatchObject({
|
|
50
|
-
valid: true,
|
|
51
|
-
failures: [],
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("模拟原子提交前崩溃时旧文件保持完整且临时文件被清理", async () => {
|
|
56
|
-
const directory = await temporaryDirectory();
|
|
57
|
-
const path = join(directory, "current.license.json");
|
|
58
|
-
await writeFile(path, '{"license":"old"}', "utf8");
|
|
59
|
-
const store = new CrashBeforeCommitStore(path);
|
|
60
|
-
|
|
61
|
-
await expect(
|
|
62
|
-
store.replaceCurrentAtomically('{"license":"new"}'),
|
|
63
|
-
).rejects.toThrow("模拟进程崩溃");
|
|
64
|
-
|
|
65
|
-
await expect(store.readCurrent()).resolves.toBe('{"license":"old"}');
|
|
66
|
-
expect(await readdir(directory)).toEqual(["current.license.json"]);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
class CrashBeforeCommitStore extends FileSystemAtomicLicenseStore {
|
|
71
|
-
protected override async beforeCommit(): Promise<void> {
|
|
72
|
-
throw new Error("模拟进程崩溃");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function temporaryDirectory(): Promise<string> {
|
|
77
|
-
const directory = await mkdtemp(join(tmpdir(), "windy-license-sdk-"));
|
|
78
|
-
temporaryDirectories.push(directory);
|
|
79
|
-
return directory;
|
|
80
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
4
|
-
import type { AtomicLicenseStore, LicenseSource } from "./storage.js";
|
|
5
|
-
|
|
6
|
-
export class FixedPathLicenseSource implements LicenseSource {
|
|
7
|
-
constructor(protected readonly licensePath: string) {}
|
|
8
|
-
|
|
9
|
-
async readCurrent(): Promise<string | undefined> {
|
|
10
|
-
try {
|
|
11
|
-
return await readFile(this.licensePath, "utf8");
|
|
12
|
-
} catch (error) {
|
|
13
|
-
if (isMissingFile(error)) return undefined;
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Atomic filesystem adapter. Temporary data is synced before the same-directory
|
|
21
|
-
* rename; the directory is synced afterwards so a crash cannot expose a partial file.
|
|
22
|
-
*/
|
|
23
|
-
export class FileSystemAtomicLicenseStore
|
|
24
|
-
extends FixedPathLicenseSource
|
|
25
|
-
implements AtomicLicenseStore
|
|
26
|
-
{
|
|
27
|
-
constructor(
|
|
28
|
-
licensePath: string,
|
|
29
|
-
private readonly fileMode = 0o600,
|
|
30
|
-
) {
|
|
31
|
-
super(licensePath);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async replaceCurrentAtomically(value: string): Promise<void> {
|
|
35
|
-
const directory = dirname(this.licensePath);
|
|
36
|
-
const temporaryPath = join(
|
|
37
|
-
directory,
|
|
38
|
-
`.${basename(this.licensePath)}.${process.pid}.${randomUUID()}.tmp`,
|
|
39
|
-
);
|
|
40
|
-
await mkdir(directory, { recursive: true });
|
|
41
|
-
|
|
42
|
-
let temporaryFile: Awaited<ReturnType<typeof open>> | undefined;
|
|
43
|
-
try {
|
|
44
|
-
temporaryFile = await open(temporaryPath, "wx", this.fileMode);
|
|
45
|
-
await temporaryFile.writeFile(value, "utf8");
|
|
46
|
-
await temporaryFile.sync();
|
|
47
|
-
await temporaryFile.close();
|
|
48
|
-
temporaryFile = undefined;
|
|
49
|
-
await this.beforeCommit();
|
|
50
|
-
await rename(temporaryPath, this.licensePath);
|
|
51
|
-
await syncDirectory(directory);
|
|
52
|
-
} finally {
|
|
53
|
-
await temporaryFile?.close().catch(() => undefined);
|
|
54
|
-
await unlink(temporaryPath).catch(() => undefined);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Test seam for simulating a process failure before the atomic commit. */
|
|
59
|
-
protected async beforeCommit(): Promise<void> {}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function syncDirectory(directory: string): Promise<void> {
|
|
63
|
-
const handle = await open(directory, "r");
|
|
64
|
-
try {
|
|
65
|
-
await handle.sync();
|
|
66
|
-
} finally {
|
|
67
|
-
await handle.close();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function isMissingFile(error: unknown): boolean {
|
|
72
|
-
return (
|
|
73
|
-
error instanceof Error &&
|
|
74
|
-
"code" in error &&
|
|
75
|
-
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
76
|
-
);
|
|
77
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { licenseVersionKey, type LicenseV1Claims } from "@windy/shared";
|
|
3
|
-
import { LicenseInstaller } from "./installation.js";
|
|
4
|
-
import type { AtomicLicenseStore } from "./storage.js";
|
|
5
|
-
import { MemoryAtomicLicenseStore } from "./storage.js";
|
|
6
|
-
import { createSignedLicenseV1 } from "./test-fixtures.js";
|
|
7
|
-
|
|
8
|
-
function claims(overrides: Partial<LicenseV1Claims> = {}): LicenseV1Claims {
|
|
9
|
-
return {
|
|
10
|
-
licenseId: "license_1",
|
|
11
|
-
productCode: "windy-platform",
|
|
12
|
-
customer: { id: "customer_1", code: "C-1", name: "客户一" },
|
|
13
|
-
project: { id: "project_1", code: "P-1", name: "项目一" },
|
|
14
|
-
contract: { id: "contract_1", contractNumber: "HT-1", version: "1" },
|
|
15
|
-
machineCode: "machine_1",
|
|
16
|
-
issuedAt: "2026-07-10T00:00:00.000Z",
|
|
17
|
-
expiresAt: "2030-07-10T00:00:00.000Z",
|
|
18
|
-
licenseVersion: {
|
|
19
|
-
key: licenseVersionKey("standard"),
|
|
20
|
-
name: "普通付费版",
|
|
21
|
-
},
|
|
22
|
-
...overrides,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function installer(
|
|
27
|
-
store: AtomicLicenseStore,
|
|
28
|
-
publicKeys: Record<string, string>,
|
|
29
|
-
trustedReplacementAuthority?: ConstructorParameters<
|
|
30
|
-
typeof LicenseInstaller
|
|
31
|
-
>[0]["trustedReplacementAuthority"],
|
|
32
|
-
trustedInvalidCurrentRecoveryAuthority?: ConstructorParameters<
|
|
33
|
-
typeof LicenseInstaller
|
|
34
|
-
>[0]["trustedInvalidCurrentRecoveryAuthority"],
|
|
35
|
-
): LicenseInstaller {
|
|
36
|
-
return new LicenseInstaller({
|
|
37
|
-
store,
|
|
38
|
-
productCode: "windy-platform",
|
|
39
|
-
machineCode: "machine_1",
|
|
40
|
-
publicKeys,
|
|
41
|
-
now: new Date("2026-08-01T00:00:00.000Z"),
|
|
42
|
-
trustedReplacementAuthority,
|
|
43
|
-
trustedInvalidCurrentRecoveryAuthority,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe("LicenseInstaller", () => {
|
|
48
|
-
test("签发时间更新的 License 立即替换,即使新到期时间更短", async () => {
|
|
49
|
-
const current = createSignedLicenseV1(claims(), "key_current");
|
|
50
|
-
const candidate = createSignedLicenseV1(
|
|
51
|
-
claims({
|
|
52
|
-
licenseId: "license_2",
|
|
53
|
-
issuedAt: "2026-07-20T00:00:00.000Z",
|
|
54
|
-
expiresAt: "2027-07-20T00:00:00.000Z",
|
|
55
|
-
licenseVersion: {
|
|
56
|
-
key: licenseVersionKey("advanced"),
|
|
57
|
-
name: "高级付费版",
|
|
58
|
-
},
|
|
59
|
-
}),
|
|
60
|
-
"key_candidate",
|
|
61
|
-
);
|
|
62
|
-
const store = new MemoryAtomicLicenseStore(JSON.stringify(current.license));
|
|
63
|
-
|
|
64
|
-
const result = await installer(store, {
|
|
65
|
-
key_current: current.publicKeyPem,
|
|
66
|
-
key_candidate: candidate.publicKeyPem,
|
|
67
|
-
}).install(JSON.stringify(candidate.license));
|
|
68
|
-
|
|
69
|
-
expect(result.status).toBe("installed");
|
|
70
|
-
expect(result.license?.claims.expiresAt).toBe("2027-07-20T00:00:00.000Z");
|
|
71
|
-
expect(JSON.parse((await store.readCurrent())!).claims.licenseId).toBe(
|
|
72
|
-
"license_2",
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("历史 License 不能覆盖签发时间更晚的当前 License", async () => {
|
|
77
|
-
const current = createSignedLicenseV1(
|
|
78
|
-
claims({
|
|
79
|
-
licenseId: "license_new",
|
|
80
|
-
issuedAt: "2026-07-20T00:00:00.000Z",
|
|
81
|
-
}),
|
|
82
|
-
"key_current",
|
|
83
|
-
);
|
|
84
|
-
const old = createSignedLicenseV1(
|
|
85
|
-
claims({
|
|
86
|
-
licenseId: "license_old",
|
|
87
|
-
issuedAt: "2026-07-10T00:00:00.000Z",
|
|
88
|
-
}),
|
|
89
|
-
"key_old",
|
|
90
|
-
);
|
|
91
|
-
const original = JSON.stringify(current.license);
|
|
92
|
-
const store = new MemoryAtomicLicenseStore(original);
|
|
93
|
-
|
|
94
|
-
const result = await installer(store, {
|
|
95
|
-
key_current: current.publicKeyPem,
|
|
96
|
-
key_old: old.publicKeyPem,
|
|
97
|
-
}).install(JSON.stringify(old.license));
|
|
98
|
-
|
|
99
|
-
expect(result).toMatchObject({
|
|
100
|
-
status: "rejected",
|
|
101
|
-
failures: ["license-not-newer"],
|
|
102
|
-
});
|
|
103
|
-
expect(await store.readCurrent()).toBe(original);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("篡改 signed issuedAt 不能伪装成新 License", async () => {
|
|
107
|
-
const current = createSignedLicenseV1(claims(), "key_current");
|
|
108
|
-
const candidate = createSignedLicenseV1(
|
|
109
|
-
claims({ licenseId: "license_2", issuedAt: "2026-07-05T00:00:00.000Z" }),
|
|
110
|
-
"key_candidate",
|
|
111
|
-
);
|
|
112
|
-
const original = JSON.stringify(current.license);
|
|
113
|
-
const store = new MemoryAtomicLicenseStore(original);
|
|
114
|
-
const tampered = {
|
|
115
|
-
...candidate.license,
|
|
116
|
-
claims: {
|
|
117
|
-
...candidate.license.claims,
|
|
118
|
-
issuedAt: "2026-07-20T00:00:00.000Z",
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const result = await installer(store, {
|
|
123
|
-
key_current: current.publicKeyPem,
|
|
124
|
-
key_candidate: candidate.publicKeyPem,
|
|
125
|
-
}).install(JSON.stringify(tampered));
|
|
126
|
-
|
|
127
|
-
expect(result.failures).toEqual(["invalid-signature"]);
|
|
128
|
-
expect(await store.readCurrent()).toBe(original);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("可信替换关系可显式授权非递增 issuedAt 的替换", async () => {
|
|
132
|
-
const current = createSignedLicenseV1(
|
|
133
|
-
claims({
|
|
134
|
-
licenseId: "license_current",
|
|
135
|
-
issuedAt: "2026-07-20T00:00:00.000Z",
|
|
136
|
-
}),
|
|
137
|
-
"key_current",
|
|
138
|
-
);
|
|
139
|
-
const replacement = createSignedLicenseV1(
|
|
140
|
-
claims({
|
|
141
|
-
licenseId: "license_replacement",
|
|
142
|
-
issuedAt: "2026-07-19T00:00:00.000Z",
|
|
143
|
-
}),
|
|
144
|
-
"key_replacement",
|
|
145
|
-
);
|
|
146
|
-
const store = new MemoryAtomicLicenseStore(JSON.stringify(current.license));
|
|
147
|
-
|
|
148
|
-
const result = await installer(
|
|
149
|
-
store,
|
|
150
|
-
{
|
|
151
|
-
key_current: current.publicKeyPem,
|
|
152
|
-
key_replacement: replacement.publicKeyPem,
|
|
153
|
-
},
|
|
154
|
-
({ current: installed, candidate }) =>
|
|
155
|
-
installed.claims.licenseId === "license_current" &&
|
|
156
|
-
candidate.claims.licenseId === "license_replacement",
|
|
157
|
-
).install(JSON.stringify(replacement.license));
|
|
158
|
-
|
|
159
|
-
expect(result.status).toBe("installed");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("未知公钥和写入失败都不覆盖当前 License", async () => {
|
|
163
|
-
const current = createSignedLicenseV1(claims(), "key_current");
|
|
164
|
-
const candidate = createSignedLicenseV1(
|
|
165
|
-
claims({ licenseId: "license_2", issuedAt: "2026-07-20T00:00:00.000Z" }),
|
|
166
|
-
"key_unknown",
|
|
167
|
-
);
|
|
168
|
-
const original = JSON.stringify(current.license);
|
|
169
|
-
const unknownKeyStore = new MemoryAtomicLicenseStore(original);
|
|
170
|
-
|
|
171
|
-
const unknownKeyResult = await installer(unknownKeyStore, {
|
|
172
|
-
key_current: current.publicKeyPem,
|
|
173
|
-
}).install(JSON.stringify(candidate.license));
|
|
174
|
-
expect(unknownKeyResult.failures).toContain("unknown-public-key");
|
|
175
|
-
expect(await unknownKeyStore.readCurrent()).toBe(original);
|
|
176
|
-
|
|
177
|
-
const failingStore = new FailingAtomicStore(original, false);
|
|
178
|
-
const storageResult = await installer(failingStore, {
|
|
179
|
-
key_current: current.publicKeyPem,
|
|
180
|
-
key_unknown: candidate.publicKeyPem,
|
|
181
|
-
}).install(JSON.stringify(candidate.license));
|
|
182
|
-
expect(storageResult.failures).toEqual(["storage-failure"]);
|
|
183
|
-
expect(await failingStore.readCurrent()).toBe(original);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("提交完成后适配器报错时通过回读确认新文件完整", async () => {
|
|
187
|
-
const candidate = createSignedLicenseV1(claims(), "key_candidate");
|
|
188
|
-
const store = new FailingAtomicStore(undefined, true);
|
|
189
|
-
|
|
190
|
-
const result = await installer(store, {
|
|
191
|
-
key_candidate: candidate.publicKeyPem,
|
|
192
|
-
}).install(JSON.stringify(candidate.license));
|
|
193
|
-
|
|
194
|
-
expect(result.status).toBe("installed");
|
|
195
|
-
expect(JSON.parse((await store.readCurrent())!).claims.licenseId).toBe(
|
|
196
|
-
"license_1",
|
|
197
|
-
);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test("损坏 current 只在可信恢复授权后由同一安装器原子替换", async () => {
|
|
201
|
-
const candidate = createSignedLicenseV1(claims(), "key_candidate");
|
|
202
|
-
const store = new MemoryAtomicLicenseStore("{broken");
|
|
203
|
-
const publicKeys = { key_candidate: candidate.publicKeyPem };
|
|
204
|
-
|
|
205
|
-
const rejected = await installer(store, publicKeys).install(
|
|
206
|
-
JSON.stringify(candidate.license),
|
|
207
|
-
);
|
|
208
|
-
expect(rejected.failures).toEqual(["current-license-invalid"]);
|
|
209
|
-
expect(await store.readCurrent()).toBe("{broken");
|
|
210
|
-
|
|
211
|
-
const recovered = await installer(
|
|
212
|
-
store,
|
|
213
|
-
publicKeys,
|
|
214
|
-
undefined,
|
|
215
|
-
({ rawCurrent, candidate: verifiedCandidate }) =>
|
|
216
|
-
rawCurrent === "{broken" &&
|
|
217
|
-
verifiedCandidate.claims.licenseId === "license_1",
|
|
218
|
-
).install(JSON.stringify(candidate.license));
|
|
219
|
-
|
|
220
|
-
expect(recovered.status).toBe("installed");
|
|
221
|
-
expect(JSON.parse((await store.readCurrent())!).claims.licenseId).toBe(
|
|
222
|
-
"license_1",
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
class FailingAtomicStore implements AtomicLicenseStore {
|
|
228
|
-
constructor(
|
|
229
|
-
private value: string | undefined,
|
|
230
|
-
private readonly commitBeforeFailure: boolean,
|
|
231
|
-
) {}
|
|
232
|
-
|
|
233
|
-
readCurrent(): string | undefined {
|
|
234
|
-
return this.value;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
replaceCurrentAtomically(value: string): never {
|
|
238
|
-
if (this.commitBeforeFailure) this.value = value;
|
|
239
|
-
throw new Error("模拟磁盘写入失败");
|
|
240
|
-
}
|
|
241
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { LicenseFailureReason, SignedLicense } from "@windy/shared";
|
|
2
|
-
import { canonicalizeJson } from "./canonical-json.js";
|
|
3
|
-
import type { AtomicLicenseStore } from "./storage.js";
|
|
4
|
-
import type { LicenseValidationOptions } from "./validation.js";
|
|
5
|
-
import {
|
|
6
|
-
validateLicenseDocument,
|
|
7
|
-
verifyLicenseDocument,
|
|
8
|
-
} from "./validation.js";
|
|
9
|
-
|
|
10
|
-
export type LicenseInstallFailure =
|
|
11
|
-
| LicenseFailureReason
|
|
12
|
-
| "current-license-invalid"
|
|
13
|
-
| "license-not-newer"
|
|
14
|
-
| "storage-failure";
|
|
15
|
-
|
|
16
|
-
export interface LicenseInstallResult {
|
|
17
|
-
status: "installed" | "unchanged" | "rejected";
|
|
18
|
-
license?: SignedLicense;
|
|
19
|
-
failures: LicenseInstallFailure[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type TrustedReplacementAuthority = (input: {
|
|
23
|
-
current: SignedLicense;
|
|
24
|
-
candidate: SignedLicense;
|
|
25
|
-
}) => boolean | Promise<boolean>;
|
|
26
|
-
|
|
27
|
-
export type TrustedInvalidCurrentRecoveryAuthority = (input: {
|
|
28
|
-
rawCurrent: string;
|
|
29
|
-
candidate: SignedLicense;
|
|
30
|
-
}) => boolean | Promise<boolean>;
|
|
31
|
-
|
|
32
|
-
export interface LicenseInstallerOptions extends Omit<
|
|
33
|
-
LicenseValidationOptions,
|
|
34
|
-
"lastTrustedAt"
|
|
35
|
-
> {
|
|
36
|
-
store: AtomicLicenseStore;
|
|
37
|
-
/** Must resolve from authenticated, locally trusted replacement metadata. */
|
|
38
|
-
trustedReplacementAuthority?: TrustedReplacementAuthority;
|
|
39
|
-
/** Must only be enabled by an authenticated, audited local recovery flow. */
|
|
40
|
-
trustedInvalidCurrentRecoveryAuthority?: TrustedInvalidCurrentRecoveryAuthority;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class LicenseInstaller {
|
|
44
|
-
constructor(private readonly options: LicenseInstallerOptions) {}
|
|
45
|
-
|
|
46
|
-
async install(rawCandidate: string): Promise<LicenseInstallResult> {
|
|
47
|
-
const candidateResult = await validateLicenseDocument(rawCandidate, {
|
|
48
|
-
productCode: this.options.productCode,
|
|
49
|
-
machineCode: this.options.machineCode,
|
|
50
|
-
publicKeys: this.options.publicKeys,
|
|
51
|
-
now: this.options.now,
|
|
52
|
-
clockToleranceMs: this.options.clockToleranceMs,
|
|
53
|
-
});
|
|
54
|
-
const candidate = candidateResult.license;
|
|
55
|
-
if (!candidateResult.valid || !candidate) {
|
|
56
|
-
return {
|
|
57
|
-
status: "rejected",
|
|
58
|
-
failures: candidateResult.failures,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let rawCurrent: string | undefined;
|
|
63
|
-
try {
|
|
64
|
-
rawCurrent = await this.options.store.readCurrent();
|
|
65
|
-
} catch {
|
|
66
|
-
return { status: "rejected", failures: ["storage-failure"] };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (rawCurrent) {
|
|
70
|
-
const currentResult = await verifyLicenseDocument(
|
|
71
|
-
rawCurrent,
|
|
72
|
-
this.options.publicKeys,
|
|
73
|
-
);
|
|
74
|
-
const current = currentResult.license;
|
|
75
|
-
if (!currentResult.valid || !current) {
|
|
76
|
-
const recoveryAllowed = Boolean(
|
|
77
|
-
await this.options.trustedInvalidCurrentRecoveryAuthority?.({
|
|
78
|
-
rawCurrent,
|
|
79
|
-
candidate,
|
|
80
|
-
}),
|
|
81
|
-
);
|
|
82
|
-
if (!recoveryAllowed) {
|
|
83
|
-
return {
|
|
84
|
-
status: "rejected",
|
|
85
|
-
failures: ["current-license-invalid"],
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
if (sameLicense(current, candidate)) {
|
|
90
|
-
return { status: "unchanged", license: current, failures: [] };
|
|
91
|
-
}
|
|
92
|
-
if (!(await this.isNewerOrReplacement(current, candidate))) {
|
|
93
|
-
return {
|
|
94
|
-
status: "rejected",
|
|
95
|
-
license: current,
|
|
96
|
-
failures: ["license-not-newer"],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const serializedCandidate = canonicalizeJson(candidate);
|
|
103
|
-
try {
|
|
104
|
-
await this.options.store.replaceCurrentAtomically(serializedCandidate);
|
|
105
|
-
return { status: "installed", license: candidate, failures: [] };
|
|
106
|
-
} catch {
|
|
107
|
-
return this.resolveUncertainCommit(candidate);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private async isNewerOrReplacement(
|
|
112
|
-
current: SignedLicense,
|
|
113
|
-
candidate: SignedLicense,
|
|
114
|
-
): Promise<boolean> {
|
|
115
|
-
if (
|
|
116
|
-
Date.parse(candidate.claims.issuedAt) >
|
|
117
|
-
Date.parse(current.claims.issuedAt)
|
|
118
|
-
) {
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
return Boolean(
|
|
122
|
-
await this.options.trustedReplacementAuthority?.({ current, candidate }),
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private async resolveUncertainCommit(
|
|
127
|
-
candidate: SignedLicense,
|
|
128
|
-
): Promise<LicenseInstallResult> {
|
|
129
|
-
try {
|
|
130
|
-
const committedRaw = await this.options.store.readCurrent();
|
|
131
|
-
const committed = committedRaw
|
|
132
|
-
? await verifyLicenseDocument(committedRaw, this.options.publicKeys)
|
|
133
|
-
: undefined;
|
|
134
|
-
if (committed?.license && sameLicense(committed.license, candidate)) {
|
|
135
|
-
return { status: "installed", license: candidate, failures: [] };
|
|
136
|
-
}
|
|
137
|
-
} catch {
|
|
138
|
-
// The adapter could not prove which complete value survived.
|
|
139
|
-
}
|
|
140
|
-
return { status: "rejected", failures: ["storage-failure"] };
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function sameLicense(left: SignedLicense, right: SignedLicense): boolean {
|
|
145
|
-
return (
|
|
146
|
-
left.claims.licenseId === right.claims.licenseId &&
|
|
147
|
-
left.publicKeyId === right.publicKeyId &&
|
|
148
|
-
left.signature === right.signature
|
|
149
|
-
);
|
|
150
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { SignedLicense, SignedLicenseV1 } from "@windy/shared";
|
|
2
|
-
import { createLicenseSigningPayload } from "./payload.js";
|
|
3
|
-
|
|
4
|
-
export function parseSignedLicenseV1(
|
|
5
|
-
rawLicense: string,
|
|
6
|
-
): SignedLicenseV1 | undefined {
|
|
7
|
-
try {
|
|
8
|
-
const value = JSON.parse(rawLicense) as unknown;
|
|
9
|
-
if (!isRecord(value)) return undefined;
|
|
10
|
-
if (
|
|
11
|
-
!hasExactKeys(value, [
|
|
12
|
-
"algorithm",
|
|
13
|
-
"claims",
|
|
14
|
-
"formatVersion",
|
|
15
|
-
"publicKeyId",
|
|
16
|
-
"signature",
|
|
17
|
-
])
|
|
18
|
-
) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
if (typeof value.signature !== "string" || !value.signature.length) {
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
const license = value as unknown as SignedLicenseV1;
|
|
25
|
-
createLicenseSigningPayload({
|
|
26
|
-
formatVersion: license.formatVersion,
|
|
27
|
-
algorithm: license.algorithm,
|
|
28
|
-
publicKeyId: license.publicKeyId,
|
|
29
|
-
claims: license.claims,
|
|
30
|
-
});
|
|
31
|
-
return license;
|
|
32
|
-
} catch {
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function parseSignedLicense(
|
|
38
|
-
rawLicense: string,
|
|
39
|
-
): SignedLicense | undefined {
|
|
40
|
-
return parseSignedLicenseV1(rawLicense);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
44
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function hasExactKeys(
|
|
48
|
-
value: Record<string, unknown>,
|
|
49
|
-
expected: readonly string[],
|
|
50
|
-
): boolean {
|
|
51
|
-
const keys = Object.keys(value);
|
|
52
|
-
return (
|
|
53
|
-
keys.length === expected.length &&
|
|
54
|
-
keys.every((key) => expected.includes(key))
|
|
55
|
-
);
|
|
56
|
-
}
|