fix(core): update

This commit is contained in:
2022-01-06 01:20:03 +01:00
parent b83c492ee2
commit 6e1db0c713
24 changed files with 31990 additions and 93 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules/

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
.nogit/
# artifacts
coverage/
public/
pages/
# installs
node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
# custom

111
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,111 @@
# gitzone ci_docker
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci-cache/
key: '$CI_BUILD_STAGE'
before_script:
- npm install -g @shipzone/npmci
- npmci npm prepare
stages:
- security
- test
- release
- metadata
- pages
# ====================
# security stage
# ====================
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --production --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags:
- lossless
- docker
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=dev
tags:
- lossless
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
testBuild:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- notpriv
# ====================
# release stage
# ====================
release:
image: registry.gitlab.com/hosttoday/ht-docker-dbase:npmci
services:
- docker:stable-dind
stage: release
script:
- npmci docker login
- npmci docker build
- npmci docker test
- npmci docker push registry.gitlab.com
only:
- tags
tags:
- lossless
- docker
- priv
# ====================
# metadata stage
# ====================
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "npm test",
"name": "Run npm test",
"request": "launch",
"type": "node-terminal"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

38
Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node1
COPY ./ /app
WORKDIR /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN rm -rf node_modules && npm install
RUN npm run build
# gitzone dockerfile_service
## STAGE 2 // install production
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node2
WORKDIR /app
COPY --from=node1 /app /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN rm -r node_modules/ && npm install --production
## STAGE 3 // rebuild dependencies for alpine
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node3
WORKDIR /app
COPY --from=node2 /app /app
RUN npm rebuild --production
## STAGE 4 // the final production image with all dependencies in place
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node4
WORKDIR /app
COPY --from=node3 /app /app
### Healthchecks
RUN npm install -g @servezone/healthy
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]
EXPOSE 80
CMD ["npm", "start"]

View File

@@ -1,93 +0,0 @@
# npmcdn
a cdn mapping to packages on npm
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.com/losslessone/services/servezone/npmcdn.git
git branch -M master
git push -uf origin master
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://gitlab.com/losslessone/services/servezone/npmcdn/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://gitlab.com/-/experiment/new_project_readme_content:0ba4da2bb83083f24ae1c5b3d0992b34?https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

4
cli.js Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env node
process.env.CLI_CALL = 'true';
const cliTool = require('./dist_ts/index');
cliTool.runCli();

5
cli.ts.js Normal file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env node
process.env.CLI_CALL = 'true';
require('@gitzone/tsrun');
const cliTool = require('./ts/index');
cliTool.runCli();

25
npmextra.json Normal file
View File

@@ -0,0 +1,25 @@
{
"gitzone": {
"projectType": "service",
"module": {
"githost": "gitlab.com",
"gitscope": "losslessone/services/servezone",
"gitrepo": "npmcdn",
"description": "a cdn using npm as source",
"npmPackagename": "npmcdn",
"license": "UNLICENSED",
"projectDomain": "npmcdn.lossless.one",
"shortDescription": "undefined variable"
}
},
"npmci": {
"npmGlobalTools": [],
"dockerRegistryRepoMap": {
"registry.gitlab.com": "losslessone/services/servezone/npmcdn"
},
"dockerBuildargEnvMap": {
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
},
"npmRegistryUrl": "verdaccio.lossless.one"
}
}

30969
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

65
package.json Normal file
View File

