fix: modernize nullresolve runtime

This commit is contained in:
2026-04-29 11:07:46 +00:00
parent a272df13c8
commit f5338fd11a
14 changed files with 4418 additions and 4555 deletions
+12 -48
View File
@@ -1,4 +1,4 @@
name: Docker (tags)
name: Docker (non-tag pushes)
on:
push:
@@ -6,44 +6,12 @@ on:
- '**'
env:
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
IMAGE: code.foss.global/host.today/ht-docker-node:szci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
jobs:
security:
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
@@ -54,18 +22,14 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
pnpm install -g @git.zone/tsdocker@latest
pnpm install
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test
run: pnpm test
- name: Test build
run: |
npmci npm prepare
npmci node install stable
npmci npm install
npmci command npm run build
- name: Build image
run: tsdocker build
- name: Test image
run: tsdocker test
+16 -81
View File
@@ -6,75 +6,15 @@ on:
- '*'
env:
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
IMAGE: code.foss.global/host.today/ht-docker-node:szci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
jobs:
security:
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci command npm run build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: code.foss.global/hosttoday/ht-docker-dbase:npmci
image: code.foss.global/host.today/ht-docker-dbase:szci
steps:
- uses: actions/checkout@v3
@@ -82,25 +22,20 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @ship.zone/npmci
pnpm install -g @git.zone/tsdocker@latest
pnpm install
- name: Release
run: |
npmci docker login
npmci docker build
npmci docker test
# npmci docker push
npmci docker push
- name: Login to registries
run: tsdocker login
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
- name: List images
run: tsdocker list
steps:
- uses: actions/checkout@v3
- name: Build images
run: tsdocker build
- name: Trigger
run: npmci trigger
- name: Test images
run: tsdocker test
- name: Push to code.foss.global
run: tsdocker push code.foss.global
+10 -13
View File
@@ -1,23 +1,20 @@
{
"npmci": {
"npmGlobalTools": [],
"dockerRegistryRepoMap": {
"registry.gitlab.com": "losslessone/services/servezone/nullresolve"
"@git.zone/tsdocker": {
"registries": ["code.foss.global"],
"registryRepoMap": {
"code.foss.global": "serve.zone/nullresolve"
},
"dockerBuildargEnvMap": {
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
},
"npmRegistryUrl": "verdaccio.lossless.one"
"platforms": ["linux/amd64", "linux/arm64"]
},
"gitzone": {
"@git.zone/cli": {
"projectType": "service",
"module": {
"githost": "gitlab.com",
"gitscope": "losslessone/services/servezone",
"githost": "code.foss.global",
"gitscope": "serve.zone",
"gitrepo": "nullresolve",
"description": "The nullresolve project is a private service designed to handle requests that would otherwise remain unserved, providing appropriate feedback mechanisms within the servzone architecture.",
"npmPackagename": "@losslessone_private/nullresolve",
"license": "MIT",
"license": "UNLICENSED",
"keywords": [
"nullresolve",
"service",
@@ -37,4 +34,4 @@
]
}
}
}
}
+20 -33
View File
@@ -1,46 +1,33 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM code.foss.global/host.today/ht-docker-node:npmci as node1
COPY ./ /app
FROM code.foss.global/host.today/ht-docker-node:lts AS build
WORKDIR /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
COPY package.json pnpm-lock.yaml ./
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules && pnpm install
RUN pnpm install --frozen-lockfile
COPY . ./
RUN pnpm run build
# gitzone dockerfile_service
## STAGE 2 // install production
FROM code.foss.global/host.today/ht-docker-node:npmci as node2
WORKDIR /app
COPY --from=node1 /app /app
RUN rm -rf .pnpm-store
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules/ && pnpm install --prod
RUN pnpm prune --prod
## STAGE 2 // PRODUCTION
FROM code.foss.global/host.today/ht-docker-node:lts AS production
## STAGE 3 // rebuild dependencies for alpine
FROM code.foss.global/host.today/ht-docker-node:alpinenpmci as node3
WORKDIR /app
COPY --from=node2 /app /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN pnpm rebuild -r
## STAGE 4 // the final production image with all dependencies in place
FROM code.foss.global/host.today/ht-docker-node:alpine as node4
WORKDIR /app
COPY --from=node3 /app /app
ENV NODE_ENV=production
### Healthchecks
RUN pnpm install -g @servezone/healthy
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/cli.js ./cli.js
COPY --from=build /app/dist_ts ./dist_ts
LABEL org.opencontainers.image.title="nullresolve" \
org.opencontainers.image.description="serve.zone fallback response service" \
org.opencontainers.image.source="https://code.foss.global/serve.zone/nullresolve"
EXPOSE 80
CMD ["npm", "start"]
CMD ["node", "cli.js"]
+16 -15
View File
@@ -10,21 +10,22 @@
"start": "(node cli.js)",
"startTs": "(node cli.ts.js)",
"build": "(tsbuild --web --allowimplicitany)",
"watch": "(tswatch service)"
"watch": "(tswatch service)",
"build:docker": "tsdocker build --verbose",
"release:docker": "tsdocker push --verbose"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.63",
"@git.zone/tstest": "^1.0.71",
"@git.zone/tswatch": "^2.0.37",
"@push.rocks/tapbundle": "^5.0.3",
"@types/node": "^22.10.2"
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsdocker": "^2.2.4",
"@git.zone/tsrun": "^2.0.2",
"@git.zone/tstest": "^3.6.3",
"@git.zone/tswatch": "^3.3.2",
"@types/node": "^25.6.0"
},
"dependencies": {
"@api.global/typedserver": "^3.0.53",
"@git.zone/tsrun": "^1.2.37",
"@push.rocks/projectinfo": "^5.0.1",
"@push.rocks/smartlog": "^3.0.7",
"@push.rocks/smartpath": "^5.0.5",
"@api.global/typedserver": "^8.4.6",
"@push.rocks/projectinfo": "^5.1.0",
"@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartstatus": "^1.1.1"
},
"private": true,
@@ -37,7 +38,7 @@
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
".smartconfig.json",
"readme.md"
],
"browserslist": [
@@ -45,12 +46,12 @@
],
"repository": {
"type": "git",
"url": "git+https://gitlab.com/losslessone/services/servezone/nullresolve.git"
"url": "git+https://code.foss.global/serve.zone/nullresolve.git"
},
"bugs": {
"url": "https://gitlab.com/losslessone/services/servezone/nullresolve/issues"
"url": "https://code.foss.global/serve.zone/nullresolve/issues"
},
"homepage": "https://gitlab.com/losslessone/services/servezone/nullresolve#readme",
"homepage": "https://code.foss.global/serve.zone/nullresolve#readme",
"keywords": [
"nullresolve",
"service",
+4168 -4209
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,4 +1,4 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as nullresolve from '../ts/index.js';
@@ -11,4 +11,4 @@ tap.test('should create, start and stop an instance of nullresolve', async () =>
await nullresolveInstance.stop();
});
tap.start();
export default tap.start();
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
node --input-type=module <<'NODE'
import fs from 'node:fs';
const readJson = (path) => JSON.parse(fs.readFileSync(path, 'utf8'));
const checks = {
packageVersion: readJson('/app/package.json').version,
typedserverVersion: readJson('/app/node_modules/@api.global/typedserver/package.json').version,
hasCli: fs.existsSync('/app/cli.js'),
};
await import('/app/dist_ts/index.js');
if (checks.packageVersion !== '1.0.31') {
throw new Error(`Unexpected nullresolve package version ${checks.packageVersion}`);
}
if (checks.typedserverVersion !== '8.4.6') {
throw new Error(`Unexpected typedserver version ${checks.typedserverVersion}`);
}
if (!checks.hasCli) {
throw new Error('Missing cli.js');
}
console.log(JSON.stringify(checks));
NODE
-8
View File
@@ -1,8 +0,0 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@losslessone_private/nullresolve',
version: '1.0.31',
description: 'The nullresolve project is a private service designed to handle requests that would otherwise remain unserved, providing appropriate feedback mechanisms within the servzone architecture.'
}
+1 -2
View File
@@ -1,8 +1,7 @@
import './nullresolve.logging.js';
import { NullResolve } from './nullresolve.classes.nullresolve.js';
export { NullResolve };
let mainNullResolve: NullResolve;
let mainNullResolve: NullResolve | null;
export const runCli = async () => {
mainNullResolve = new NullResolve();
+143 -128
View File
@@ -1,147 +1,162 @@
import * as plugins from './nullresolve.plugins.js';
import { projectinfo } from './nullresolve.projectinfo.js';
import * as paths from './nullresolve.paths.js';
import { configObject } from './nullresolve.config.js';
import type { IHtmlInfoOptions } from '@api.global/typedserver/infohtml';
export interface INullResolveOptions {
port?: number;
serviceDomain?: string;
}
type TResolvedNullResolveOptions = INullResolveOptions & {
serviceDomain: string;
};
export class NullResolve {
public serviceServer: plugins.typedserver.utilityservers.UtilityServiceServer;
public serviceServer: plugins.typedserver.utilityservers.UtilityServiceServer | null = null;
private options: TResolvedNullResolveOptions;
constructor() {
this.serviceServer = new plugins.typedserver.utilityservers.UtilityServiceServer({
constructor(optionsArg: INullResolveOptions = {}) {
this.options = {
serviceDomain: 'nullresolve.lossless.one',
serviceName: 'nullresolve',
serviceVersion: projectinfo.npm.version,
addCustomRoutes: async (serverArg) => {
serverArg.addRoute(
'/status/:code',
new plugins.typedserver.servertools.Handler('GET', async (req, res) => {
let infoHtmlOptions: plugins.typedserverInfoHtml.IHtmlInfoOptions;
...optionsArg,
};
}
switch (req.params.code) {
case 'ipblock':
infoHtmlOptions = {
title: 'Lossless Network: Blocked IP',
heading: 'Blocked IP',
text: 'Your IP (::CLIENT_IP::) is not allowed to access this ressource.',
sentryDsn: configObject.sentryDsn,
sentryMessage: 'ipblock',
redirectTo: 'https://lossless.com',
};
break;
case 'firewall':
infoHtmlOptions = {
title: 'Lossless Network: Firewall',
heading: 'Firewall',
text: 'Your request has been blocked by our firewall since it showed possibly harmful behaviour.',
sentryDsn: configObject.sentryDsn,
sentryMessage: 'firewall',
redirectTo: 'https://lossless.com',
};
break;
case '500class':
infoHtmlOptions = {
title: 'Lossless Network: 5xx',
heading: '5xx',
text: '::CLOUDFLARE_ERROR_500S_BOX::',
sentryDsn: configObject.sentryDsn,
sentryMessage: '5xx error',
redirectTo: 'https://lossless.com',
};
break;
case '1000class':
infoHtmlOptions = {
title: 'Lossless Network: DNS Resolution failed',
heading: '1xxx',
text: '::CLOUDFLARE_ERROR_1000S_BOX::',
sentryDsn: configObject.sentryDsn,
sentryMessage: '1000 class error',
redirectTo: 'https://lossless.com',
};
break;
case 'alwaysonline':
infoHtmlOptions = {
title: 'Lossless Network: No Cache',
heading: 'No Cache',
text: '::ALWAYS_ONLINE_NO_COPY_BOX::',
sentryDsn: configObject.sentryDsn,
sentryMessage: 'alwaysonline triggered. Potentially offline!',
redirectTo: 'https://lossless.com',
};
break;
case 'waf':
infoHtmlOptions = {
title: 'Lossless Network: Firewall Challenge',
heading: 'Firewall Challenge',
text: '::CAPTCHA_BOX::',
redirectTo: 'https://lossless.com',
};
break;
case 'country':
infoHtmlOptions = {
title: 'Lossless Network: Country Challenge',
heading: 'Country Challenge',
text: '::CAPTCHA_BOX::',
redirectTo: 'https://lossless.com',
};
break;
case 'attack':
infoHtmlOptions = {
title: 'Lossless Network: Advanced User Challenge',
heading: 'Advanced User Challenge',
text: '::IM_UNDER_ATTACK_BOX::',
redirectTo: 'https://lossless.com',
};
break;
default:
const statusInstance = plugins.smartstatus.HttpStatus.getHttpStatusByString(
req.params.code,
);
infoHtmlOptions = {
title: `Lossless Network: ${statusInstance.code.toString()}`,
heading: statusInstance.code.toString(),
text: statusInstance.text,
};
break;
}
const infoHtmlInstance =
await plugins.typedserverInfoHtml.InfoHtml.fromOptions(infoHtmlOptions);
res.status(200);
res.send(infoHtmlInstance.htmlString);
}),
);
serverArg.addRoute(
'/custom',
new plugins.typedserver.servertools.Handler('GET', async (req, res) => {
console.log(req.query);
const options: any = {
title: 'Lossless Network',
heading: 'Error!',
text: 'Please wait...',
redirectTo: 'https://lossless.com',
...req.query,
};
const infoHtmlInstance = await plugins.typedserverInfoHtml.InfoHtml.fromOptions({
title: decodeURI(options.title),
heading: decodeURI(options.heading),
text: decodeURI(options.text),
redirectTo: decodeURI(options.redirectTo),
sentryDsn: configObject.sentryDsn,
sentryMessage: `nullresolve custom: ${decodeURI(options.title)}`,
});
res.status(200);
res.send(infoHtmlInstance.htmlString);
}),
);
private async createInfoHtmlResponse(
infoHtmlOptionsArg: IHtmlInfoOptions,
) {
const { InfoHtml } = await import('@api.global/typedserver/infohtml');
const infoHtmlInstance = await InfoHtml.fromOptions(infoHtmlOptionsArg);
return new Response(infoHtmlInstance.htmlString, {
status: 200,
headers: {
'content-type': 'text/html; charset=utf-8',
},
});
}
private getStatusInfoOptions(statusCodeArg: string): IHtmlInfoOptions {
switch (statusCodeArg) {
case 'ipblock':
return {
title: 'Lossless Network: Blocked IP',
heading: 'Blocked IP',
text: 'Your IP (::CLIENT_IP::) is not allowed to access this resource.',
sentryDsn: configObject.sentryDsn,
sentryMessage: 'ipblock',
redirectTo: 'https://lossless.com',
};
case 'firewall':
return {
title: 'Lossless Network: Firewall',
heading: 'Firewall',
text: 'Your request has been blocked by our firewall since it showed possibly harmful behaviour.',
sentryDsn: configObject.sentryDsn,
sentryMessage: 'firewall',
redirectTo: 'https://lossless.com',
};
case '500class':
return {
title: 'Lossless Network: 5xx',
heading: '5xx',
text: '::CLOUDFLARE_ERROR_500S_BOX::',
sentryDsn: configObject.sentryDsn,
sentryMessage: '5xx error',
redirectTo: 'https://lossless.com',
};
case '1000class':
return {
title: 'Lossless Network: DNS Resolution failed',
heading: '1xxx',
text: '::CLOUDFLARE_ERROR_1000S_BOX::',
sentryDsn: configObject.sentryDsn,
sentryMessage: '1000 class error',
redirectTo: 'https://lossless.com',
};
case 'alwaysonline':
return {
title: 'Lossless Network: No Cache',
heading: 'No Cache',
text: '::ALWAYS_ONLINE_NO_COPY_BOX::',
sentryDsn: configObject.sentryDsn,
sentryMessage: 'alwaysonline triggered. Potentially offline!',
redirectTo: 'https://lossless.com',
};
case 'waf':
return {
title: 'Lossless Network: Firewall Challenge',
heading: 'Firewall Challenge',
text: '::CAPTCHA_BOX::',
redirectTo: 'https://lossless.com',
};
case 'country':
return {
title: 'Lossless Network: Country Challenge',
heading: 'Country Challenge',
text: '::CAPTCHA_BOX::',
redirectTo: 'https://lossless.com',
};
case 'attack':
return {
title: 'Lossless Network: Advanced User Challenge',
heading: 'Advanced User Challenge',
text: '::IM_UNDER_ATTACK_BOX::',
redirectTo: 'https://lossless.com',
};
default: {
const statusInstance = plugins.smartstatus.HttpStatus.getHttpStatusByString(statusCodeArg);
return {
title: `Lossless Network: ${statusInstance.code.toString()}`,
heading: statusInstance.code.toString(),
text: statusInstance.text,
};
}
}
}
private async addCustomRoutes(typedServerArg: plugins.typedserver.TypedServer) {
typedServerArg.addRoute('/status/:code', 'GET', async (ctxArg) => {
return this.createInfoHtmlResponse(this.getStatusInfoOptions(ctxArg.params.code));
});
typedServerArg.addRoute('/custom', 'GET', async (ctxArg) => {
const options = {
title: ctxArg.query.title || 'Lossless Network',
heading: ctxArg.query.heading || 'Error!',
text: ctxArg.query.text || 'Please wait...',
redirectTo: ctxArg.query.redirectTo || 'https://lossless.com',
};
return this.createInfoHtmlResponse({
...options,
sentryDsn: configObject.sentryDsn,
sentryMessage: `nullresolve custom: ${options.title}`,
});
});
}
public async start() {
if (this.serviceServer) {
return;
}
const projectinfo = await plugins.projectinfo.ProjectInfo.create(paths.packageDir);
this.serviceServer = new plugins.typedserver.utilityservers.UtilityServiceServer({
serviceDomain: this.options.serviceDomain,
serviceName: 'nullresolve',
serviceVersion: projectinfo.npm.version,
port: this.options.port,
addCustomRoutes: async (typedServerArg) => this.addCustomRoutes(typedServerArg),
});
await this.serviceServer.start();
}
public async stop() {
if (!this.serviceServer) {
return;
}
await this.serviceServer.stop();
this.serviceServer = null;
}
}
-8
View File
@@ -1,8 +0,0 @@
import * as plugins from './nullresolve.plugins.js';
import * as paths from './nullresolve.paths.js';
const projectInfoNpm = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
import { commitinfo } from './00_commitinfo_data.js';
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
+2 -4
View File
@@ -5,14 +5,12 @@ export { path };
// @api.global scope
import * as typedserver from '@api.global/typedserver';
import * as typedserverInfoHtml from '@api.global/typedserver/infohtml';
export { typedserver, typedserverInfoHtml };
export { typedserver };
// @push.rocks scope
import * as projectinfo from '@push.rocks/projectinfo';
import * as smartlog from '@push.rocks/smartlog';
import * as smartpath from '@push.rocks/smartpath';
import * as smartstatus from '@push.rocks/smartstatus';
export { projectinfo, smartlog, smartpath, smartstatus };
export { projectinfo, smartpath, smartstatus };
-4
View File
@@ -1,4 +0,0 @@
import * as plugins from './nullresolve.plugins.js';
import * as paths from './nullresolve.paths.js';
export const projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);