@deepsel/cms-utils
Advanced tools
| import type { MenuItem } from './types'; | ||
| import type { PageData } from '../page'; | ||
| export declare const isActiveMenu: (menuItem: MenuItem, pageData: PageData) => boolean; |
| // Check if a menu item should be marked as active | ||
| export const isActiveMenu = (menuItem, pageData) => { | ||
| // return if not browser | ||
| if (typeof window === 'undefined') { | ||
| return false; | ||
| } | ||
| const location = window.location; | ||
| const currentLang = pageData.lang; | ||
| let result; | ||
| if (menuItem.url === '/') { | ||
| result = | ||
| location.pathname === '/' || | ||
| location.pathname === `/${currentLang}` || | ||
| location.pathname === `/${currentLang}/`; | ||
| } | ||
| else { | ||
| result = | ||
| location.pathname === menuItem?.url || | ||
| location.pathname === `/${currentLang}${menuItem.url}` || | ||
| location.pathname === `/${currentLang}${menuItem.url}/`; | ||
| } | ||
| return result; | ||
| }; |
@@ -1,1 +0,2 @@ | ||
| export * from './getCurrentLangMenus'; | ||
| export * from './isActiveMenu'; | ||
| export * from './types'; |
@@ -1,1 +0,2 @@ | ||
| export * from './getCurrentLangMenus'; | ||
| export * from './isActiveMenu'; | ||
| export * from './types'; |
| export interface MenuItem { | ||
| id: number; | ||
| parent_id: number | null; | ||
| position: number; | ||
| translations: Record<string, { | ||
| open_in_new_tab: boolean; | ||
| page_content_id: number; | ||
| title: string; | ||
| url: string | null; | ||
| use_custom_url: boolean; | ||
| use_page_title: boolean; | ||
| }>; | ||
| children: MenuItem[]; | ||
| } | ||
| export interface ProcessedMenuItem { | ||
| id: number; | ||
| position: number; | ||
| title: string; | ||
| url: string | null; | ||
| open_in_new_tab: boolean; | ||
| children: ProcessedMenuItem[]; | ||
| children: MenuItem[]; | ||
| } |
@@ -1,5 +0,4 @@ | ||
| import type { ApiResponse } from './types'; | ||
| /** | ||
| * Fetches Form data from the backend by language and slug | ||
| */ | ||
| export declare function fetchFormData(lang: string, slug: string, backendHost?: string): Promise<ApiResponse>; | ||
| export declare function fetchFormData(lang: string, slug: string, backendHost?: string): Promise<Record<string, unknown>>; |
@@ -1,5 +0,5 @@ | ||
| import type { ApiResponse } from './types'; | ||
| import type { PageData } from './types'; | ||
| /** | ||
| * Fetches page data from the backend by language and slug | ||
| */ | ||
| export declare function fetchPageData(lang: string | null, slug: string, isPreview?: boolean, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<ApiResponse>; | ||
| export declare function fetchPageData(lang: string | null, slug: string, isPreview?: boolean, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<PageData>; |
@@ -49,17 +49,5 @@ import { fetchPublicSettings } from './fetchPublicSettings'; | ||
| // Add authentication headers if token exists (for both preview and protected content) | ||
| let token = authToken; | ||
| // If no token provided and we're in browser environment, try Capacitor Preferences | ||
| if (!token && typeof window !== 'undefined') { | ||
| try { | ||
| const { Preferences } = await import('@capacitor/preferences'); | ||
| const tokenResult = await Preferences.get({ key: 'token' }); | ||
| token = tokenResult.value; | ||
| } | ||
| catch (e) { | ||
| console.warn('Could not get token from Preferences:', e); | ||
| } | ||
| if (authToken) { | ||
| fetchOptions.headers['Authorization'] = `Bearer ${authToken}`; | ||
| } | ||
| if (token) { | ||
| fetchOptions.headers['Authorization'] = `Bearer ${token}`; | ||
| } | ||
| // Fetch the page data from the backend | ||
@@ -69,3 +57,3 @@ const response = await fetch(url, fetchOptions); | ||
| if (response.status === 401) { | ||
| return { error: true, status: 401, message: 'Authentication required' }; | ||
| throw new Error('Authentication required'); | ||
| } | ||
@@ -84,3 +72,3 @@ // Only treat actual 404 as not found | ||
| public_settings: siteSettings, | ||
| lang: lang || siteSettings?.default_language?.iso_code || 'en', | ||
| lang: lang || siteSettings.default_language?.iso_code || 'en', | ||
| }; | ||
@@ -90,3 +78,3 @@ } | ||
| console.warn('Could not fetch site settings for 404 page:', settingsError); | ||
| return { notFound: true, status: 404, detail }; | ||
| throw new Error(`Page not found: ${detail}`); | ||
| } | ||
@@ -101,3 +89,3 @@ } | ||
| console.error(`Failed to parse response: ${parseError.message}`); | ||
| return { error: true, parseError: parseError.message }; | ||
| throw new Error(`Failed to parse response: ${parseError.message}`); | ||
| } | ||
@@ -107,4 +95,4 @@ } | ||
| console.error('Error fetching page data:', error); | ||
| return { error: true, message: error.message }; | ||
| throw error; | ||
| } | ||
| } |
@@ -5,2 +5,2 @@ import type { SiteSettings } from '../types'; | ||
| */ | ||
| export declare function fetchPublicSettings(orgId?: number | null, astroRequest?: Request | null, lang?: string | null, backendHost?: string): Promise<SiteSettings | null>; | ||
| export declare function fetchPublicSettings(orgId?: number | null, astroRequest?: Request | null, lang?: string | null, backendHost?: string): Promise<SiteSettings>; |
@@ -61,4 +61,4 @@ /** | ||
| console.error('Error fetching public settings:', error); | ||
| return null; | ||
| throw error; | ||
| } | ||
| } |
| /** | ||
| * Extracts the authentication token from cookies or URL parameter (for iframe preview) | ||
| */ | ||
| export declare function getAuthToken(astro: any): any; | ||
| export declare function getAuthToken(astro: any): string | null; |
@@ -1,8 +0,6 @@ | ||
| export * from './fetchBlogListData.js'; | ||
| export * from './fetchBlogPostData.js'; | ||
| export * from './fetchFormData.js'; | ||
| export * from './fetchPageData.js'; | ||
| export * from './fetchPublicSettings.js'; | ||
| export * from './getAuthToken.js'; | ||
| export * from './parseSlugForLangAndPath.js'; | ||
| export * from './types.js'; | ||
| export * from './fetchFormData'; | ||
| export * from './fetchPageData'; | ||
| export * from './fetchPublicSettings'; | ||
| export * from './getAuthToken'; | ||
| export * from './parseSlugForLangAndPath'; | ||
| export * from './types'; |
@@ -1,8 +0,6 @@ | ||
| export * from './fetchBlogListData.js'; | ||
| export * from './fetchBlogPostData.js'; | ||
| export * from './fetchFormData.js'; | ||
| export * from './fetchPageData.js'; | ||
| export * from './fetchPublicSettings.js'; | ||
| export * from './getAuthToken.js'; | ||
| export * from './parseSlugForLangAndPath.js'; | ||
| export * from './types.js'; | ||
| export * from './fetchFormData'; | ||
| export * from './fetchPageData'; | ||
| export * from './fetchPublicSettings'; | ||
| export * from './getAuthToken'; | ||
| export * from './parseSlugForLangAndPath'; | ||
| export * from './types'; |
+68
-14
@@ -0,1 +1,3 @@ | ||
| import type { SiteSettings } from '../types'; | ||
| import type { MenuItem } from '../menus/types'; | ||
| export interface SlugParseResult { | ||
@@ -5,18 +7,70 @@ lang: string | null; | ||
| } | ||
| export interface ErrorResponse { | ||
| error: true; | ||
| status?: number; | ||
| message?: string; | ||
| parseError?: string; | ||
| export interface Language { | ||
| id: number; | ||
| name: string; | ||
| iso_code: string; | ||
| svg_flag: string; | ||
| } | ||
| export interface NotFoundResponse { | ||
| notFound: true; | ||
| status: number; | ||
| detail: string; | ||
| public_settings?: Record<string, unknown>; | ||
| lang?: string; | ||
| export interface SpecialTemplate { | ||
| name: string; | ||
| html?: string; | ||
| component_name?: string; | ||
| } | ||
| export interface BlogListResponse { | ||
| posts: Record<string, unknown>[]; | ||
| export type Menus = MenuItem[]; | ||
| export interface ContentField { | ||
| 'ds-label': string; | ||
| 'ds-type': string; | ||
| 'ds-value': string; | ||
| } | ||
| export type ApiResponse = Record<string, unknown> | ErrorResponse | NotFoundResponse | BlogListResponse; | ||
| export interface Content { | ||
| main: ContentField; | ||
| } | ||
| export interface SeoMetadata { | ||
| title: string; | ||
| description: string | null; | ||
| featured_image_id: number | null; | ||
| featured_image_name: string | null; | ||
| allow_indexing: boolean; | ||
| } | ||
| export interface LanguageAlternative { | ||
| slug: string; | ||
| locale: Language; | ||
| } | ||
| export interface BlogPostAuthor { | ||
| id: number; | ||
| display_name?: string; | ||
| username: string; | ||
| image?: string; | ||
| } | ||
| export interface BlogPostListItem { | ||
| id: number; | ||
| title: string; | ||
| slug: string; | ||
| excerpt?: string; | ||
| featured_image_id?: number; | ||
| publish_date?: string; | ||
| author?: BlogPostAuthor; | ||
| lang: string; | ||
| } | ||
| export interface PageData { | ||
| id?: number; | ||
| title?: string; | ||
| content?: Content; | ||
| lang: string; | ||
| public_settings: SiteSettings; | ||
| seo_metadata?: SeoMetadata; | ||
| language_alternatives?: LanguageAlternative[]; | ||
| is_frontend_page?: boolean | null; | ||
| string_id?: string | null; | ||
| contents?: unknown; | ||
| page_custom_code?: string | null; | ||
| custom_code?: string | null; | ||
| require_login?: boolean; | ||
| blog_posts?: BlogPostListItem[]; | ||
| featured_image_id?: number; | ||
| publish_date?: string; | ||
| author?: BlogPostAuthor; | ||
| notFound?: boolean; | ||
| status?: number; | ||
| detail?: string; | ||
| } |
+14
-0
| import type { MenuItem } from './menus/types'; | ||
| import type { SpecialTemplate } from './page/types'; | ||
| export interface SiteSettings { | ||
| id: number; | ||
| name: string; | ||
| domains: string[]; | ||
@@ -25,2 +28,13 @@ available_languages: Array<{ | ||
| menus: MenuItem[]; | ||
| access_token_expire_minutes: number; | ||
| require_2fa_all_users: boolean; | ||
| allow_public_signup: boolean; | ||
| is_enabled_google_sign_in: boolean; | ||
| is_enabled_saml: boolean; | ||
| saml_sp_entity_id: string | null; | ||
| auto_translate_components: boolean; | ||
| has_openai_api_key: boolean; | ||
| ai_default_writing_model_id: number; | ||
| special_templates: Record<string, SpecialTemplate>; | ||
| selected_theme: string; | ||
| } |
+75
-8
| { | ||
| "name": "@deepsel/cms-utils", | ||
| "version": "1.0.0", | ||
| "description": "Helper utilities for DeepCMS", | ||
| "version": "1.1.0", | ||
| "description": "Helper utilities for Deepsel CMS", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/DeepselSystems/cms-utils.git" | ||
| }, | ||
| "author": "Tim Tran <tim.tran@deepsel.com> (https://deepsel.com)", | ||
| "license": "MIT", | ||
| "bugs": { | ||
| "url": "https://github.com/DeepselSystems/cms-utils/issues" | ||
| "url": "https://github.com/DeepselSystems/deepsel-cms/issues" | ||
| }, | ||
| "homepage": "https://github.com/DeepselSystems/cms-utils#readme", | ||
| "homepage": "https://github.com/DeepselSystems/deepsel-cms/tree/main/packages/cms-utils#readme", | ||
| "type": "module", | ||
@@ -22,2 +18,73 @@ "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "sideEffects": false, | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| }, | ||
| "./types": { | ||
| "import": "./dist/types.js", | ||
| "types": "./dist/types.d.ts" | ||
| }, | ||
| "./language": { | ||
| "import": "./dist/language/index.js", | ||
| "types": "./dist/language/index.d.ts" | ||
| }, | ||
| "./language/isValidLanguageCode": { | ||
| "import": "./dist/language/isValidLanguageCode.js", | ||
| "types": "./dist/language/isValidLanguageCode.d.ts" | ||
| }, | ||
| "./menus": { | ||
| "import": "./dist/menus/index.js", | ||
| "types": "./dist/menus/index.d.ts" | ||
| }, | ||
| "./menus/isActiveMenu": { | ||
| "import": "./dist/menus/isActiveMenu.js", | ||
| "types": "./dist/menus/isActiveMenu.d.ts" | ||
| }, | ||
| "./menus/types": { | ||
| "import": "./dist/menus/types.js", | ||
| "types": "./dist/menus/types.d.ts" | ||
| }, | ||
| "./page": { | ||
| "import": "./dist/page/index.js", | ||
| "types": "./dist/page/index.d.ts" | ||
| }, | ||
| "./page/constants": { | ||
| "import": "./dist/page/constants.js", | ||
| "types": "./dist/page/constants.d.ts" | ||
| }, | ||
| "./page/types": { | ||
| "import": "./dist/page/types.js", | ||
| "types": "./dist/page/types.d.ts" | ||
| }, | ||
| "./page/fetchBlogListData": { | ||
| "import": "./dist/page/fetchBlogListData.js", | ||
| "types": "./dist/page/fetchBlogListData.d.ts" | ||
| }, | ||
| "./page/fetchBlogPostData": { | ||
| "import": "./dist/page/fetchBlogPostData.js", | ||
| "types": "./dist/page/fetchBlogPostData.d.ts" | ||
| }, | ||
| "./page/fetchFormData": { | ||
| "import": "./dist/page/fetchFormData.js", | ||
| "types": "./dist/page/fetchFormData.d.ts" | ||
| }, | ||
| "./page/fetchPageData": { | ||
| "import": "./dist/page/fetchPageData.js", | ||
| "types": "./dist/page/fetchPageData.d.ts" | ||
| }, | ||
| "./page/fetchPublicSettings": { | ||
| "import": "./dist/page/fetchPublicSettings.js", | ||
| "types": "./dist/page/fetchPublicSettings.d.ts" | ||
| }, | ||
| "./page/getAuthToken": { | ||
| "import": "./dist/page/getAuthToken.js", | ||
| "types": "./dist/page/getAuthToken.d.ts" | ||
| }, | ||
| "./page/parseSlugForLangAndPath": { | ||
| "import": "./dist/page/parseSlugForLangAndPath.js", | ||
| "types": "./dist/page/parseSlugForLangAndPath.d.ts" | ||
| } | ||
| }, | ||
| "files": [ | ||
@@ -24,0 +91,0 @@ "dist" |
+12
-8
| # @deepsel/cms-utils | ||
| A collection of light-weight helper functions for building DeepCMS features in JavaScript/TypeScript apps. | ||
| Framework-agnostic utilities for building DeepCMS themes in any JavaScript framework. | ||
| `@deepsel/cms-utils` is designed to be: | ||
| - **Framework-agnostic** – works with Node, React, Astro, Next.js, etc. | ||
| - **Framework-agnostic** – works with React, Vue, Angular, Astro, Next.js, etc. | ||
| - **TypeScript-friendly** – fully typed helpers. | ||
| - **CMS-oriented** – utilities for menus, slugs, URLs, localization, content trees, etc. | ||
| - **CMS-oriented** – utilities for menus, slugs, URLs, localization, page data fetching, etc. | ||
| > **Note:** For React-specific hooks and components, use [`@deepsel/deep-cms-react`](https://github.com/DeepselSystems/deep-cms-react) | ||
| --- | ||
@@ -24,7 +26,7 @@ | ||
| ```typescript | ||
| import { getMenusForCurrentLang } from '@deepsel/cms-utils'; | ||
| import { isValidLanguageCode } from '@deepsel/cms-utils'; | ||
| const menus = getMenusForCurrentLang(); | ||
| const isValidLanguageCode = isValidLanguageCode('en'); | ||
| console.log(menus); | ||
| console.log({ isValidLanguageCode }); | ||
| ``` | ||
@@ -93,5 +95,7 @@ | ||
| // inside my-app | ||
| import { getMenusForCurrentLang } from '@deepsel/cms-utils'; | ||
| import { isValidLanguageCode } from '@deepsel/cms-utils'; | ||
| const menus = getMenusForCurrentLang(); | ||
| const isValidLanguageCode = isValidLanguageCode('en'); | ||
| console.log({ isValidLanguageCode }); | ||
| ``` | ||
@@ -98,0 +102,0 @@ |
| import { ProcessedMenuItem } from './types'; | ||
| import type { SiteSettings } from '../types'; | ||
| export declare const getCurrentLangMenus: (settings: SiteSettings) => ProcessedMenuItem[] | null; |
| /* | ||
| Get menus based on the current selected language | ||
| */ | ||
| export const getCurrentLangMenus = (settings) => { | ||
| if (!settings || !settings.menus || !settings.menus.length) | ||
| return null; | ||
| let currentLang = localStorage.getItem('i18nextLng'); | ||
| // If no language is selected, use the default language | ||
| if (!currentLang) { | ||
| currentLang = settings.default_language.iso_code; | ||
| } | ||
| return settings.menus | ||
| .map((menu) => processMenuItem(menu, currentLang)) | ||
| .filter((item) => item !== null); | ||
| }; | ||
| /* | ||
| Process a menu item and its children recursively: | ||
| - Remove null items | ||
| - Only keep fields that are needed from the translation for the current language | ||
| - Remove other translations | ||
| */ | ||
| function processMenuItem(menuItem, currentLang) { | ||
| const translation = menuItem.translations[currentLang]; | ||
| if (!translation) { | ||
| return null; | ||
| } | ||
| // Process children recursively and filter out null items | ||
| const children = menuItem.children && menuItem.children.length > 0 | ||
| ? menuItem.children | ||
| .map((child) => processMenuItem(child, currentLang)) | ||
| .filter((item) => item !== null) | ||
| : []; | ||
| return { | ||
| id: menuItem.id, | ||
| position: menuItem.position, | ||
| title: translation.title, | ||
| url: translation.url, | ||
| open_in_new_tab: translation.open_in_new_tab, | ||
| children, | ||
| }; | ||
| } |
| import type { ApiResponse } from './types'; | ||
| /** | ||
| * Fetches blog list data from the backend by language | ||
| */ | ||
| export declare function fetchBlogListData(lang: string, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<ApiResponse>; |
| /** | ||
| * Fetches blog list data from the backend by language | ||
| */ | ||
| export async function fetchBlogListData(lang, authToken = null, astroRequest = null, backendHost = 'http://localhost:8000') { | ||
| try { | ||
| // Determine the URL based on whether a language is provided | ||
| const url = lang && lang !== 'default' | ||
| ? `${backendHost}/blog_post/website/${lang}` | ||
| : `${backendHost}/blog_post/website/default`; | ||
| // Prepare fetch options | ||
| const fetchOptions = { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }; | ||
| // Send the current hostname to the backend for proper domain detection | ||
| let hostname = null; | ||
| // Server-side: Extract hostname from Astro request | ||
| if (astroRequest) { | ||
| const url = new URL(astroRequest.url); | ||
| hostname = url.hostname; | ||
| } | ||
| // Client-side: Extract hostname from window | ||
| else if (typeof window !== 'undefined') { | ||
| hostname = window.location.hostname; | ||
| } | ||
| if (hostname) { | ||
| fetchOptions.headers['X-Original-Host'] = hostname; | ||
| fetchOptions.headers['X-Frontend-Host'] = hostname; | ||
| // Note: Cannot override Host header due to browser security restrictions | ||
| } | ||
| // Add authentication headers if token exists (for both preview and protected content) | ||
| let token = authToken; | ||
| // If no token provided and we're in browser environment, try Capacitor Preferences | ||
| if (!token && typeof window !== 'undefined') { | ||
| try { | ||
| const { Preferences } = await import('@capacitor/preferences'); | ||
| const tokenResult = await Preferences.get({ key: 'token' }); | ||
| token = tokenResult.value; | ||
| } | ||
| catch (e) { | ||
| console.warn('Could not get token from Preferences:', e); | ||
| } | ||
| } | ||
| if (token) { | ||
| fetchOptions.headers['Authorization'] = `Bearer ${token}`; | ||
| } | ||
| // Fetch the blog list data from the backend | ||
| const response = await fetch(url, fetchOptions); | ||
| // Handle authentication errors | ||
| if (response.status === 401) { | ||
| return { error: true, status: 401, message: 'Authentication required' }; | ||
| } | ||
| // Only treat actual 404 as not found | ||
| if (response.status === 404) { | ||
| const { detail } = await response.json(); | ||
| console.warn('404', url, { detail }); | ||
| return { notFound: true, status: 404, detail }; | ||
| } | ||
| try { | ||
| // Parse the JSON | ||
| const posts = await response.json(); | ||
| return { posts }; | ||
| } | ||
| catch (parseError) { | ||
| console.error(`Failed to parse response: ${parseError.message}`); | ||
| return { error: true, parseError: parseError.message }; | ||
| } | ||
| } | ||
| catch (error) { | ||
| console.error('Error fetching blog list data:', error); | ||
| return { error: true, message: error.message }; | ||
| } | ||
| } |
| import type { ApiResponse } from './types'; | ||
| /** | ||
| * Fetches Blog-Post-Content data from the backend by language and slug | ||
| */ | ||
| export declare function fetchBlogPostData(lang: string, slug: string, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<ApiResponse>; |
| /** | ||
| * Fetches Blog-Post-Content data from the backend by language and slug | ||
| */ | ||
| export async function fetchBlogPostData(lang, slug, authToken = null, astroRequest = null, backendHost = 'http://localhost:8000') { | ||
| try { | ||
| // Format the slug properly | ||
| const formattedSlug = slug.replace(/^\/blog\//, ''); | ||
| // Determine the URL based on whether a language is provided | ||
| const url = lang && lang !== 'default' | ||
| ? `${backendHost}/blog_post/website/${lang}/${formattedSlug}` | ||
| : `${backendHost}/blog_post/website/default/${formattedSlug}`; | ||
| // Prepare fetch options | ||
| const fetchOptions = { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }; | ||
| // Send the current hostname to the backend for proper domain detection | ||
| let hostname = null; | ||
| // Server-side: Extract hostname from Astro request | ||
| if (astroRequest) { | ||
| const url = new URL(astroRequest.url); | ||
| hostname = url.hostname; | ||
| } | ||
| // Client-side: Extract hostname from window | ||
| else if (typeof window !== 'undefined') { | ||
| hostname = window.location.hostname; | ||
| } | ||
| if (hostname) { | ||
| fetchOptions.headers['X-Original-Host'] = hostname; | ||
| fetchOptions.headers['X-Frontend-Host'] = hostname; | ||
| // Note: Cannot override Host header due to browser security restrictions | ||
| } | ||
| // Add authentication headers if token exists (for both preview and protected content) | ||
| let token = authToken; | ||
| // If no token provided and we're in browser environment, try Capacitor Preferences | ||
| if (!token && typeof window !== 'undefined') { | ||
| try { | ||
| const { Preferences } = await import('@capacitor/preferences'); | ||
| const tokenResult = await Preferences.get({ key: 'token' }); | ||
| token = tokenResult.value; | ||
| } | ||
| catch (e) { | ||
| console.warn('Could not get token from Preferences:', e); | ||
| } | ||
| } | ||
| if (token) { | ||
| fetchOptions.headers['Authorization'] = `Bearer ${token}`; | ||
| } | ||
| // Fetch the page data from the backend | ||
| const response = await fetch(url, fetchOptions); | ||
| // Handle authentication errors | ||
| if (response.status === 401) { | ||
| return { error: true, status: 401, message: 'Authentication required' }; | ||
| } | ||
| // Only treat actual 404 as not found | ||
| if (response.status === 404) { | ||
| const { detail } = await response.json(); | ||
| console.warn('404', url, { detail }); | ||
| return { notFound: true, status: 404, detail }; | ||
| } | ||
| try { | ||
| // Parse the JSON | ||
| return await response.json(); | ||
| } | ||
| catch (parseError) { | ||
| console.error(`Failed to parse response: ${parseError.message}`); | ||
| return { error: true, parseError: parseError.message }; | ||
| } | ||
| } | ||
| catch (error) { | ||
| console.error('Error fetching page data:', error); | ||
| return { error: true, message: error.message }; | ||
| } | ||
| } |
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
117
3.54%3
-40%23784
-15.93%33
-10.81%536
-20.71%1
Infinity%