@jmruthers/pace-core 0.5.184 → 0.5.186
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 +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +206 -15
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -13
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +746 -50
- package/docs/api-reference/components.md +26 -12
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/check-pace-core-compliance.js +512 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-MI7HBHN3.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/chunk-W22JP75J.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Incremental TypeDoc Build Script
|
|
5
|
+
*
|
|
6
|
+
* This script only regenerates documentation when source files have changed,
|
|
7
|
+
* significantly speeding up the build process.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { existsSync, statSync, readFileSync, writeFileSync, readdirSync, mkdirSync, rmSync, cpSync, utimesSync } from 'fs';
|
|
12
|
+
import { join, dirname, relative } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { createHash } from 'crypto';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const PACKAGE_ROOT = join(__dirname, '..');
|
|
20
|
+
const REPO_ROOT = join(PACKAGE_ROOT, '..', '..');
|
|
21
|
+
const SRC_DIR = join(PACKAGE_ROOT, 'src');
|
|
22
|
+
const DOCS_DIR = join(PACKAGE_ROOT, 'docs', 'api');
|
|
23
|
+
const CACHE_FILE = join(PACKAGE_ROOT, '.docs-cache.json');
|
|
24
|
+
const DOCS_RELEVANT_PATHS = [
|
|
25
|
+
'packages/core/src',
|
|
26
|
+
'packages/core/examples',
|
|
27
|
+
'packages/core/typedoc.json'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Recursively scan directory for files matching pattern
|
|
32
|
+
*/
|
|
33
|
+
function scanDirectory(dir, extensions, ignorePatterns = []) {
|
|
34
|
+
const files = [];
|
|
35
|
+
|
|
36
|
+
function scan(currentDir) {
|
|
37
|
+
if (!existsSync(currentDir)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const items = readdirSync(currentDir, { withFileTypes: true });
|
|
43
|
+
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const fullPath = join(currentDir, item.name);
|
|
46
|
+
|
|
47
|
+
// Skip ignored patterns
|
|
48
|
+
const shouldIgnore = ignorePatterns.some(pattern => {
|
|
49
|
+
const relativePath = fullPath.replace(SRC_DIR + '/', '');
|
|
50
|
+
return pattern.test(relativePath);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (shouldIgnore) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
|
|
58
|
+
scan(fullPath);
|
|
59
|
+
} else if (item.isFile()) {
|
|
60
|
+
const ext = item.name.substring(item.name.lastIndexOf('.'));
|
|
61
|
+
if (extensions.includes(ext)) {
|
|
62
|
+
files.push(fullPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// Skip directories we can't read
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
scan(dir);
|
|
72
|
+
return files;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Determine if we're inside a git repository
|
|
77
|
+
*/
|
|
78
|
+
function isGitRepository() {
|
|
79
|
+
try {
|
|
80
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
81
|
+
cwd: REPO_ROOT,
|
|
82
|
+
stdio: 'pipe'
|
|
83
|
+
});
|
|
84
|
+
return true;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if git reports any relevant source changes
|
|
92
|
+
*/
|
|
93
|
+
function hasGitTrackedSourceChanges() {
|
|
94
|
+
if (!isGitRepository()) {
|
|
95
|
+
return true; // Without git context, err on the side of rebuilding
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const status = execSync(`git status --porcelain -- ${DOCS_RELEVANT_PATHS.join(' ')}`.trim(), {
|
|
100
|
+
cwd: REPO_ROOT,
|
|
101
|
+
encoding: 'utf-8',
|
|
102
|
+
stdio: 'pipe'
|
|
103
|
+
}).trim();
|
|
104
|
+
|
|
105
|
+
return status.length > 0;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.log('⚠️ Unable to determine git status for docs generation, running TypeDoc to be safe.');
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* If we don't have a cache yet but the repo is clean and docs exist, we can skip
|
|
114
|
+
*/
|
|
115
|
+
function shouldSkipDueToCleanGitState() {
|
|
116
|
+
if (!existsSync(DOCS_DIR)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const existingDocs = scanDirectory(DOCS_DIR, ['.md']);
|
|
121
|
+
if (existingDocs.length === 0) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (hasGitTrackedSourceChanges()) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('✅ Git working tree is clean for documentation-related sources.');
|
|
130
|
+
console.log(' Assuming checked-in docs are up to date, skipping TypeDoc run.');
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Prime the docs cache using the currently checked-in documentation.
|
|
136
|
+
* This lets future runs rely on hash comparisons even if git status is dirty.
|
|
137
|
+
*/
|
|
138
|
+
function primeCacheFromExistingDocs() {
|
|
139
|
+
const currentDocHashes = getExistingDocHashes();
|
|
140
|
+
const currentSourceHashes = getSourceFileHashes();
|
|
141
|
+
|
|
142
|
+
const docHashesObj = {};
|
|
143
|
+
for (const [relativePath, hash] of currentDocHashes.entries()) {
|
|
144
|
+
docHashesObj[relativePath] = hash;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const sourceHashesObj = {};
|
|
148
|
+
for (const [relativePath, hash] of currentSourceHashes.entries()) {
|
|
149
|
+
sourceHashesObj[relativePath] = hash;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const cache = {
|
|
153
|
+
lastBuildTime: Date.now(),
|
|
154
|
+
sourceModTime: getLatestSourceModTime(),
|
|
155
|
+
docModTime: getLatestDocModTime(),
|
|
156
|
+
fileHashes: docHashesObj,
|
|
157
|
+
sourceFileHashes: sourceHashesObj
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
saveCache(cache);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get the most recent modification time of all source files
|
|
165
|
+
*/
|
|
166
|
+
function getLatestSourceModTime() {
|
|
167
|
+
const ignorePatterns = [
|
|
168
|
+
/\.test\.(ts|tsx)$/,
|
|
169
|
+
/\.spec\.(ts|tsx)$/,
|
|
170
|
+
/__tests__/,
|
|
171
|
+
/\/test\//,
|
|
172
|
+
/\/tests\//
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const sourceFiles = scanDirectory(SRC_DIR, ['.ts', '.tsx'], ignorePatterns);
|
|
176
|
+
|
|
177
|
+
let latestTime = 0;
|
|
178
|
+
for (const filePath of sourceFiles) {
|
|
179
|
+
try {
|
|
180
|
+
const stats = statSync(filePath);
|
|
181
|
+
if (stats.mtimeMs > latestTime) {
|
|
182
|
+
latestTime = stats.mtimeMs;
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
// File might have been deleted, skip it
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return latestTime;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get the most recent modification time of generated docs
|
|
194
|
+
*/
|
|
195
|
+
function getLatestDocModTime() {
|
|
196
|
+
if (!existsSync(DOCS_DIR)) {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const docFiles = scanDirectory(DOCS_DIR, ['.md']);
|
|
201
|
+
|
|
202
|
+
if (docFiles.length === 0) {
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let latestTime = 0;
|
|
207
|
+
for (const filePath of docFiles) {
|
|
208
|
+
try {
|
|
209
|
+
const stats = statSync(filePath);
|
|
210
|
+
if (stats.mtimeMs > latestTime) {
|
|
211
|
+
latestTime = stats.mtimeMs;
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
// File might have been deleted, skip it
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return latestTime;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get hashes of all source files
|
|
223
|
+
*/
|
|
224
|
+
function getSourceFileHashes() {
|
|
225
|
+
const ignorePatterns = [
|
|
226
|
+
/\.test\.(ts|tsx)$/,
|
|
227
|
+
/\.spec\.(ts|tsx)$/,
|
|
228
|
+
/__tests__/,
|
|
229
|
+
/\/test\//,
|
|
230
|
+
/\/tests\//
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const sourceFiles = scanDirectory(SRC_DIR, ['.ts', '.tsx'], ignorePatterns);
|
|
234
|
+
const hashes = new Map();
|
|
235
|
+
|
|
236
|
+
for (const filePath of sourceFiles) {
|
|
237
|
+
try {
|
|
238
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
239
|
+
const hash = createHash('md5').update(content).digest('hex');
|
|
240
|
+
const relativePath = relative(SRC_DIR, filePath);
|
|
241
|
+
hashes.set(relativePath, hash);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
// Skip files we can't read
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return hashes;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if source files have changed by comparing hashes
|
|
252
|
+
*/
|
|
253
|
+
function hasSourceChanged(currentHashes, cachedHashes) {
|
|
254
|
+
if (!cachedHashes || Object.keys(cachedHashes).length === 0) {
|
|
255
|
+
return true; // No cache, assume changed
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check if any file changed
|
|
259
|
+
for (const [relativePath, currentHash] of currentHashes.entries()) {
|
|
260
|
+
const cachedHash = cachedHashes[relativePath];
|
|
261
|
+
if (cachedHash !== currentHash) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check if any files were added or removed
|
|
267
|
+
if (currentHashes.size !== Object.keys(cachedHashes).length) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if doc file hashes match cached hashes
|
|
276
|
+
*/
|
|
277
|
+
function hashesMatch(currentHashes, cachedHashes) {
|
|
278
|
+
if (!cachedHashes || Object.keys(cachedHashes).length === 0) {
|
|
279
|
+
return false; // No cache, assume mismatch
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (currentHashes.size !== Object.keys(cachedHashes).length) {
|
|
283
|
+
return false; // Different number of files
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const [relativePath, currentHash] of currentHashes.entries()) {
|
|
287
|
+
const cachedHash = cachedHashes[relativePath];
|
|
288
|
+
if (cachedHash !== currentHash) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Load the cache file
|
|
298
|
+
*/
|
|
299
|
+
function loadCache() {
|
|
300
|
+
if (!existsSync(CACHE_FILE)) {
|
|
301
|
+
return {
|
|
302
|
+
lastBuildTime: 0,
|
|
303
|
+
sourceModTime: 0,
|
|
304
|
+
docModTime: 0,
|
|
305
|
+
fileHashes: {},
|
|
306
|
+
sourceFileHashes: {}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const content = readFileSync(CACHE_FILE, 'utf-8');
|
|
312
|
+
const cache = JSON.parse(content);
|
|
313
|
+
// Ensure fileHashes exists for backward compatibility
|
|
314
|
+
if (!cache.fileHashes) {
|
|
315
|
+
cache.fileHashes = {};
|
|
316
|
+
}
|
|
317
|
+
if (!cache.sourceFileHashes) {
|
|
318
|
+
cache.sourceFileHashes = {};
|
|
319
|
+
}
|
|
320
|
+
return cache;
|
|
321
|
+
} catch (err) {
|
|
322
|
+
return {
|
|
323
|
+
lastBuildTime: 0,
|
|
324
|
+
sourceModTime: 0,
|
|
325
|
+
docModTime: 0,
|
|
326
|
+
fileHashes: {},
|
|
327
|
+
sourceFileHashes: {}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Save the cache file
|
|
334
|
+
*/
|
|
335
|
+
function saveCache(cache) {
|
|
336
|
+
// Ensure fileHashes is always present
|
|
337
|
+
if (!cache.fileHashes) {
|
|
338
|
+
cache.fileHashes = {};
|
|
339
|
+
}
|
|
340
|
+
if (!cache.sourceFileHashes) {
|
|
341
|
+
cache.sourceFileHashes = {};
|
|
342
|
+
}
|
|
343
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Aggressive pre-check: Should we run TypeDoc at all?
|
|
348
|
+
* This checks source file hashes BEFORE running TypeDoc to avoid unnecessary runs
|
|
349
|
+
*/
|
|
350
|
+
function shouldRunTypeDoc() {
|
|
351
|
+
const cache = loadCache();
|
|
352
|
+
|
|
353
|
+
// If no cache, must run
|
|
354
|
+
if (cache.lastBuildTime === 0) {
|
|
355
|
+
if (shouldSkipDueToCleanGitState()) {
|
|
356
|
+
console.log('⏭️ Skipping TypeDoc - no cache yet but git shows no relevant source changes.');
|
|
357
|
+
console.log(' Reason: Using checked-in docs since source tree matches HEAD');
|
|
358
|
+
console.log(' Action: Priming docs cache from checked-in files for future runs');
|
|
359
|
+
primeCacheFromExistingDocs();
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
console.log('📝 No cache found, running TypeDoc...');
|
|
363
|
+
console.log(' Reason: First run - no cache exists');
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// If docs don't exist, must run
|
|
368
|
+
if (!existsSync(DOCS_DIR)) {
|
|
369
|
+
console.log('📝 Documentation directory not found, running TypeDoc...');
|
|
370
|
+
console.log(' Reason: Documentation directory missing');
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Check source file hashes - most reliable check
|
|
375
|
+
console.log('🔍 Checking source file hashes...');
|
|
376
|
+
const currentSourceHashes = getSourceFileHashes();
|
|
377
|
+
const cachedSourceHashes = cache.sourceFileHashes || {};
|
|
378
|
+
|
|
379
|
+
console.log(` Current source files: ${currentSourceHashes.size}`);
|
|
380
|
+
console.log(` Cached source files: ${Object.keys(cachedSourceHashes).length}`);
|
|
381
|
+
|
|
382
|
+
if (hasSourceChanged(currentSourceHashes, cachedSourceHashes)) {
|
|
383
|
+
// Find which files changed
|
|
384
|
+
const changedFiles = [];
|
|
385
|
+
for (const [relativePath, currentHash] of currentSourceHashes.entries()) {
|
|
386
|
+
const cachedHash = cachedSourceHashes[relativePath];
|
|
387
|
+
if (cachedHash !== currentHash) {
|
|
388
|
+
changedFiles.push(relativePath);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
console.log('📝 Source files have changed (hash comparison), running TypeDoc...');
|
|
392
|
+
if (changedFiles.length > 0 && changedFiles.length <= 10) {
|
|
393
|
+
console.log(` Changed files: ${changedFiles.slice(0, 5).join(', ')}${changedFiles.length > 5 ? ` ... and ${changedFiles.length - 5} more` : ''}`);
|
|
394
|
+
} else if (changedFiles.length > 10) {
|
|
395
|
+
console.log(` Changed files: ${changedFiles.length} files modified`);
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Source hasn't changed - check if docs match cache
|
|
401
|
+
console.log('🔍 Checking documentation file hashes...');
|
|
402
|
+
const currentDocHashes = getExistingDocHashes();
|
|
403
|
+
const cachedDocHashes = cache.fileHashes || {};
|
|
404
|
+
|
|
405
|
+
console.log(` Current doc files: ${currentDocHashes.size}`);
|
|
406
|
+
console.log(` Cached doc files: ${Object.keys(cachedDocHashes).length}`);
|
|
407
|
+
|
|
408
|
+
if (hashesMatch(currentDocHashes, cachedDocHashes)) {
|
|
409
|
+
console.log('✅ Source unchanged and docs match cache - skipping TypeDoc entirely');
|
|
410
|
+
console.log(' Reason: No source changes detected and documentation is up to date');
|
|
411
|
+
return false; // Skip TypeDoc!
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Docs don't match cache but source unchanged - refresh cache snapshot instead of regenerating
|
|
415
|
+
const mismatchedFiles = [];
|
|
416
|
+
for (const [relativePath, currentHash] of currentDocHashes.entries()) {
|
|
417
|
+
const cachedHash = cachedDocHashes[relativePath];
|
|
418
|
+
if (cachedHash !== currentHash) {
|
|
419
|
+
mismatchedFiles.push(relativePath);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
console.log('⚠️ Source unchanged but docs don\'t match cache - refreshing cache from disk');
|
|
423
|
+
console.log(` Mismatched files: ${mismatchedFiles.length} files don't match cache`);
|
|
424
|
+
if (mismatchedFiles.length <= 5) {
|
|
425
|
+
console.log(` Files: ${mismatchedFiles.join(', ')}`);
|
|
426
|
+
}
|
|
427
|
+
primeCacheFromExistingDocs();
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Check if any source files have changed since last build
|
|
433
|
+
*/
|
|
434
|
+
function needsRebuild() {
|
|
435
|
+
const cache = loadCache();
|
|
436
|
+
const currentSourceModTime = getLatestSourceModTime();
|
|
437
|
+
const currentDocModTime = getLatestDocModTime();
|
|
438
|
+
|
|
439
|
+
// If docs don't exist, we need to build
|
|
440
|
+
if (!existsSync(DOCS_DIR) || currentDocModTime === 0) {
|
|
441
|
+
console.log('📝 Documentation directory not found or empty, building...');
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Check if typedoc.json has changed
|
|
446
|
+
const typedocConfigPath = join(PACKAGE_ROOT, 'typedoc.json');
|
|
447
|
+
if (existsSync(typedocConfigPath)) {
|
|
448
|
+
const configStats = statSync(typedocConfigPath);
|
|
449
|
+
if (cache.lastBuildTime > 0 && configStats.mtimeMs > cache.lastBuildTime) {
|
|
450
|
+
console.log('📝 TypeDoc configuration has changed, rebuilding...');
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
if (currentDocModTime > 0 && configStats.mtimeMs > currentDocModTime) {
|
|
454
|
+
console.log('📝 TypeDoc configuration is newer than docs, rebuilding...');
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check entry point file (what TypeDoc actually uses)
|
|
460
|
+
const entryPointPath = join(PACKAGE_ROOT, 'src', 'index.ts');
|
|
461
|
+
if (existsSync(entryPointPath)) {
|
|
462
|
+
const entryStats = statSync(entryPointPath);
|
|
463
|
+
if (cache.lastBuildTime > 0 && entryStats.mtimeMs > cache.lastBuildTime) {
|
|
464
|
+
console.log('📝 Entry point file has changed, rebuilding...');
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
if (currentDocModTime > 0 && entryStats.mtimeMs > currentDocModTime) {
|
|
468
|
+
console.log('📝 Entry point file is newer than docs, rebuilding...');
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// If source files are newer than docs, we need to rebuild
|
|
474
|
+
if (currentSourceModTime > currentDocModTime) {
|
|
475
|
+
console.log('📝 Source files are newer than documentation, rebuilding...');
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// If cache indicates a rebuild is needed (e.g., after a clean)
|
|
480
|
+
// Only check cache if we have a valid cache entry
|
|
481
|
+
if (cache.lastBuildTime > 0) {
|
|
482
|
+
// Check if source files have changed since last build
|
|
483
|
+
const sourceChanged = currentSourceModTime > cache.sourceModTime;
|
|
484
|
+
|
|
485
|
+
// Check if docs are newer than the last build time (meaning they were just built)
|
|
486
|
+
const docsJustBuilt = currentDocModTime > cache.lastBuildTime - 1000; // 1 second tolerance
|
|
487
|
+
|
|
488
|
+
if (sourceChanged) {
|
|
489
|
+
console.log('📝 Source files have changed since last build, rebuilding...');
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// If docs were just built (within last second), skip rebuild to avoid double-building
|
|
494
|
+
if (docsJustBuilt) {
|
|
495
|
+
console.log('✅ Documentation was just built, skipping rebuild to avoid double-build');
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Check file-level hashes if available
|
|
500
|
+
if (cache.fileHashes && Object.keys(cache.fileHashes).length > 0) {
|
|
501
|
+
const currentHashes = getExistingDocHashes();
|
|
502
|
+
let filesChanged = false;
|
|
503
|
+
|
|
504
|
+
// Check if any file hashes changed
|
|
505
|
+
for (const [relativePath, cachedHash] of Object.entries(cache.fileHashes)) {
|
|
506
|
+
const currentHash = currentHashes.get(relativePath);
|
|
507
|
+
if (currentHash !== cachedHash) {
|
|
508
|
+
filesChanged = true;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Check if any new files were added
|
|
514
|
+
if (!filesChanged && currentHashes.size !== Object.keys(cache.fileHashes).length) {
|
|
515
|
+
filesChanged = true;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!filesChanged && !sourceChanged) {
|
|
519
|
+
console.log('✅ Documentation is up to date (file hashes match cache), skipping rebuild');
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// If cache exists and source hasn't changed, we're good
|
|
525
|
+
console.log('✅ Documentation is up to date (cache valid), skipping rebuild');
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// No cache exists - compare source vs docs directly
|
|
530
|
+
if (currentSourceModTime <= currentDocModTime) {
|
|
531
|
+
console.log('✅ Documentation is up to date, skipping rebuild');
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Fallback: rebuild if we can't determine
|
|
536
|
+
console.log('📝 Unable to determine if rebuild needed, rebuilding to be safe...');
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Get current package version from package.json
|
|
542
|
+
*/
|
|
543
|
+
function getPackageVersion() {
|
|
544
|
+
try {
|
|
545
|
+
const packageJsonPath = join(PACKAGE_ROOT, 'package.json');
|
|
546
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
547
|
+
return packageJson.version || '';
|
|
548
|
+
} catch (err) {
|
|
549
|
+
return '';
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Sort markdown list items for deterministic comparison
|
|
555
|
+
*/
|
|
556
|
+
function sortMarkdownListItems(text) {
|
|
557
|
+
// Match markdown list sections (lines starting with - or *)
|
|
558
|
+
const lines = text.split('\n');
|
|
559
|
+
const sortedLines = [];
|
|
560
|
+
let currentList = [];
|
|
561
|
+
let inList = false;
|
|
562
|
+
|
|
563
|
+
for (const line of lines) {
|
|
564
|
+
const isListItem = /^\s*[-*]\s+/.test(line);
|
|
565
|
+
|
|
566
|
+
if (isListItem) {
|
|
567
|
+
if (!inList && currentList.length > 0) {
|
|
568
|
+
// Flush previous non-list content
|
|
569
|
+
sortedLines.push(...currentList);
|
|
570
|
+
currentList = [];
|
|
571
|
+
}
|
|
572
|
+
inList = true;
|
|
573
|
+
currentList.push(line);
|
|
574
|
+
} else {
|
|
575
|
+
if (inList && currentList.length > 0) {
|
|
576
|
+
// Sort and flush list
|
|
577
|
+
currentList.sort();
|
|
578
|
+
sortedLines.push(...currentList);
|
|
579
|
+
currentList = [];
|
|
580
|
+
}
|
|
581
|
+
inList = false;
|
|
582
|
+
currentList.push(line);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Flush remaining content
|
|
587
|
+
if (currentList.length > 0) {
|
|
588
|
+
if (inList) {
|
|
589
|
+
currentList.sort();
|
|
590
|
+
}
|
|
591
|
+
sortedLines.push(...currentList);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return sortedLines.join('\n');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Normalize markdown link references for deterministic comparison
|
|
599
|
+
*/
|
|
600
|
+
function normalizeMarkdownLinks(text) {
|
|
601
|
+
// Extract and sort link references [id]: url "title"
|
|
602
|
+
const linkRefPattern = /^\[([^\]]+)\]:\s*(.+)$/gm;
|
|
603
|
+
const links = [];
|
|
604
|
+
let match;
|
|
605
|
+
|
|
606
|
+
while ((match = linkRefPattern.exec(text)) !== null) {
|
|
607
|
+
links.push(match[0]);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (links.length > 0) {
|
|
611
|
+
// Sort links by ID
|
|
612
|
+
links.sort();
|
|
613
|
+
// Replace all link references with sorted version
|
|
614
|
+
let normalized = text.replace(linkRefPattern, 'LINKREF');
|
|
615
|
+
// Append sorted links at the end
|
|
616
|
+
normalized = normalized.replace(/LINKREF/g, () => links.shift() || 'LINKREF');
|
|
617
|
+
return normalized;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return text;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Normalize "Defined in" links emitted by TypeDoc so that branch/remote differences
|
|
625
|
+
* don't cause hash mismatches between environments.
|
|
626
|
+
*/
|
|
627
|
+
function normalizeDefinedInLinks(text) {
|
|
628
|
+
const definedInPattern = /\[(packages\/core\/[^\]]+?:\d+)\]\((https?:\/\/[^(]+?#L\d+)\)/g;
|
|
629
|
+
return text.replace(definedInPattern, (_match, displayText) => displayText);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Normalize content for comparison (remove trailing whitespace, normalize line endings, version numbers, timestamps)
|
|
634
|
+
*/
|
|
635
|
+
function normalizeContent(content) {
|
|
636
|
+
let normalized = content
|
|
637
|
+
.replace(/\r\n/g, '\n') // Normalize line endings
|
|
638
|
+
.replace(/\r/g, '\n') // Handle old Mac line endings
|
|
639
|
+
.replace(/[ \t]+$/gm, '') // Remove trailing spaces/tabs
|
|
640
|
+
.replace(/\n{3,}/g, '\n\n'); // Normalize multiple blank lines
|
|
641
|
+
|
|
642
|
+
// Remove version numbers from TypeDoc-generated content
|
|
643
|
+
// TypeDoc includes version like "@jmruthers/pace-core@0.5.158"
|
|
644
|
+
// We normalize this to "@jmruthers/pace-core@VERSION" so version changes don't trigger updates
|
|
645
|
+
// This allows docs to remain unchanged when only the version number changes
|
|
646
|
+
normalized = normalized.replace(/@jmruthers\/pace-core@[\d.]+/g, '@jmruthers/pace-core@VERSION');
|
|
647
|
+
normalized = normalized.replace(/version\s+[\d.]+/gi, 'version VERSION');
|
|
648
|
+
// Also handle any other version patterns that might appear
|
|
649
|
+
normalized = normalized.replace(/\b[\d]+\.[\d]+\.[\d]+/g, 'VERSION');
|
|
650
|
+
|
|
651
|
+
// Remove timestamps and dates that might be generated dynamically
|
|
652
|
+
normalized = normalized.replace(/\d{4}-\d{2}-\d{2}/g, 'DATE'); // YYYY-MM-DD
|
|
653
|
+
normalized = normalized.replace(/\d{2}\/\d{2}\/\d{4}/g, 'DATE'); // MM/DD/YYYY
|
|
654
|
+
normalized = normalized.replace(/\d{2}:\d{2}:\d{2}/g, 'TIME'); // HH:MM:SS
|
|
655
|
+
normalized = normalized.replace(/Generated\s+on[^\n]*/gi, 'Generated on DATE');
|
|
656
|
+
normalized = normalized.replace(/Last\s+updated[^\n]*/gi, 'Last updated DATE');
|
|
657
|
+
normalized = normalized.replace(/Updated\s+[^\n]*/gi, 'Updated DATE');
|
|
658
|
+
|
|
659
|
+
// Remove any git commit hashes or SHAs that might appear
|
|
660
|
+
normalized = normalized.replace(/\b[0-9a-f]{7,40}\b/gi, 'HASH');
|
|
661
|
+
|
|
662
|
+
// Remove any ISO timestamps
|
|
663
|
+
normalized = normalized.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[.\d]*Z?/g, 'TIMESTAMP');
|
|
664
|
+
|
|
665
|
+
// Normalize whitespace more aggressively
|
|
666
|
+
normalized = normalized.replace(/[ \t]+/g, ' '); // Multiple spaces/tabs to single space
|
|
667
|
+
normalized = normalized.replace(/ \n/g, '\n'); // Remove trailing spaces before newlines
|
|
668
|
+
|
|
669
|
+
// Sort markdown lists for deterministic comparison
|
|
670
|
+
normalized = sortMarkdownListItems(normalized);
|
|
671
|
+
|
|
672
|
+
// Normalize markdown link references
|
|
673
|
+
normalized = normalizeMarkdownLinks(normalized);
|
|
674
|
+
|
|
675
|
+
// Normalize "Defined in" GitHub links
|
|
676
|
+
normalized = normalizeDefinedInLinks(normalized);
|
|
677
|
+
|
|
678
|
+
return normalized.trim();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Get file content hash for comparison
|
|
683
|
+
*/
|
|
684
|
+
function getFileHash(filePath) {
|
|
685
|
+
if (!existsSync(filePath)) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
690
|
+
const normalized = normalizeContent(content);
|
|
691
|
+
return createHash('md5').update(normalized).digest('hex');
|
|
692
|
+
} catch (err) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Get hashes of all existing doc files
|
|
699
|
+
*/
|
|
700
|
+
function getExistingDocHashes() {
|
|
701
|
+
const hashes = new Map();
|
|
702
|
+
if (!existsSync(DOCS_DIR)) {
|
|
703
|
+
return hashes;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const docFiles = scanDirectory(DOCS_DIR, ['.md']);
|
|
707
|
+
for (const filePath of docFiles) {
|
|
708
|
+
const relativePath = relative(DOCS_DIR, filePath);
|
|
709
|
+
const hash = getFileHash(filePath);
|
|
710
|
+
if (hash) {
|
|
711
|
+
hashes.set(relativePath, hash);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return hashes;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Get git status of docs directory
|
|
720
|
+
*/
|
|
721
|
+
function getGitStatus(directory) {
|
|
722
|
+
try {
|
|
723
|
+
const result = execSync(`git status --porcelain ${directory}`, {
|
|
724
|
+
cwd: PACKAGE_ROOT,
|
|
725
|
+
encoding: 'utf-8',
|
|
726
|
+
stdio: 'pipe'
|
|
727
|
+
});
|
|
728
|
+
return result.trim().split('\n').filter(line => line.trim());
|
|
729
|
+
} catch (err) {
|
|
730
|
+
// Not a git repo or git not available
|
|
731
|
+
return [];
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Run TypeDoc to generate documentation, but only update files that actually changed
|
|
737
|
+
*/
|
|
738
|
+
function runTypeDoc(dryRun = false) {
|
|
739
|
+
if (dryRun) {
|
|
740
|
+
console.log('🔍 DRY-RUN MODE: Showing what would be updated without actually updating');
|
|
741
|
+
}
|
|
742
|
+
console.log('🔨 Running TypeDoc to generate documentation...');
|
|
743
|
+
console.log(`📝 Incremental build script v2.0 - Only updating files with actual content changes`);
|
|
744
|
+
|
|
745
|
+
// Get hashes of existing files before generation
|
|
746
|
+
// Version numbers are normalized out during hash calculation
|
|
747
|
+
const existingHashes = getExistingDocHashes();
|
|
748
|
+
const totalExistingFiles = existingHashes.size;
|
|
749
|
+
console.log(`📋 Found ${totalExistingFiles} existing documentation files to compare against`);
|
|
750
|
+
|
|
751
|
+
// Create temp directory for new docs
|
|
752
|
+
const tempDocsDir = join(PACKAGE_ROOT, '.docs-temp');
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
// Backup current docs if they exist
|
|
756
|
+
const backupDir = join(PACKAGE_ROOT, '.docs-backup');
|
|
757
|
+
if (existsSync(DOCS_DIR)) {
|
|
758
|
+
if (existsSync(backupDir)) {
|
|
759
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
760
|
+
}
|
|
761
|
+
cpSync(DOCS_DIR, backupDir, { recursive: true });
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Run TypeDoc to temp directory first
|
|
765
|
+
const tempTypedocConfig = {
|
|
766
|
+
...JSON.parse(readFileSync(join(PACKAGE_ROOT, 'typedoc.json'), 'utf-8')),
|
|
767
|
+
out: '.docs-temp'
|
|
768
|
+
};
|
|
769
|
+
const tempConfigPath = join(PACKAGE_ROOT, '.typedoc-temp.json');
|
|
770
|
+
writeFileSync(tempConfigPath, JSON.stringify(tempTypedocConfig, null, 2));
|
|
771
|
+
|
|
772
|
+
execSync(`npx typedoc --options ${tempConfigPath}`, {
|
|
773
|
+
cwd: PACKAGE_ROOT,
|
|
774
|
+
stdio: 'inherit'
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// Compare and only copy changed files
|
|
778
|
+
let updatedCount = 0;
|
|
779
|
+
let unchangedCount = 0;
|
|
780
|
+
|
|
781
|
+
if (existsSync(tempDocsDir)) {
|
|
782
|
+
const newDocFiles = scanDirectory(tempDocsDir, ['.md']);
|
|
783
|
+
|
|
784
|
+
// Ensure docs directory exists
|
|
785
|
+
if (!existsSync(DOCS_DIR)) {
|
|
786
|
+
mkdirSync(DOCS_DIR, { recursive: true });
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const updatedFiles = [];
|
|
790
|
+
|
|
791
|
+
for (const newFilePath of newDocFiles) {
|
|
792
|
+
const relativePath = relative(tempDocsDir, newFilePath);
|
|
793
|
+
const targetPath = join(DOCS_DIR, relativePath);
|
|
794
|
+
const newHash = getFileHash(newFilePath);
|
|
795
|
+
const oldHash = existingHashes.get(relativePath);
|
|
796
|
+
|
|
797
|
+
// CRITICAL: Only copy if normalized content hash changed
|
|
798
|
+
// If hash matches, do NOT write the file at all to prevent git from seeing it as modified
|
|
799
|
+
if (newHash !== oldHash) {
|
|
800
|
+
// Double-check: read existing file and compare normalized content
|
|
801
|
+
if (existsSync(targetPath)) {
|
|
802
|
+
const existingContent = readFileSync(targetPath, 'utf-8');
|
|
803
|
+
const existingNormalized = normalizeContent(existingContent);
|
|
804
|
+
const newContent = readFileSync(newFilePath, 'utf-8');
|
|
805
|
+
const newNormalized = normalizeContent(newContent);
|
|
806
|
+
|
|
807
|
+
// If normalized content is identical, skip write even if hash differs
|
|
808
|
+
// (this handles edge cases where hash calculation might differ)
|
|
809
|
+
if (existingNormalized === newNormalized) {
|
|
810
|
+
unchangedCount++;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Ensure directory exists
|
|
816
|
+
const targetDir = dirname(targetPath);
|
|
817
|
+
if (!existsSync(targetDir)) {
|
|
818
|
+
mkdirSync(targetDir, { recursive: true });
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Copy file only if content actually changed
|
|
822
|
+
if (!dryRun) {
|
|
823
|
+
const content = readFileSync(newFilePath, 'utf-8');
|
|
824
|
+
writeFileSync(targetPath, content, 'utf-8');
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
updatedCount++;
|
|
828
|
+
updatedFiles.push(relativePath);
|
|
829
|
+
} else {
|
|
830
|
+
// Content is identical - DO NOT TOUCH THE FILE AT ALL
|
|
831
|
+
// This prevents git from seeing it as modified
|
|
832
|
+
// We don't restore timestamps or do anything - just leave it alone
|
|
833
|
+
unchangedCount++;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Log which files were updated if any (only if debug mode or significant changes)
|
|
838
|
+
if (updatedCount > 0) {
|
|
839
|
+
if (process.env.DEBUG_DOCS || updatedCount <= 10) {
|
|
840
|
+
console.log(`\n📝 Files updated (${updatedCount}):`);
|
|
841
|
+
updatedFiles.slice(0, 10).forEach(file => console.log(` - ${file}`));
|
|
842
|
+
if (updatedFiles.length > 10) {
|
|
843
|
+
console.log(` ... and ${updatedFiles.length - 10} more`);
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
console.log(`\n📝 ${updatedCount} files were updated (use DEBUG_DOCS=1 to see list)`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Handle deleted files - files that existed before but don't exist in new generation
|
|
851
|
+
// (TypeDoc might remove some files if exports changed)
|
|
852
|
+
for (const [relativePath, oldHash] of existingHashes.entries()) {
|
|
853
|
+
const targetPath = join(DOCS_DIR, relativePath);
|
|
854
|
+
const newFilePath = join(tempDocsDir, relativePath);
|
|
855
|
+
if (!existsSync(newFilePath) && existsSync(targetPath)) {
|
|
856
|
+
// File was deleted by TypeDoc - remove it
|
|
857
|
+
rmSync(targetPath, { force: true });
|
|
858
|
+
console.log(` Deleted: ${relativePath}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Clean up temp files
|
|
863
|
+
rmSync(tempDocsDir, { recursive: true, force: true });
|
|
864
|
+
if (existsSync(backupDir)) {
|
|
865
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
866
|
+
}
|
|
867
|
+
if (existsSync(tempConfigPath)) {
|
|
868
|
+
rmSync(tempConfigPath, { force: true });
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
console.log(`📊 Documentation update summary:`);
|
|
872
|
+
console.log(` Updated: ${updatedCount} files`);
|
|
873
|
+
console.log(` Unchanged: ${unchangedCount} files`);
|
|
874
|
+
|
|
875
|
+
// Verification step: Check if suspicious number of files changed
|
|
876
|
+
if (updatedCount > 50 && updatedCount === newDocFiles.length) {
|
|
877
|
+
console.log(`\n⚠️ WARNING: All ${updatedCount} files appear to have changed!`);
|
|
878
|
+
console.log(` This might indicate TypeDoc is generating non-deterministic content.`);
|
|
879
|
+
console.log(` Check if source files actually changed, or if TypeDoc config changed.`);
|
|
880
|
+
|
|
881
|
+
// Additional verification: Check if source files actually changed
|
|
882
|
+
const cache = loadCache();
|
|
883
|
+
const currentSourceModTime = getLatestSourceModTime();
|
|
884
|
+
if (cache.lastBuildTime > 0 && currentSourceModTime <= cache.sourceModTime) {
|
|
885
|
+
console.log(`\n🔍 VERIFICATION: Source files have NOT changed since last build.`);
|
|
886
|
+
console.log(` This suggests TypeDoc may be generating non-deterministic output.`);
|
|
887
|
+
console.log(` Files were updated but source is unchanged - this is unexpected.`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Verification: If many files changed but normalized content matches, warn
|
|
892
|
+
if (updatedCount > 0 && unchangedCount > 0) {
|
|
893
|
+
const changeRatio = updatedCount / (updatedCount + unchangedCount);
|
|
894
|
+
if (changeRatio > 0.8) {
|
|
895
|
+
console.log(`\n⚠️ WARNING: ${Math.round(changeRatio * 100)}% of files were updated.`);
|
|
896
|
+
console.log(` This is unusually high. Verify source files actually changed.`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Get final file hashes after update
|
|
901
|
+
const finalHashes = getExistingDocHashes();
|
|
902
|
+
const fileHashesObj = {};
|
|
903
|
+
for (const [relativePath, hash] of finalHashes.entries()) {
|
|
904
|
+
fileHashesObj[relativePath] = hash;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return { success: true, fileHashes: fileHashesObj };
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return { success: true, fileHashes: {} };
|
|
911
|
+
} catch (error) {
|
|
912
|
+
// Clean up on error
|
|
913
|
+
if (existsSync(tempDocsDir)) {
|
|
914
|
+
rmSync(tempDocsDir, { recursive: true, force: true });
|
|
915
|
+
}
|
|
916
|
+
const tempConfigPath = join(PACKAGE_ROOT, '.typedoc-temp.json');
|
|
917
|
+
if (existsSync(tempConfigPath)) {
|
|
918
|
+
rmSync(tempConfigPath, { force: true });
|
|
919
|
+
}
|
|
920
|
+
console.error('❌ TypeDoc failed:', error.message);
|
|
921
|
+
return { success: false, fileHashes: {} };
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Main function
|
|
927
|
+
*/
|
|
928
|
+
function main() {
|
|
929
|
+
const forceRebuild = process.argv.includes('--force');
|
|
930
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
931
|
+
const verifyGit = process.argv.includes('--verify-git');
|
|
932
|
+
|
|
933
|
+
if (forceRebuild) {
|
|
934
|
+
console.log('🔄 Force rebuild requested, regenerating all documentation...');
|
|
935
|
+
const result = runTypeDoc(dryRun);
|
|
936
|
+
if (result.success) {
|
|
937
|
+
const currentSourceHashes = getSourceFileHashes();
|
|
938
|
+
const sourceHashesObj = {};
|
|
939
|
+
for (const [relativePath, hash] of currentSourceHashes.entries()) {
|
|
940
|
+
sourceHashesObj[relativePath] = hash;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const cache = {
|
|
944
|
+
lastBuildTime: Date.now(),
|
|
945
|
+
sourceModTime: getLatestSourceModTime(),
|
|
946
|
+
docModTime: getLatestDocModTime(),
|
|
947
|
+
fileHashes: result.fileHashes || {},
|
|
948
|
+
sourceFileHashes: sourceHashesObj
|
|
949
|
+
};
|
|
950
|
+
if (!dryRun) {
|
|
951
|
+
saveCache(cache);
|
|
952
|
+
}
|
|
953
|
+
console.log('✅ Documentation generation completed');
|
|
954
|
+
}
|
|
955
|
+
process.exit(result.success ? 0 : 1);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Aggressive pre-check: Skip TypeDoc entirely if source unchanged
|
|
960
|
+
if (!shouldRunTypeDoc()) {
|
|
961
|
+
console.log('⏭️ Skipping TypeDoc - source files unchanged and docs are up to date');
|
|
962
|
+
process.exit(0);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Get git status before if verifying
|
|
967
|
+
let gitStatusBefore = [];
|
|
968
|
+
if (verifyGit) {
|
|
969
|
+
gitStatusBefore = getGitStatus(DOCS_DIR);
|
|
970
|
+
console.log(`📋 Git status before: ${gitStatusBefore.length} modified files`);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (needsRebuild()) {
|
|
974
|
+
const result = runTypeDoc(dryRun);
|
|
975
|
+
if (result.success) {
|
|
976
|
+
const currentSourceHashes = getSourceFileHashes();
|
|
977
|
+
const sourceHashesObj = {};
|
|
978
|
+
for (const [relativePath, hash] of currentSourceHashes.entries()) {
|
|
979
|
+
sourceHashesObj[relativePath] = hash;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const cache = {
|
|
983
|
+
lastBuildTime: Date.now(),
|
|
984
|
+
sourceModTime: getLatestSourceModTime(),
|
|
985
|
+
docModTime: getLatestDocModTime(),
|
|
986
|
+
fileHashes: result.fileHashes || {},
|
|
987
|
+
sourceFileHashes: sourceHashesObj
|
|
988
|
+
};
|
|
989
|
+
if (!dryRun) {
|
|
990
|
+
saveCache(cache);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Verify git status after if requested
|
|
994
|
+
if (verifyGit && !dryRun) {
|
|
995
|
+
const gitStatusAfter = getGitStatus(DOCS_DIR);
|
|
996
|
+
const newChanges = gitStatusAfter.filter(file => !gitStatusBefore.includes(file));
|
|
997
|
+
if (newChanges.length > 0 && result.updatedCount === 0) {
|
|
998
|
+
console.log(`\n⚠️ WARNING: Git shows ${newChanges.length} files changed but script reported 0 updates`);
|
|
999
|
+
console.log(` This suggests files were modified unnecessarily`);
|
|
1000
|
+
} else if (newChanges.length !== result.updatedCount) {
|
|
1001
|
+
console.log(`\n📊 Git verification: ${newChanges.length} files changed in git, ${result.updatedCount} reported by script`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
console.log('✅ Documentation generation completed');
|
|
1006
|
+
}
|
|
1007
|
+
process.exit(result.success ? 0 : 1);
|
|
1008
|
+
} else {
|
|
1009
|
+
console.log('⏭️ Skipping documentation generation (no changes detected)');
|
|
1010
|
+
process.exit(0);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
main();
|
|
1015
|
+
|