@@ -0,0 +1,65 @@
{
"name": "npmcdn",
"version": "1.0.1",
"description": "a cdn using npm as source",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"author": "Lossless GmbH",
"license": "UNLICENSED",
"scripts": {
"test": "(tstest test/)",
"start": "(node --max_old_space_size=100 ./cli.js)",
"startTs": "(node cli.ts.js)",
"build": "(tsbuild --web)"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.17",
"@gitzone/tsrun": "^1.2.8",
"@gitzone/tstest": "^1.0.28",
"@gitzone/tswatch": "^1.0.30",
"@pushrocks/tapbundle": "^3.0.13",
"tslint": "^5.20.0",
"tslint-config-prettier": "^1.18.0"
},
"dependencies": {
"@losslessone_private/lole-serviceserver": "^1.0.24",
"@pushrocks/smartstate": "^1.0.14",
"@pushrocks/qenv": "^4.0.10",
"@pushrocks/smartdata": "^4.0.12",
"@pushrocks/projectinfo": "^4.0.5",
"@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartfile": "^8.0.10",
"@pushrocks/smartmarkdown": "^2.0.6",
"@pushrocks/smartmime": "^1.0.5",
"@pushrocks/smartnpm": "^1.0.39",
"@pushrocks/smartpromise": "^3.1.6",
"@pushrocks/smarttime": "^3.0.38",
"@pushrocks/tapbundle": "^3.2.14",
"@types/autocannon": "^4.1.1",
"@types/compression": "^1.7.2",
"@types/express": "^4.17.13",
"@types/splunk-logging": "^0.9.2",
"compression": "^1.7.4",
"express": "^4.17.1",
"lit-ntml": "^2.20.0",
"prom-client": "^13.2.0",
"splunk-logging": "^0.11.1",
"tty-table": "^4.1.3"
},
"private": true,
"browserslist": [
"last 1 chrome versions"
],
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
]
}

34
readme.md Normal file
View File

