diff --git a/src/cli/domain/package-components.ts b/src/cli/domain/package-components.ts index e46eb6e19..40b4a4744 100644 --- a/src/cli/domain/package-components.ts +++ b/src/cli/domain/package-components.ts @@ -13,6 +13,38 @@ export interface PackageOptions { production?: boolean; } +interface Sizes { + client: number; + server?: number; +} + +function checkSizes(folder: string) { + const jsFiles = fs.readdirSync(folder).filter(x => x.endsWith('.js')); + + const sizes: Sizes = { + client: 0 + }; + + for (const file of jsFiles) { + if (file === 'server.js') { + sizes.server = fs.statSync(path.join(folder, file)).size; + } else { + sizes.client += fs.statSync(path.join(folder, file)).size; + } + } + + return sizes; +} + +function addSizes(folder: string, component: Component, sizes: Sizes) { + component.oc.files.template.size = sizes.client; + if (sizes.server) { + component.oc.files.dataProvider.size = sizes.server; + } + + fs.writeJsonSync(path.join(folder, 'package.json'), component, { spaces: 2 }); +} + const packageComponents = () => async (options: PackageOptions): Promise => { @@ -55,7 +87,11 @@ const packageComponents = componentPath }); const compile = promisify(ocTemplate.compile); - return compile(compileOptions); + const component = await compile(compileOptions); + + addSizes(publishPath, component, checkSizes(publishPath)); + + return component; }; export default packageComponents; diff --git a/src/registry/domain/components-details.ts b/src/registry/domain/components-details.ts index e82d02cab..5b2b1efce 100644 --- a/src/registry/domain/components-details.ts +++ b/src/registry/domain/components-details.ts @@ -52,7 +52,8 @@ export default function componentsDetails(conf: Config, cdn: StorageAdapter) { true ); details.components[name][version] = { - publishDate: content.oc.date || 0 + publishDate: content.oc.date || 0, + templateSize: content.oc.files.template.size }; }) ) diff --git a/src/registry/routes/component-info.ts b/src/registry/routes/component-info.ts index cdeea133b..e49b190ca 100644 --- a/src/registry/routes/component-info.ts +++ b/src/registry/routes/component-info.ts @@ -6,7 +6,7 @@ import infoView from '../views/info'; import isUrlDiscoverable from './helpers/is-url-discoverable'; import * as urlBuilder from '../domain/url-builder'; import type { Repository } from '../domain/repository'; -import { Component, Config } from '../../types'; +import { Component, ComponentDetail, Config } from '../../types'; import { Request, Response } from 'express'; function getParams(component: Component) { @@ -38,9 +38,10 @@ function componentInfo( err: InfoError | string | null, req: Request, res: Response, - component: Component + component?: Component, + componentDetail?: ComponentDetail ): void { - if (err) { + if (!component || err) { res.errorDetails = (err as any).registryError || err; res.status(404).json(err); return; @@ -67,6 +68,7 @@ function componentInfo( res.send( infoView({ component, + componentDetail, dependencies: Object.keys(component.dependencies || {}), href, parsedAuthor, @@ -89,24 +91,32 @@ export default function componentInfoRoute( conf: Config, repository: Repository ) { - return function (req: Request, res: Response): void { - fromPromise(repository.getComponent)( - req.params['componentName'], - req.params['componentVersion'], - (registryError: any, component) => { - if (registryError && conf.fallbackRegistryUrl) { - return getComponentFallback.getComponentInfo( - conf, - req, - res, - registryError, - (fallbackError, fallbackComponent) => - componentInfo(fallbackError, req, res, fallbackComponent) - ); - } - - componentInfo(registryError, req, res, component); + async function handler(req: Request, res: Response): Promise { + try { + const history = await repository + .getComponentsDetails() + .catch(() => undefined); + const componentDetail = history?.components[req.params['componentName']]; + const component = await repository.getComponent( + req.params['componentName'], + req.params['componentVersion'] + ); + componentInfo(null, req, res, component, componentDetail); + } catch (registryError) { + if (conf.fallbackRegistryUrl) { + return getComponentFallback.getComponentInfo( + conf, + req, + res, + registryError as any, + (fallbackError, fallbackComponent) => + componentInfo(fallbackError, req, res, fallbackComponent) + ); } - ); - }; + + componentInfo(registryError as any, req, res); + } + } + + return handler; } diff --git a/src/registry/routes/helpers/get-components-history.ts b/src/registry/routes/helpers/get-components-history.ts index 3853c8dbb..7befb51e0 100644 --- a/src/registry/routes/helpers/get-components-history.ts +++ b/src/registry/routes/helpers/get-components-history.ts @@ -5,12 +5,14 @@ interface UnformmatedComponentHistory { name: string; version: string; publishDate: number; + templateSize?: number; } interface ComponentHistory { name: string; version: string; publishDate: string; + templateSize?: number; } export default function getComponentsHistory( @@ -23,6 +25,7 @@ export default function getComponentsHistory( result.push({ name, publishDate: details.publishDate, + templateSize: details.templateSize, version }); } @@ -33,6 +36,7 @@ export default function getComponentsHistory( .map(x => ({ name: x.name, version: x.version, + templateSize: x.templateSize, publishDate: !x.publishDate ? 'Unknown' : dateStringified(new Date(x.publishDate)) diff --git a/src/registry/views/info.ts b/src/registry/views/info.ts index c285dc812..3b27db758 100644 --- a/src/registry/views/info.ts +++ b/src/registry/views/info.ts @@ -1,4 +1,4 @@ -import { Component } from '../../types'; +import { Component, ComponentDetail } from '../../types'; import getComponentAuthor from './partials/component-author'; import getComponentParameters from './partials/component-parameters'; @@ -12,6 +12,7 @@ import isTemplateLegacy from '../../utils/is-template-legacy'; interface Vm { parsedAuthor: { name?: string; email?: string; url?: string }; component: Component; + componentDetail?: ComponentDetail; dependencies: string[]; href: string; sandBoxDefaultQs: string; @@ -19,6 +20,80 @@ interface Vm { repositoryUrl: string | null; } +function statsJs(componentDetail: ComponentDetail) { + return ` + + + + `; +} + export default function info(vm: Vm): string { const componentAuthor = getComponentAuthor(vm); const componentParameters = getComponentParameters(vm); @@ -49,10 +124,18 @@ export default function info(vm: Vm): string { ? 'legacy' : compiler + '@' + component.oc.files.template.version })`; + const statsAvailable = + !!vm.componentDetail && Object.keys(vm.componentDetail).length > 1; const content = `<< All components

${component.name}  ${componentVersions()}

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

+ ${ + statsAvailable + ? `

Stats

+ ` + : '' + }

Component Info

${property('Repository', repositoryUrl || 'not available', !!repositoryUrl)} ${componentAuthor()} @@ -61,6 +144,7 @@ export default function info(vm: Vm): string { ${property('Template', template)} ${showArray('Node.js dependencies', dependencies)} ${showArray('Plugin dependencies', component.oc.plugins)} + ${component.oc.files.template.size ? property('Template size', `${Math.round(component.oc.files.template.size / 1024)} kb`) : ''} ${componentParameters()}

Code

@@ -81,9 +165,12 @@ export default function info(vm: Vm): string { `; - const scripts = ``; + + ${statsAvailable ? statsJs(vm.componentDetail!) : ''} + `; return layout({ content, scripts }); } diff --git a/src/registry/views/partials/components-history.ts b/src/registry/views/partials/components-history.ts index 9d147e244..ad78123c2 100644 --- a/src/registry/views/partials/components-history.ts +++ b/src/registry/views/partials/components-history.ts @@ -4,17 +4,20 @@ export default function componentsHistory(vm: VM): string { const componentRow = ({ name, publishDate, - version + version, + templateSize }: { name: string; + templateSize?: number; publishDate: string; version: string; - }) => - ` + }) => { + return `

`; + }; const history = vm.componentsHistory || []; diff --git a/src/registry/views/partials/components-list.ts b/src/registry/views/partials/components-list.ts index d6dd1dba1..eb708619a 100644 --- a/src/registry/views/partials/components-list.ts +++ b/src/registry/views/partials/components-list.ts @@ -3,11 +3,16 @@ import getSelectedCheckbox from './selected-checkbox'; export default function componentsList(vm: VM): string { const isLocal = vm.type !== 'oc-registry'; + const isRemote = !isLocal; + const sizeAvailable = vm.components.some( + component => component.oc.files.template.size + ); const selectedCheckbox = getSelectedCheckbox(vm); - const extraColumn = !isLocal + const remoteServerColumns = isRemote ? '
Updated
Activity
' : ''; + const sizeColumn = sizeAvailable ? '
Size
' : ''; const componentRow = (component: ParsedComponent) => { const componentState = component.oc.state @@ -20,16 +25,21 @@ export default function componentsList(vm: VM): string { component.oc.state === 'deprecated' || component.oc.state === 'experimental'; - const extraColumn = !isLocal + const remoteServerColumns = isRemote ? `
${ component.oc.stringifiedDate || '' }
${component.allVersions.length}
` : ''; + const sizeColumn = sizeAvailable + ? component.oc.files.template.size + ? `
${Math.round(component.oc.files.template.size / 1024)} kb
` + : '
? Kb
' + : ''; return ` ${vm.components.map(componentRow).join('')} `; diff --git a/src/types.ts b/src/types.ts index 11fb75b0b..4d9448351 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,6 +18,7 @@ interface ComponentHistory { name: string; publishDate: string; version: string; + templateSize: number; } export interface TemplateInfo { @@ -31,11 +32,16 @@ export interface TemplateInfo { version: string; } +export type ComponentDetail = { + [componentVersion: string]: { + publishDate: number; + templateSize?: number; + }; +}; + export interface ComponentsDetails { components: { - [componentName: string]: { - [componentVersion: string]: { publishDate: number }; - }; + [componentName: string]: ComponentDetail; }; lastEdit: number; } @@ -71,6 +77,7 @@ interface OcConfiguration { hashKey: string; src: string; type: string; + size?: number; }; static: string[]; template: { @@ -78,6 +85,7 @@ interface OcConfiguration { src: string; type: string; version: string; + size?: number; }; env?: string; }; diff --git a/test/unit/cli-domain-package-components.js b/test/unit/cli-domain-package-components.js index 8a9c0ab5c..9564d6c1d 100644 --- a/test/unit/cli-domain-package-components.js +++ b/test/unit/cli-domain-package-components.js @@ -23,7 +23,10 @@ describe('cli : domain : package-components', () => { const fsMock = { existsSync: sinon.stub(), emptyDir: sinon.stub().resolves(), - readJson: sinon.stub() + readJson: sinon.stub(), + readdirSync: sinon.stub().returns(['template.js']), + statSync: sinon.stub().returns({ size: 300 }), + writeJsonSync: sinon.stub().returns({}) }; fsMock.existsSync.returns(true); @@ -38,7 +41,8 @@ describe('cli : domain : package-components', () => { return { compile: function (options, callback) { if (options.componentPath === '.') { - callback(null, 'ok'); + callback(null, { oc: { files: { template: {} } } }); + // callback(null, 'ok'); } if (options.componentPath === '') { callback(new Error('Ouch')); @@ -63,15 +67,19 @@ describe('cli : domain : package-components', () => { describe('when packaging', () => { describe('when component is valid', () => { const PackageComponents = initialise(); - it('should correctly invoke the callback when template succeed packaging', done => { + it('should add sizes and correctly invoke the callback when template succeed packaging', done => { let info; PackageComponents()({ componentPath: '.', minify: true }) - .then(res => (info = res)) + .then(res => { + info = res; + }) .finally(() => { - expect(info).to.equal('ok'); + expect(info).to.deep.equal({ + oc: { files: { template: { size: 300 } } } + }); done(); }); }); diff --git a/test/unit/registry-domain-components-details.js b/test/unit/registry-domain-components-details.js index 53fe48a0c..86805b998 100644 --- a/test/unit/registry-domain-components-details.js +++ b/test/unit/registry-domain-components-details.js @@ -103,7 +103,9 @@ describe('registry : domain : components-details', () => { before(done => { stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).resolves(details); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon.stub().resolves('ok'); const componentsDetails = ComponentsDetails(conf, stubs); @@ -144,7 +146,9 @@ describe('registry : domain : components-details', () => { before(done => { stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).resolves(details); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon.stub().resolves('ok'); const componentsDetails = ComponentsDetails(conf, stubs); @@ -161,7 +165,7 @@ describe('registry : domain : components-details', () => { components: { hello: { '1.0.0': { publishDate: 1459864868000 }, - '1.0.1': { publishDate: 1459864868001 } + '1.0.1': { publishDate: 1459864868001, templateSize: 300 } } } }) @@ -173,7 +177,12 @@ describe('registry : domain : components-details', () => { fireStub.reset(); stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).resolves(details); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { + date: 1459864868001, + files: { template: { size: 300 } } + } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon .stub() @@ -208,7 +217,9 @@ describe('registry : domain : components-details', () => { before(done => { stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).resolves(details); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon.stub().resolves('ok'); const componentsDetails = ComponentsDetails(conf, stubs); @@ -225,7 +236,7 @@ describe('registry : domain : components-details', () => { components: { hello: { '1.0.0': { publishDate: 1459864868000 }, - '1.0.1': { publishDate: 1459864868001 } + '1.0.1': { publishDate: 1459864868001, templateSize: 300 } } } }); @@ -298,8 +309,12 @@ describe('registry : domain : components-details', () => { before(done => { stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).resolves('not found'); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868000 } }); - stubs.getJson.onCall(2).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868000, files: { template: { size: 300 } } } + }); + stubs.getJson.onCall(2).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon.stub().resolves('ok'); const componentsDetails = ComponentsDetails(conf, stubs); @@ -344,8 +359,12 @@ describe('registry : domain : components-details', () => { before(done => { stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).rejects('not found'); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868000 } }); - stubs.getJson.onCall(2).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868000, files: { template: { size: 300 } } } + }); + stubs.getJson.onCall(2).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon.stub().resolves('ok'); const componentsDetails = ComponentsDetails(conf, stubs); @@ -361,8 +380,8 @@ describe('registry : domain : components-details', () => { lastEdit: 1234567890, components: { hello: { - '1.0.0': { publishDate: 1459864868000 }, - '1.0.1': { publishDate: 1459864868001 } + '1.0.0': { publishDate: 1459864868000, templateSize: 300 }, + '1.0.1': { publishDate: 1459864868001, templateSize: 300 } } } }) @@ -374,8 +393,12 @@ describe('registry : domain : components-details', () => { fireStub.reset(); stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).rejects('not found'); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868000 } }); - stubs.getJson.onCall(2).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868000, files: { template: { size: 300 } } } + }); + stubs.getJson.onCall(2).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon .stub() @@ -410,8 +433,12 @@ describe('registry : domain : components-details', () => { before(done => { stubs = { getJson: sinon.stub() }; stubs.getJson.onCall(0).rejects('not found'); - stubs.getJson.onCall(1).resolves({ oc: { date: 1459864868000 } }); - stubs.getJson.onCall(2).resolves({ oc: { date: 1459864868001 } }); + stubs.getJson.onCall(1).resolves({ + oc: { date: 1459864868000, files: { template: { size: 300 } } } + }); + stubs.getJson.onCall(2).resolves({ + oc: { date: 1459864868001, files: { template: { size: 300 } } } + }); stubs.maxConcurrentRequests = 20; stubs.putFileContent = sinon.stub().resolves('ok'); const componentsDetails = ComponentsDetails(conf, stubs); @@ -427,8 +454,8 @@ describe('registry : domain : components-details', () => { lastEdit: 1234567890, components: { hello: { - '1.0.0': { publishDate: 1459864868000 }, - '1.0.1': { publishDate: 1459864868001 } + '1.0.0': { publishDate: 1459864868000, templateSize: 300 }, + '1.0.1': { publishDate: 1459864868001, templateSize: 300 } } } });