diff --git a/biome.json b/biome.json index 29017fa6..7506c591 100644 --- a/biome.json +++ b/biome.json @@ -1,55 +1,52 @@ { - "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "includes": [ - "src/**/*", - "!src/components/oc-client/**/*" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "correctness": { - "useJsxKeyInIterable": "off" - }, - "complexity": { - "useLiteralKeys": "off", - "noExcessiveNestedTestSuites": "off" - }, - "style": { - "noParameterAssign": "off", - "noNonNullAssertion": "off", - "useTemplate": "off" - }, - "suspicious": { - "noExplicitAny": "off" - } - } - }, - "javascript": { - "formatter": { - "trailingCommas": "none", - "indentStyle": "space", - "quoteStyle": "single" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - } -} \ No newline at end of file + "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": ["preview/**/*", "src/**/*", "!src/components/oc-client/**/*"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useJsxKeyInIterable": "off" + }, + "complexity": { + "useLiteralKeys": "off", + "noExcessiveNestedTestSuites": "off" + }, + "style": { + "noParameterAssign": "off", + "noNonNullAssertion": "off", + "useTemplate": "off" + }, + "suspicious": { + "noExplicitAny": "off" + } + } + }, + "javascript": { + "formatter": { + "trailingCommas": "none", + "indentStyle": "space", + "quoteStyle": "single" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/package-lock.json b/package-lock.json index ddb0c3e4..9ddc1325 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oc", - "version": "0.50.31", + "version": "0.50.32", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oc", - "version": "0.50.31", + "version": "0.50.32", "license": "MIT", "dependencies": { "@kitajs/html": "^4.2.9", @@ -19,6 +19,7 @@ "builtin-modules": "^3.3.0", "chokidar": "^3.6.0", "colors": "^1.4.0", + "cookie-parser": "^1.4.7", "cross-spawn": "^7.0.5", "dependency-graph": "^1.0.0", "dotenv": "^17.2.1", @@ -66,6 +67,8 @@ "@biomejs/biome": "2.1.2", "@types/accept-language-parser": "^1.5.6", "@types/async": "^3.2.24", + "@types/bun": "^1.2.21", + "@types/cookie-parser": "^1.4.9", "@types/cross-spawn": "^6.0.6", "@types/errorhandler": "^1.5.3", "@types/express": "^5.0.3", @@ -3844,6 +3847,16 @@ "@types/node": "*" } }, + "node_modules/@types/bun": { + "version": "1.2.21", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.21.tgz", + "integrity": "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.2.21" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -3853,6 +3866,16 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", + "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -4031,6 +4054,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.1.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, "node_modules/@types/read": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@types/read/-/read-0.0.32.tgz", @@ -4821,6 +4855,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bun-types": { + "version": "1.2.21", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.21.tgz", + "integrity": "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "peerDependencies": { + "@types/react": "^19" + } + }, "node_modules/busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -5171,14 +5218,33 @@ } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", diff --git a/package.json b/package.json index 670df426..37446c69 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "types": "./dist/index.d.ts", "bin": "./dist/oc-cli.js", "scripts": { - "lint": "npx @biomejs/biome check src test tasks", - "lint:apply": "npx @biomejs/biome check --write src test tasks", + "lint": "npx @biomejs/biome check preview src test tasks", + "lint:apply": "npx @biomejs/biome check --write preview src test tasks", "prebuild": "rimraf dist", "build": "npm run lint && tsc && node tasks/build.js", "git-stage-and-push": "node tasks/git-stage-and-push.js", @@ -44,6 +44,8 @@ "@biomejs/biome": "2.1.2", "@types/accept-language-parser": "^1.5.6", "@types/async": "^3.2.24", + "@types/bun": "^1.2.21", + "@types/cookie-parser": "^1.4.9", "@types/cross-spawn": "^6.0.6", "@types/errorhandler": "^1.5.3", "@types/express": "^5.0.3", @@ -84,6 +86,7 @@ "builtin-modules": "^3.3.0", "chokidar": "^3.6.0", "colors": "^1.4.0", + "cookie-parser": "^1.4.7", "cross-spawn": "^7.0.5", "dependency-graph": "^1.0.0", "dotenv": "^17.2.1", diff --git a/preview/fakeVM.ts b/preview/fakeVM.ts new file mode 100644 index 00000000..5b329325 --- /dev/null +++ b/preview/fakeVM.ts @@ -0,0 +1,583 @@ +import type { ComponentHistory } from '../src/registry/routes/helpers/get-components-history'; +import type { Vm as InfoVm } from '../src/registry/views/info'; +import type { VM as IndexVm } from '../src/types'; + +export const mockComponentHistory: ComponentHistory[] = [ + { + name: 'my-component', + version: '1.0.0', + publishDate: new Date().toISOString(), + templateSize: 1000 + }, + { + name: 'my-component', + version: '1.0.1', + publishDate: new Date().toISOString(), + templateSize: 1000 + }, + { + name: 'my-component', + version: '1.0.2', + publishDate: new Date().toISOString(), + templateSize: 1200 + }, + { + name: 'my-component', + version: '1.1.0', + publishDate: new Date().toISOString(), + templateSize: 1150 + }, + { + name: 'my-component', + version: '1.1.1', + publishDate: new Date().toISOString(), + templateSize: 1300 + }, + { + name: 'my-component', + version: '1.2.0', + publishDate: new Date().toISOString(), + templateSize: 1250 + }, + { + name: 'my-component', + version: '1.2.1', + publishDate: new Date().toISOString(), + templateSize: 1400 + }, + { + name: 'my-component', + version: '1.3.0', + publishDate: new Date().toISOString(), + templateSize: 1350 + }, + { + name: 'my-component', + version: '2.0.0', + publishDate: new Date().toISOString(), + templateSize: 1500 + }, + { + name: 'my-component', + version: '2.0.1', + publishDate: new Date().toISOString(), + templateSize: 1450 + }, + { + name: 'my-component', + version: '2.1.0', + publishDate: new Date().toISOString(), + templateSize: 1600 + }, + { + name: 'my-component', + version: '2.1.1', + publishDate: new Date().toISOString(), + templateSize: 1550 + } +]; + +// Mock data for Index VM (main registry page) +export const mockIndexVM: IndexVm = { + availableDependencies: [ + { + core: true, + name: 'react', + version: '18.2.0', + link: 'https://www.npmjs.com/package/react' + }, + { + core: true, + name: 'lodash', + version: '4.17.21', + link: 'https://www.npmjs.com/package/lodash' + }, + { + core: false, + name: 'moment', + version: '2.29.4', + link: 'https://www.npmjs.com/package/moment' + }, + { + core: false, + name: 'axios', + version: '1.6.0', + link: 'https://www.npmjs.com/package/axios' + } + ], + availablePlugins: { + 'oc-plugin-sass': { + handler: () => {}, + description: 'Sass/SCSS compilation plugin for OpenComponents', + context: false + }, + 'oc-plugin-typescript': { + handler: () => {}, + description: 'TypeScript compilation plugin for OpenComponents', + context: true + }, + 'oc-plugin-babel': { + handler: () => {}, + description: 'Babel transpilation plugin for modern JavaScript', + context: false + } + }, + components: [ + { + name: 'header-component', + version: '2.1.0', + allVersions: ['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0'], + description: 'A responsive header component with navigation and branding', + keywords: ['header', 'navigation', 'responsive', 'branding'], + author: { + name: 'John Doe', + email: 'john.doe@example.com', + url: 'https://github.com/johndoe' + }, + license: 'MIT', + homepage: 'https://github.com/johndoe/header-component', + repository: { + type: 'git', + url: 'git+https://github.com/johndoe/header-component.git' + }, + bugs: { + url: 'https://github.com/johndoe/header-component/issues' + }, + oc: { + date: 1704067200000, // 2024-01-01 + files: { + dataProvider: { + hashKey: 'abc123def456', + src: 'server.js', + type: 'node.js', + size: 2048 + }, + static: ['styles.css', 'logo.png'], + template: { + hashKey: 'def456ghi789', + src: 'template.js', + type: 'jade', + version: '1.11.0', + minOcVersion: '0.44.0', + size: 1536 + } + }, + packaged: true, + parameters: { + title: { + type: 'string', + description: 'The main title displayed in the header', + example: 'My Awesome App', + mandatory: true + }, + showLogo: { + type: 'boolean', + description: 'Whether to display the logo', + default: true, + example: true + }, + navigationItems: { + type: 'string', + description: 'JSON string of navigation items', + example: + '[{"label":"Home","url":"/"},{"label":"About","url":"/about"}]' + }, + theme: { + type: 'string', + description: 'Color theme for the header', + enum: ['light', 'dark', 'auto'], + default: 'light' + } + }, + plugins: ['oc-plugin-sass'], + renderInfo: true, + state: 'experimental', + stringifiedDate: '2024-01-01T00:00:00.000Z', + publisher: 'johndoe', + version: '2.1.0' + } + }, + { + name: 'product-card', + version: '1.5.2', + allVersions: [ + '1.0.0', + '1.1.0', + '1.2.0', + '1.3.0', + '1.4.0', + '1.5.0', + '1.5.1', + '1.5.2' + ], + description: + 'A flexible product card component for e-commerce applications', + keywords: ['product', 'card', 'ecommerce', 'shopping'], + author: { + name: 'Jane Smith', + email: 'jane.smith@company.com', + url: 'https://github.com/janesmith' + }, + license: 'Apache-2.0', + homepage: 'https://company.com/components/product-card', + repository: { + type: 'git', + url: 'git+https://github.com/company/product-card.git' + }, + oc: { + date: 1706745600000, // 2024-02-01 + files: { + dataProvider: { + hashKey: 'ghi789jkl012', + src: 'server.js', + type: 'node.js', + size: 3072 + }, + static: ['product-card.css', 'placeholder.svg'], + template: { + hashKey: 'jkl012mno345', + src: 'template.js', + type: 'handlebars', + version: '4.7.8', + minOcVersion: '0.45.0', + size: 2560 + } + }, + packaged: true, + parameters: { + productId: { + type: 'string', + description: 'Unique identifier for the product', + example: 'PROD-12345', + mandatory: true + }, + showPrice: { + type: 'boolean', + description: 'Whether to display the product price', + default: true + }, + showRating: { + type: 'boolean', + description: 'Whether to display the product rating', + default: true + }, + layout: { + type: 'string', + description: 'Card layout style', + enum: ['vertical', 'horizontal', 'compact'], + default: 'vertical' + }, + maxWidth: { + type: 'number', + description: 'Maximum width of the card in pixels', + default: 300, + example: 350 + } + }, + plugins: ['oc-plugin-typescript', 'oc-plugin-sass'], + renderInfo: true, + stringifiedDate: '2024-02-01T00:00:00.000Z', + publisher: 'janesmith', + version: '1.5.2' + } + }, + { + name: 'footer-component', + version: '1.0.0', + allVersions: ['1.0.0'], + description: + 'A simple footer component with links and copyright information', + keywords: ['footer', 'links', 'copyright'], + author: { + name: 'Bob Wilson', + email: 'bob.wilson@example.org' + }, + license: 'MIT', + oc: { + date: 1709424000000, // 2024-03-01 + files: { + dataProvider: { + hashKey: 'mno345pqr678', + src: 'server.js', + type: 'node.js', + size: 1024 + }, + static: ['footer.css'], + template: { + hashKey: 'pqr678stu901', + src: 'template.js', + type: 'jade', + version: '1.11.0', + size: 768 + } + }, + packaged: true, + parameters: { + companyName: { + type: 'string', + description: 'Name of the company', + example: 'Acme Corp', + mandatory: true + }, + year: { + type: 'number', + description: 'Copyright year', + default: 2024 + }, + links: { + type: 'string', + description: 'JSON string of footer links', + example: + '[{"text":"Privacy Policy","url":"/privacy"},{"text":"Terms","url":"/terms"}]' + } + }, + renderInfo: false, + state: 'deprecated', + stringifiedDate: '2024-03-01T00:00:00.000Z', + publisher: 'bobwilson', + version: '1.0.0' + } + } + ], + componentsList: [ + { + author: { name: 'John Doe', email: 'john.doe@example.com' }, + name: 'header-component', + state: 'experimental' + }, + { + author: { name: 'Jane Smith', email: 'jane.smith@company.com' }, + name: 'product-card', + state: 'stable' + }, + { + author: { name: 'Bob Wilson', email: 'bob.wilson@example.org' }, + name: 'footer-component', + state: 'deprecated' + } + ], + componentsReleases: 12, + href: '/', + ocVersion: '0.45.2', + q: '', + stateCounts: { + deprecated: 1, + experimental: 1 + }, + templates: [ + { + externals: [ + { + name: 'react', + global: 'React', + url: 'https://unpkg.com/react@18/umd/react.production.min.js', + devUrl: 'https://unpkg.com/react@18/umd/react.development.js' + }, + { + name: 'react-dom', + global: 'ReactDOM', + url: 'https://unpkg.com/react-dom@18/umd/react-dom.production.min.js', + devUrl: 'https://unpkg.com/react-dom@18/umd/react-dom.development.js' + } + ], + type: 'react', + version: '18.2.0' + }, + { + externals: [ + { + name: 'handlebars', + global: 'Handlebars', + url: 'https://cdn.jsdelivr.net/npm/handlebars@4.7.8/dist/handlebars.min.js' + } + ], + type: 'handlebars', + version: '4.7.8' + }, + { + externals: [], + type: 'jade', + version: '1.11.0' + } + ], + title: 'OpenComponents Registry', + theme: 'dark', + type: 'oc-registry' +}; + +// Mock data for Info VM (component detail page) +export const mockInfoVM: InfoVm = { + parsedAuthor: { + name: 'Jane Smith', + email: 'jane.smith@company.com', + url: 'https://github.com/janesmith' + }, + component: { + name: 'product-card', + version: '1.5.2', + allVersions: [ + '1.0.0', + '1.1.0', + '1.2.0', + '1.3.0', + '1.4.0', + '1.5.0', + '1.5.1', + '1.5.2' + ], + description: + 'A flexible product card component for e-commerce applications with support for multiple layouts, ratings, and customizable styling. Perfect for product listings, search results, and recommendation sections.', + keywords: [ + 'product', + 'card', + 'ecommerce', + 'shopping', + 'responsive', + 'rating' + ], + author: { + name: 'Jane Smith', + email: 'jane.smith@company.com', + url: 'https://github.com/janesmith' + }, + license: 'Apache-2.0', + homepage: 'https://company.com/components/product-card', + repository: { + type: 'git', + url: 'git+https://github.com/company/product-card.git' + }, + bugs: { + url: 'https://github.com/company/product-card/issues' + }, + engines: { + node: '>=14.0.0' + }, + oc: { + date: 1706745600000, // 2024-02-01 + files: { + dataProvider: { + hashKey: 'ghi789jkl012', + src: 'server.js', + type: 'node.js', + size: 3072 + }, + static: ['product-card.css', 'placeholder.svg', 'star-rating.svg'], + template: { + hashKey: 'jkl012mno345', + src: 'template.js', + type: 'handlebars', + version: '4.7.8', + minOcVersion: '0.45.0', + size: 2560 + }, + imports: { + 'product-service': './services/product-service.js', + 'image-utils': './utils/image-utils.js' + } + }, + packaged: true, + parameters: { + productId: { + type: 'string', + description: + 'Unique identifier for the product. This is used to fetch product data from the API.', + example: 'PROD-12345', + mandatory: true + }, + showPrice: { + type: 'boolean', + description: + 'Whether to display the product price. When false, the price section is hidden.', + default: true, + example: true + }, + showRating: { + type: 'boolean', + description: + 'Whether to display the product rating stars and review count.', + default: true, + example: true + }, + layout: { + type: 'string', + description: + 'Card layout style. Vertical shows image on top, horizontal shows image on the left, compact is a smaller version.', + enum: ['vertical', 'horizontal', 'compact'], + default: 'vertical', + example: 'vertical' + }, + maxWidth: { + type: 'number', + description: + 'Maximum width of the card in pixels. Useful for responsive layouts.', + default: 300, + example: 350 + }, + showAddToCart: { + type: 'boolean', + description: 'Whether to show the "Add to Cart" button', + default: true, + example: false + }, + currency: { + type: 'string', + description: 'Currency code for price display', + default: 'USD', + example: 'EUR' + }, + imageSize: { + type: 'string', + description: 'Size of the product image', + enum: ['small', 'medium', 'large'], + default: 'medium' + } + }, + plugins: ['oc-plugin-typescript', 'oc-plugin-sass'], + renderInfo: true, + stringifiedDate: '2024-02-01T00:00:00.000Z', + publisher: 'janesmith', + version: '1.5.2' + } + }, + componentDetail: { + '1.0.0': { + publishDate: 1704067200000, // 2024-01-01 + templateSize: 2048 + }, + '1.1.0': { + publishDate: 1704153600000, // 2024-01-02 + templateSize: 2176 + }, + '1.2.0': { + publishDate: 1704326400000, // 2024-01-04 + templateSize: 2304 + }, + '1.3.0': { + publishDate: 1704499200000, // 2024-01-06 + templateSize: 2432 + }, + '1.4.0': { + publishDate: 1704672000000, // 2024-01-08 + templateSize: 2560 + }, + '1.5.0': { + publishDate: 1704844800000, // 2024-01-10 + templateSize: 2560 + }, + '1.5.1': { + publishDate: 1704931200000, // 2024-01-11 + templateSize: 2560 + }, + '1.5.2': { + publishDate: 1706745600000, // 2024-02-01 + templateSize: 2560 + } + }, + dependencies: ['react', 'lodash', 'moment'], + href: '/', + sandBoxDefaultQs: + '?productId=PROD-12345&showPrice=true&showRating=true&layout=vertical&maxWidth=300&showAddToCart=true¤cy=USD&imageSize=medium', + title: 'product-card - OpenComponents Registry', + theme: 'dark', + repositoryUrl: 'https://github.com/company/product-card' +}; diff --git a/preview/uiPreview.ts b/preview/uiPreview.ts new file mode 100644 index 00000000..9510ef6b --- /dev/null +++ b/preview/uiPreview.ts @@ -0,0 +1,57 @@ +import fs from 'node:fs'; +import indexView from '../src/registry/views'; +import infoView from '../src/registry/views/info'; +import { mockComponentHistory, mockIndexVM, mockInfoVM } from './fakeVM'; + +const clientJS = fs.readFileSync( + './src/components/oc-client/_package/src/oc-client.js', + 'utf8' +); + +const serve = (content: any) => ({ + GET: () => { + const isHTML = typeof content === 'string' && content.includes(''); + + return typeof content !== 'string' + ? Response.json(content) + : new Response(isHTML ? addReloadScript(content) : content, { + headers: { + 'Content-Type': isHTML ? 'text/html' : 'application/javascript' + } + }); + } +}); +const id = crypto.randomUUID(); + +const addReloadScript = (content: string) => { + return content.replace( + '', + `` + ); +}; + +Bun.serve({ + port: 4444, + routes: { + '/id': serve({ id }), + '/oc-client/client.js': serve(clientJS), + '/~registry/history': serve({ componentsHistory: mockComponentHistory }), + '/:name/:version/~info': serve(infoView(mockInfoVM)), + '/': serve(indexView(mockIndexVM)) + } +}); + +console.log('Server is running on http://localhost:4444'); diff --git a/src/registry/middleware/index.ts b/src/registry/middleware/index.ts index e3fd900c..ef6fd44a 100644 --- a/src/registry/middleware/index.ts +++ b/src/registry/middleware/index.ts @@ -1,3 +1,4 @@ +import cookieParser from 'cookie-parser'; import errorhandler from 'errorhandler'; import express, { type Express } from 'express'; import morgan from 'morgan'; @@ -27,6 +28,7 @@ export const bind = (app: Express, options: Config): Express => { }); app.use(requestHandler()); + app.use(cookieParser()); if (options.postRequestPayloadSize) { // Type is incorrect since limit can be a string like '50mb' diff --git a/src/registry/routes/component-info.ts b/src/registry/routes/component-info.ts index 09ddaf41..dfb50d11 100644 --- a/src/registry/routes/component-info.ts +++ b/src/registry/routes/component-info.ts @@ -22,7 +22,7 @@ function getParams(component: Component) { const value = param.default !== undefined ? String(param.default) - : param.example!; + : String(param.example); return [paramName, value]; }) ); @@ -72,6 +72,9 @@ function componentInfo( href = `//${req.headers.host}${res.conf.prefix}`; } + // Get theme from cookie or default to dark + const theme = req.cookies?.['oc-theme'] || 'dark'; + res.send( infoView({ component, @@ -81,7 +84,8 @@ function componentInfo( parsedAuthor, repositoryUrl, sandBoxDefaultQs: urlBuilder.queryString(params), - title: 'Component Info' + title: 'Component Info', + theme }) ); }); diff --git a/src/registry/routes/helpers/get-components-history.ts b/src/registry/routes/helpers/get-components-history.ts index 69936637..df4a4574 100644 --- a/src/registry/routes/helpers/get-components-history.ts +++ b/src/registry/routes/helpers/get-components-history.ts @@ -8,7 +8,7 @@ interface UnformmatedComponentHistory { templateSize?: number; } -interface ComponentHistory { +export interface ComponentHistory { name: string; version: string; publishDate: string; diff --git a/src/registry/routes/index.ts b/src/registry/routes/index.ts index 6fd51607..f3005c34 100644 --- a/src/registry/routes/index.ts +++ b/src/registry/routes/index.ts @@ -86,6 +86,9 @@ export default function (repository: Repository) { componentsInfo.sort((a, b) => a.name.localeCompare(b.name)); + // Get theme from cookie or default to dark + const theme = req.cookies?.['oc-theme'] || 'dark'; + res.send( indexView( // @ts-ignore existing code relies on runtime merging @@ -100,7 +103,8 @@ export default function (repository: Repository) { q: req.query['q'] || '', stateCounts, templates: repository.getTemplatesInfo(), - title: 'OpenComponents Registry' + title: 'OpenComponents Registry', + theme }) ) ); diff --git a/src/registry/views/index.tsx b/src/registry/views/index.tsx index 44aefe12..c62eeea9 100644 --- a/src/registry/views/index.tsx +++ b/src/registry/views/index.tsx @@ -5,7 +5,6 @@ import List from './partials/components-list'; import Plugins from './partials/components-plugins'; import Templates from './partials/components-templates'; import Layout from './partials/layout'; -import Property from './partials/property'; import indexJS from './static/index'; export default function indexView(vm: VM) { @@ -20,67 +19,103 @@ export default function indexView(vm: VM) { ${indexJS}`; return ( - - - - - {!isLocal ? ( - - ) : ( - '' - )} - - - - - - - [ - name, - description - ]) - )} - /> + {!isLocal ? ( + <> + + History + + + Templates + + + Dependencies + + + Plugins + + + ) : ( + '' + )} + + + + + + + [name, description] + ) + )} + /> + + + + ); } diff --git a/src/registry/views/info.tsx b/src/registry/views/info.tsx index b46e04ab..1ba25a94 100644 --- a/src/registry/views/info.tsx +++ b/src/registry/views/info.tsx @@ -6,10 +6,9 @@ import ComponentParameters from './partials/component-parameters'; import getComponentState from './partials/component-state'; import ComponentVersions from './partials/component-versions'; import Layout from './partials/layout'; -import Property from './partials/property'; import infoJS from './static/info'; -interface Vm { +export interface Vm { parsedAuthor: { name?: string; email?: string; url?: string }; component: Component; componentDetail?: ComponentDetail; @@ -17,9 +16,23 @@ interface Vm { href: string; sandBoxDefaultQs: string; title: string; + theme: 'light' | 'dark'; repositoryUrl: string | null; } +function formatDate(date: Date | string) { + if (typeof date === 'string') return date; + + return new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric' + }).format(date); +} + function statsJs(name: string, componentDetail: ComponentDetail) { return ` @@ -30,54 +43,118 @@ function statsJs(name: string, componentDetail: ComponentDetail) { const ctx = document.getElementById('stats'); const dataPoints = []; const versionNumbers = Object.keys(componentDetail); - + for (const versionNumber of versionNumbers) { const versionData = componentDetail[versionNumber]; const date = new Date(versionData.publishDate); const size = Math.round(versionData.templateSize / 1024); - + // Add the data point to the array dataPoints.push({ x: date, y: size, version: versionNumber }); } - + const dataset = { label: "${name}", data: dataPoints, tension: 0.1, - borderWidth: 1, - backgroundColor: "#fbbf24", + borderWidth: 2, + borderColor: "#6366f1", + backgroundColor: "rgba(99, 102, 241, 0.1)", + pointBackgroundColor: "#ffffff", + pointBorderColor: "#6366f1", + pointBorderWidth: 2, + pointRadius: 6, + pointHoverRadius: 8, + pointHoverBackgroundColor: "#ffffff", + pointHoverBorderColor: "#8b5cf6", + pointHoverBorderWidth: 3, + fill: true, } - + new Chart(ctx, { type: 'line', data: { datasets: [dataset] }, options: { + responsive: true, + maintainAspectRatio: false, plugins: { + legend: { + display: true, + position: 'top', + labels: { + color: '#ffffff', + font: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 14, + weight: '500' + }, + padding: 20, + usePointStyle: true, + pointStyle: 'circle' + } + }, tooltip: { + backgroundColor: 'rgba(15, 15, 35, 0.95)', + titleColor: '#ffffff', + bodyColor: '#a1a1aa', + borderColor: 'rgba(99, 102, 241, 0.3)', + borderWidth: 1, + cornerRadius: 12, + displayColors: false, + titleFont: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 14, + weight: '600' + }, + bodyFont: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 13 + }, callbacks: { - footer(items) { + title: function(context) { + return 'Size: ' + context[0].parsed.y + ' KB'; + }, + footer: function(items) { const version = items[0].raw.version; return 'Version: ' + version; } } } }, - title: { - display: true, - text: "Package Sizes Over Time", - }, scales: { x: { type: "time", time: { - unit: "day", + unit: "month", + displayFormats: { + month: 'MMM yyyy' + } }, display: true, title: { display: true, - text: 'Date published' + text: 'Release Date', + color: '#a1a1aa', + font: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 14, + weight: '500' + } + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)', + drawBorder: false + }, + ticks: { + color: '#a1a1aa', + font: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 12 + }, + maxRotation: 45, + minRotation: 45 } }, y: { @@ -85,7 +162,25 @@ function statsJs(name: string, componentDetail: ComponentDetail) { display: true, title: { display: true, - text: 'Size in KB' + text: 'Size (KB)', + color: '#a1a1aa', + font: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 14, + weight: '500' + } + }, + grid: { + color: 'rgba(255, 255, 255, 0.1)', + drawBorder: false + }, + ticks: { + color: '#a1a1aa', + font: { + family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', + size: 12 + }, + stepSize: 50 } }, }, @@ -99,15 +194,6 @@ function statsJs(name: string, componentDetail: ComponentDetail) { export default function Info(vm: Vm) { const componentState = getComponentState(vm); - const ShowArray = (props: { title: string; arr?: string[] }) => ( - 0 ? props.arr.join(', ') : 'none' - } - /> - ); - const { component, dependencies, href, repositoryUrl, sandBoxDefaultQs } = vm; const componentHref = `${href}${component.name}/${component.version}/${sandBoxDefaultQs}`; @@ -138,107 +224,236 @@ export default function Info(vm: Vm) { `; return ( - - - << All components - -

- {component.name}   - -

-

- {component.description} {componentState()} -

- {statsAvailable ? ( - <> -

Stats

- - - ) : ( - '' - )} -

Component Info

- - - - - - - - {component.oc.files.template.size ? ( - - ) : ( - '' - )} - { - return param.default !== undefined || param.example !== undefined; - }) - .map(([paramName, param]) => { - const value = - param.default !== undefined - ? String(param.default) - : param.example!; - return [paramName, value]; - }) - )} - /> -

Code

-

- You can edit the following area and then - - {' '} - refresh{' '} - - or hit Enter to apply the change into the preview window. -

-
-

Component's href:

-
- -
-

Accept-Language header:

+ +
+ +

+ {component.name} +

+ +
+ Version: + +
+ +

+ {component.description} +

+ + + +
+ Latest Release: {formatDate(publishDate)} + {componentState()} +
+
+
+ +
+
+ {statsAvailable ? ( +
+
+
📈
+

Package Size History

+ +
+
+
+ +
+
+
+ ) : ( + '' + )} + + { + return ( + param.default !== undefined || param.example !== undefined + ); + }) + .map(([paramName, param]) => { + const value = + param.default !== undefined + ? String(param.default) + : String(param.example); + return [paramName, value]; + }) + )} + /> + +
+
+
+
🔗
+

Component URL

+
+
+

+ You can edit the following area and then refresh or hit Enter + to apply the change into the preview window. +

+ +
+

Accept-Language header:

+ +
+
+
+
+ +
+
+

Live Preview

+
+ + +
+
+