@@ -0,0 +1,34 @@
# @losslessone/services/servezone/npmcdn
undefined variable
## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/npmcdn)
* [gitlab.com (source)](https://gitlab.com/losslessone/services/servezone/npmcdn)
* [github.com (source mirror)](https://github.com/losslessone/services/servezone/npmcdn)
* [docs (typedoc)](https://losslessone/services/servezone.gitlab.io/npmcdn/)
## Status for master
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/losslessone/services/servezone/npmcdn/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/losslessone/services/servezone/npmcdn/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/npmcdn)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/losslessone/services/servezone/npmcdn)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/npmcdn)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/npmcdn)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/npmcdn)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage
Use TypeScript for best in class intellisense
For further information read the linked docs at the top of this readme.
> UNLICENSED licensed | **©** [Lossless GmbH](https://lossless.gmbh)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

9
test/devserver.ts Normal file
View File

@@ -0,0 +1,9 @@
import { UiPublicServer } from '../ts';
export const defaultPublicServer = new UiPublicServer({
port: 3000,
packageBaseDirectory: './ts',
npmRegistryUrl: 'https://registry.npmjs.org',
allowedPackages: ['@pushrocks/smartfile', '@pushrocks/smarturl', '@pushrocks/notthere'],
mode: 'dev',
});

99
test/test.node.ts Normal file
View File

@@ -0,0 +1,99 @@
import { tap, expect } from '@pushrocks/tapbundle';
import * as uiPublicServer from '../ts/npm-publicserver.classes.uipublicserver';
import * as smartnetwork from '@pushrocks/smartnetwork';
import * as smartrequest from '@pushrocks/smartrequest';
const plugins = {
smartnetwork,
smartrequest,
};
let testserverInstance: uiPublicServer.UiPublicServer;
tap.test('should create an instance of PublicServer', async () => {
testserverInstance = new uiPublicServer.UiPublicServer({
npmRegistryUrl: 'https://registry.npmjs.org',
port: 3000,
allowedPackages: ['@pushrocks/smartfile'],
mode: 'prod',
});
expect(testserverInstance).to.be.instanceOf(uiPublicServer.UiPublicServer);
});
tap.test('should start the server', async () => {
const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
const result1 = await smartnetworkInstance.isLocalPortUnused(3000);
await testserverInstance.startServer();
const result2 = await smartnetworkInstance.isLocalPortUnused(3000);
expect(result1).to.be.true;
expect(result2).to.be.false;
});
tap.skip.test('optional manual testing', async (toolsArg) => {
await toolsArg.delayFor(30000);
});
tap.test('should NOT deliver a file for a malformed org', async () => {
const response = await plugins.smartrequest.request('http://localhost:3000/someorg/somemodule', {
method: 'GET',
});
console.log(response.body);
expect(response.body).to.equal('npmorg "someorg" must start with @');
});
tap.test('should NOT deliver a file for a nonexisting file', async () => {
const response = await plugins.smartrequest.request(
'http://localhost:3000/@pushrocks/smartfile/readme2.md',
{
method: 'GET',
}
);
console.log(response.body);
expect(response.body.includes('@pushrocks/smartfile@ does not have a file at')).to.be.true;
});
tap.test('should deliver a file for an existing file', async () => {
const response = await plugins.smartrequest.request(
'http://localhost:3000/@pushrocks/smartfile/readme.md',
{
method: 'GET',
}
);
expect(response.body.startsWith('# @pushrocks/smartfile')).to.be.true;
});
tap.test('should deliver different versions', async () => {
const response = await plugins.smartrequest.request(
'http://localhost:3000/@pushrocks/smartfile/package.json?version=7',
{
method: 'GET',
}
);
const packageJson = response.body;
expect(packageJson.version.startsWith('7')).to.be.true;
const response2 = await plugins.smartrequest.request(
'http://localhost:3000/@pushrocks/smartfile/package.json?version=8.x.x',
{
method: 'GET',
}
);
const packageJson2 = response2.body;
expect(packageJson2.version.startsWith('8')).to.be.true;
const response3 = await plugins.smartrequest.request(
'http://localhost:3000/@pushrocks/smartfile/package.json?version=6.0.6',
{
method: 'GET',
}
);
const packageJson3 = response3.body;
expect(packageJson3.version).to.equal('6.0.6');
});
tap.test('should stop the server', async () => {
await testserverInstance.stopServer();
});
tap.start();

22
ts/index.ts Normal file
View File

@@ -0,0 +1,22 @@
export * from './npm-publicserver.classes.uipublicserver';
import { UiPublicServer } from '.';
process.env.UIP_ENV = process.env.BACKEND_URL.includes('develop-backend') ? 'dev' : 'prod';
export const defaultPublicServer = new UiPublicServer({
port: 3000,
packageBaseDirectory: './public/',
npmRegistryUrl: 'https://registry.npmjs.org/',
allowedPackages: [
'@pushrocks/smartfile'
],
mode: process.env.UIP_ENV === 'dev' ? 'dev' : 'prod',
log: false,
});
export const runCli = async () => {
}
export const stop = async () => {}

12
ts/interfaces.ts Normal file
View File

@@ -0,0 +1,12 @@
import * as url from 'url';
export interface ISimpleRequest {
headers: {};
parsedUrl: url.URL;
}
export interface ISimpleResponse {
headers: { [key: string]: string };
status: number;
body: string | Buffer;
}
export type IRenderFunction = (req: ISimpleRequest) => Promise<ISimpleResponse>;

20
ts/logging.ts Normal file
View File

@@ -0,0 +1,20 @@
import * as plugins from './plugins';
export const logger = new plugins.splunkLogging.Logger({
token: '',
});
logger.send({
message: {
package: '',
subFolder: '',
filePath: '',
status: 200,
},
severity: 'info',
metadata: {
host: 'ui-publicserver',
source: 'nodejs',
sourcetype: 'process',
},
});

View File

@@ -0,0 +1,403 @@
import * as interfaces from './interfaces';
import * as plugins from './plugins';
import * as paths from './paths';
import * as ntml from './ntml';
export interface IPublicServerOptions {
packageBaseDirectory?: string;
npmRegistryUrl?: string;
allowedPackages?: string[];
port?: number;
mode: 'dev' | 'prod';
log?: boolean;
}
/**
* the main public server instance
*/
export class UiPublicServer {
public projectinfo = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
public readme = new plugins.smartmarkdown.SmartMarkdown().markdownToHtml(
plugins.smartfile.fs.toStringSync(plugins.path.join(paths.packageDir, 'readme.md'))
);
public startedAt: string;
private server: plugins.http.Server;
private npmRegistry: plugins.smartnpm.NpmRegistry;
private corsDev = '*';
private corsProd = ''; // TODO: Define allowed URLs
public defaultOptions: IPublicServerOptions = {
packageBaseDirectory: './',
npmRegistryUrl: 'https://registry.npmjs.org',
allowedPackages: [],
port: 8080,
mode: 'dev',
log: true,
};
public options: IPublicServerOptions;
constructor(optionsArg?: IPublicServerOptions) {
// lets create an npm instance for the registry that we are talking to
this.options = {
...this.defaultOptions,
...optionsArg,
};
if (!this.options.packageBaseDirectory.endsWith('/')) {
this.options.packageBaseDirectory += '/';
}
this.npmRegistry = new plugins.smartnpm.NpmRegistry({
npmRegistryUrl: this.options.npmRegistryUrl,
});
}
/**
* starts the server
*/
public async startServer() {
console.log('starting the uipublicserver');
const done = plugins.smartpromise.defer();
const expressApplication = plugins.express();
const shouldCompress = (req: plugins.express.Request, res: plugins.express.Response) => {
if (req.headers['x-no-compression']) {
// don't compress responses with this request header
return false;
}
// fallback to standard filter function
return plugins.compression.filter(req, res);
};
expressApplication.use(plugins.compression({ filter: shouldCompress }));
if (this.options.mode === 'dev') {
expressApplication.use('/peek/', async (req, res) => {
const host = req.headers.host || 'localhost';
const parsedUrl = new plugins.url.URL(`http://${host}${req.url}`);
const simpleResponse: interfaces.ISimpleResponse = await this.renderPeek({
headers: req.headers,
parsedUrl,
});
for (const header of Object.keys(simpleResponse.headers)) {
res.setHeader(header, simpleResponse.headers[header]);
}
res.status(simpleResponse.status);
res.write(simpleResponse.body);
res.end();
});
expressApplication.use('/readme/', async (req, res) => {
const host = req.headers.host || 'localhost';
const parsedUrl = new plugins.url.URL(`http://${host}${req.url}`);
const simpleResponse: interfaces.ISimpleResponse = await this.renderReadme({
headers: req.headers,
parsedUrl,
});
for (const header of Object.keys(simpleResponse.headers)) {
res.setHeader(header, simpleResponse.headers[header]);
}
res.status(simpleResponse.status);
res.write(simpleResponse.body);
res.end();
});
}
expressApplication.use('/', async (req, res) => {
const host = req.headers.host || 'localhost';
const parsedUrl = new plugins.url.URL(`http://${host}${req.url}`);
const simpleResponse: interfaces.ISimpleResponse = await this.render({
headers: req.headers,
parsedUrl,
});
for (const header of Object.keys(simpleResponse.headers)) {
res.setHeader(header, simpleResponse.headers[header]);
}
// CORS
res.setHeader('ui-public-server-version', this.projectinfo.version);
res.setHeader('access-control-allow-origin', true ? this.corsDev : this.corsProd); // TODO: replace true with check for env
res.status(simpleResponse.status);
res.write(simpleResponse.body);
res.end();
});
// note: We assume that ssl termination is done in reverse proxy in kubernetes. so simple http should suffice here.
this.server = expressApplication.listen(this.options.port, () => {
console.log(`listening on port ${this.options.port}`);
this.startedAt = new Date().toISOString();
done.resolve();
});
await done.promise;
}
public disableLogging() {
this.options.log = false;
}
/**
* stops the server
*/
public async stopServer() {
const done = plugins.smartpromise.defer();
if (!this.server) {
console.log('server does not seem to be started, so no need to stop it');
return;
}
this.server.close(() => {
done.resolve();
});
await done.promise;
}
public requestMap: { [key: string]: plugins.smartpromise.Deferred<any> } = {};
/**
* renders a response using interfaces.ISimpleRequest and interfaces.ISimpleResponse
* @param req
*/
private render: interfaces.IRenderFunction = async (req) => {
const pathArray = req.parsedUrl.pathname.split('/');
if (pathArray.length < 3) {
const textArray = [
`serving javascript to the web for Lossless GmbH.<br/>`,
`Running in mode: <b>${this.options.mode}</b><br/><br/>`,
];
if (this.options.mode === 'dev') {
textArray.push(
`<b>Wondering what we serve?</b> <a href="/peek/">Lets take a peek!</a><br/>`
);
textArray.push(`<b>Documentation:</b> <a href="/readme/">Read the docs</a>`);
}
return {
headers: {
'content-type': 'text/html',
},
status: 201,
body: await plugins.litNtml.html`
${ntml.getBody(this, textArray)}
`,
};
}
// lets care about the npm organization
const npmOrg = pathArray[1];
if (!npmOrg.startsWith('@')) {
console.log('malformed npmorg');
return {
status: 500,
headers: {
'content-type': 'text/html',
},
body: `npmorg "${npmOrg}" must start with @`,
};
}
// lets care about the packageName
const npmPackage = pathArray[2];
const packageName = `${npmOrg}/${npmPackage}`;
if (this.options.log) {
console.log(
`got a request for package ${packageName} on registry ${this.npmRegistry.options.npmRegistryUrl}`
);
}
if (!this.options.allowedPackages.includes(packageName)) {
return {
headers: {
'content-type': 'text/html',
},
status: 503,
body: await plugins.litNtml.html`
the requested package is not allowlisted for public access
`,
};
}
// lets care about the inner package path
let filePath = this.options.packageBaseDirectory;
let first = true;
for (let i = 3; i < pathArray.length; i++) {
if (first) {
filePath += './';
first = false;
} else {
filePath += '/';
}
filePath += pathArray[i];
}
// lets care about version and disttag
const version = req.parsedUrl.searchParams.get('version') || '';
const distTag = req.parsedUrl.searchParams.get('disttag') || '';
const requestDescriptor = `${packageName}/${filePath}/${distTag}/${version}`;
let smartfile: plugins.smartfile.Smartfile;
// protect against parallel requests
if (this.requestMap[requestDescriptor]) {
smartfile = await this.requestMap[requestDescriptor].promise;
} else {
this.requestMap[requestDescriptor] = plugins.smartpromise.defer();
smartfile = await this.npmRegistry
.getFileFromPackage(packageName, filePath, {
version,
distTag,
})
.catch((err) => {
console.log(err);
return null;
});
this.requestMap[requestDescriptor].resolve(smartfile);
delete this.requestMap[requestDescriptor];
}
if (!smartfile) {
return {
status: 404,
headers: {
'content-type': 'text/html',
},
body: await ntml.getBody(
this,
`${packageName}@${version} does not have a file at "${filePath}"`
),
};
}
return {
headers: {
'cache-control': `max-age=${
(version
? plugins.smarttime.getMilliSecondsFromUnits({ months: 1 })
: plugins.smarttime.getMilliSecondsFromUnits({ days: 1 })) / 1000
}`,
'content-length': smartfile.contentBuffer.length.toString(),
'content-type': plugins.smartmime.detectMimeType(smartfile.path),
},
status: 200,
body: smartfile.contentBuffer,
};
};
private renderPeek: interfaces.IRenderFunction = async (req) => {
const pathArray = req.parsedUrl.pathname.split('/');
const npmOrg = pathArray[1];
const npmPackage = pathArray[2];
const npmPackageName = `${npmOrg}/${npmPackage}`;
const distTag = req.parsedUrl.searchParams.get('disttag');
const version = req.parsedUrl.searchParams.get('version');
// lets care about cors
if (!npmOrg || !npmPackage || !this.options.allowedPackages.includes(npmPackageName)) {
return {
status: 200,
headers: {
'content-type': 'text/html',
},
body: `
${await ntml.getBody(this, [
`<a href="../">../ -> Go back</a><br/>`,
`<b>allowlisted packages:</b><br/>`,
...this.options.allowedPackages.map((packageArg) => {
return `
<div>"${packageArg}" -> mapping to "${this.options.packageBaseDirectory}" inside the package <a href="/peek/${packageArg}">Peek...</a></div>
`;
}),
])}
`,
};
}
const result = await this.npmRegistry
.getFilesFromPackage(npmPackageName, plugins.path.join(this.options.packageBaseDirectory), {
version: version,
distTag: distTag,
})
.catch((err) => {
return;
});
if (!result) {
return {
status: 404,
headers: {
'content-type': 'text/html',
},
body: `
${await ntml.getBody(this, [
`<a href="/peek/">/peek/ -> Go to peek overview</a><br/>`,
`<b>Not found: package ${npmPackageName}@${
version || 'latest'
} is not available on the supplied registry</b><br/>`,
])}
`,
};
}
const packageInfo = await this.npmRegistry.getPackageInfo(npmPackageName);
return {
status: 200,
headers: {
'content-type': 'text/html',
},
body: `
${await ntml.getBody(this, [
`<a href="/peek/">/peek/ -> Go to peek overview</a><br/><br/>`,
`<h1>${npmPackageName}@${version || distTag || '<i>inferred</i> latest'}</h1>`,
`<b>Versions:</b> ${packageInfo.allVersions
.map(
(versionArg) =>
`<a href="./${npmPackage}?version=${versionArg.version}">${versionArg.version}</a> | `
)
.join('')}<br/><br/>`,
`<b>DistTags:</b> ${
packageInfo.allDistTags
.map(
(distTagArg) =>
`<a href="./${npmPackage}?disttag=${distTagArg.name}">${distTagArg.name} (${distTagArg.targetVersion})</a> | `
)
.join('') || 'no dist tags found!'
}<br/><br/>`,
`<b>File Overview at version and path:</b><br/>`,
`"<b>${npmPackageName}@${
version || distTag || '<i>inferred</i> latest'
}</b>" under internal path "<b>${this.options.packageBaseDirectory}</b>"<br/></br>`,
...result.map((smartfile) => {
const displayPath = smartfile.path.replace(
plugins.path.join('package', this.options.packageBaseDirectory),
''
);
return `<a href="/${npmPackageName}/${displayPath}?version=${version || ''}&disttag=${
distTag || ''
}">${displayPath}</a><br/>`;
}),
])}
`,
};
};
private renderReadme: interfaces.IRenderFunction = async (req) => {
return {
status: 200,
headers: {
'content-type': 'text/html',
},
body: await ntml.getBody(this, [
`<style>
pre {
display: block;
background: #fafafa;
border: 1px dotted #CCC;
padding: 20px;
}
</style>`,
this.readme,
]),
};
};
}

59
ts/ntml/body.ts Normal file
View File

@@ -0,0 +1,59 @@
import { UiPublicServer } from '../npm-publicserver.classes.uipublicserver';
import * as plugins from '../plugins';
export const getBody = async (uipublicServerArg: UiPublicServer, contentArg: string | string[]) => {
return await plugins.litNtml.html`
<head></head>
<body>
<style>
body {
margin: 0px;
padding: 20px;
background: #eeeeeb;
font-family: 'Courier New', Courier, monospace;
}
.main {
background: #ffffff;
max-width: 1000px;
margin: auto;
border: 1px dashed #CCCCCC;
border-radius: 10px;
padding: 20px;
padding-bottom: 60px;
position:relative;
overflow: hidden;
}
.logo {
text-align: center;
background: #fafafa;
border-bottom: 1px dotted #CCCCCC;
margin-left: -20px;
margin-top: -20px;
margin-right: -20px;
margin-bottom: 20px;
padding: 20px;
}
.footer {
position: absolute;
bottom: 0px;
right: 0px;
left: 0px;
text-align: center;
line-height: 40px;
background: #fafafa;
}
</style>
<div class="main">
<div class="logo">
<img src="https://assetbroker.lossless.one/brandfiles/lossless/svg-minimal-bright.svg" onclick="window.location.href = 'https://lossless.com'" />
</div>
${contentArg}
<div class="footer">
UiPublicServer v${uipublicServerArg.projectinfo.version} |
running since ${uipublicServerArg.startedAt} |
<a href="https://lossless.gmbh" target="_blank">Legal Info</a></div>
</div>
</body>
`;
};

1
ts/ntml/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './body';

3
ts/paths.ts Normal file
View File

@@ -0,0 +1,3 @@
import * as plugins from './plugins';
export const packageDir = plugins.path.join(__dirname, '../');

36
ts/plugins.ts Normal file
View File

@@ -0,0 +1,36 @@
// node native
import * as http from 'http';
import * as url from 'url';
import * as path from 'path';
export { http, url, path };
// @pushrocks scope (maintained by Lossless GmbH)
import * as projectinfo from '@pushrocks/projectinfo';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartfile from '@pushrocks/smartfile';
import * as smartmime from '@pushrocks/smartmime';
import * as smartmarkdown from '@pushrocks/smartmarkdown';
import * as smartnpm from '@pushrocks/smartnpm';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smarttime from '@pushrocks/smarttime';
export {
projectinfo,
smartdelay,
smartfile,
smartmime,
smartmarkdown,
smartnpm,
smartpromise,
smarttime,
};
// unscoped packages
import compression from 'compression';
import express from 'express';
import * as litNtml from 'lit-ntml';
import * as promClient from 'prom-client';
import splunkLogging from 'splunk-logging';
export { compression, express, litNtml, promClient, splunkLogging };

17
tslint.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [true, "always"],
"no-console": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"member-ordering": {
"options":{
"order": [
"static-method"
]
}
}
},
"defaultSeverity": "warning"
}