Compare commits

...

3 Commits

Author SHA1 Message Date
Eli Bosley
085a74b793 feat: more changes 2024-12-02 15:35:05 -05:00
Eli Bosley
cd35af28a4 fix: node 20 2024-12-02 15:20:43 -05:00
Eli Bosley
167ef4c659 feat: initial setup 2024-12-02 14:50:12 -05:00
12 changed files with 1762 additions and 11886 deletions

View File

@@ -5,7 +5,7 @@ require:
- ts-node/register
config:
namingConvention:
typeNames: './fix-array-type.cjs'
typeNames: './configs/fix-array-type.cjs'
enumValues: 'change-case#upperCase'
useTypeImports: true
scalars:

View File

@@ -0,0 +1,5 @@
export const __import_meta_url =
typeof document === 'undefined'
? new (require('url'.replace('', '')).URL)('file:' + __filename).href
: (document.currentScript && document.currentScript.src) ||
new URL('main.js', document.baseURI).href;

177
api/configs/esbuild.js Normal file
View File

@@ -0,0 +1,177 @@
import { cp, readFile, stat, writeFile } from 'fs/promises';
import { createRequire as topLevelCreateRequire } from 'module';
import { join } from 'path';
import esbuild from 'esbuild';
import { emptyDir, exists } from 'fs-extra';
const outputDir = 'deploy/esbuild/';
function cleanPkgJson(json) {
delete json.devDependencies;
delete json['release-it'];
delete json.optionalDependencies;
delete json.dependencies;
return json;
}
/**
* Remove useless fields from package.json, this is needed mostly for `pkg`
* otherwise it will try to bundle dependencies
*/
async function patchPkgJson(path) {
const pkgJsonPath = join(outputDir, path, 'package.json');
const pkgJson = require('./' + pkgJsonPath);
cleanPkgJson(pkgJson);
delete pkgJson.scripts;
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
}
// from https://github.com/evanw/esbuild/issues/1051#issuecomment-806325487
const nativeNodeModulesPlugin = {
name: 'native-node-modules',
setup(build) {
const require = topLevelCreateRequire(import.meta.url);
// If a ".node" file is imported within a module in the "file" namespace, resolve
// it to an absolute path and put it into the "node-file" virtual namespace.
build.onResolve({ filter: /\.node$/, namespace: 'file' }, (args) => {
console.log('args', args);
return {
path: require.resolve(args.path, { paths: [args.resolveDir] }),
namespace: 'node-file',
}});
// Files in the "node-file" virtual namespace call "require()" on the
// path from esbuild of the ".node" file in the output directory.
build.onLoad({ filter: /.*/, namespace: 'node-file' }, (args) => ({
contents: `
import path from ${JSON.stringify(args.path)}
try { module.exports = require(path) }
catch {}
`,
}));
// If a ".node" file is imported within a module in the "node-file" namespace, put
// it in the "file" namespace where esbuild's default loading behavior will handle
// it. It is already an absolute path since we resolved it to one above.
build.onResolve({ filter: /\.node$/, namespace: 'node-file' }, (args) => ({
path: args.path,
namespace: 'file',
}));
// Tell esbuild's default loading behavior to use the "file" loader for
// these ".node" files.
let opts = build.initialOptions;
opts.loader = opts.loader || {};
opts.loader['.node'] = 'file';
},
};
async function printSize(fileName) {
const stats = await stat(fileName);
// print size in MB
console.log(`Bundle size: ${Math.round(stats.size / 10000) / 100}MB\n\n`);
}
async function main() {
const start = Date.now();
// clean build folder
await emptyDir(outputDir);
const outfile = `${outputDir}/index.js`;
const externals = [
'@fastify/static',
'@fastify/view',
'@apollo/gateway',
'@nestjs/websockets/socket-module',
'@nestjs/microservices/microservices-module',
'@nestjs/microservices',
'@nestjs/microservices',
'@nestjs/platform-express',
'@apollo/subgraph',
'@apollo/subgraph/package.json',
'@apollo/subgraph/dist/directives',
'class-transformer/storage',
'redlock',
'@vmngr/libvirt/build/Release',
'ts-invariant/',
];
/** @type { import('esbuild').BuildOptions } */
const config = {
entryPoints: ['dist/cli.js', 'dist/main.js'],
logLevel: 'error',
plugins: [nativeNodeModulesPlugin],
bundle: true,
platform: 'node',
target: 'node20',
sourcemap: true,
outdir: outputDir,
// suppress direct-eval warning
logOverride: {
'direct-eval': 'silent',
},
format: 'esm',
treeShaking: false,
external: externals,
// Prevent esbuild from adding a "2" to the names of CC classes for some reason.
keepNames: true,
};
await esbuild.build(config);
console.log(`Build took ${Date.now() - start}ms`);
/* const content = (await readFile(outfile, 'utf-8'))
.replace(/__dirname, "\.\.\/"/g, '__dirname, "./node_modules/@serialport/bindings-cpp"')
.replace(`"../../package.json"`, `"./node_modules/@zwave-js/server/package.json"`); */
// await writeFile(outfile, content);
// copy assets to build folder
for (const ext of externals) {
const path = ext.startsWith('./') ? ext : `node_modules/${ext}`;
if (await exists(path)) {
console.log(`Copying "${path}" to "${outputDir}" folder`);
await cp(path, `${outputDir}/${path}`, { recursive: true });
} else {
console.log(`Asset "${path}" does not exist. Skipping...`);
}
}
// Copy the folder directly into the build directory
const folderToCopy = 'node_modules/@vmngr/libvirt/build/Release/libvirt.node';
const destination = `${outputDir}/build/libvirt.node`;
await cp(folderToCopy, destination, { recursive: true });
// create main patched packege.json
const json = await import('../package.json', { assert: { type: 'json' } });
const pkgJson = { ...json };
cleanPkgJson(pkgJson);
pkgJson.scripts = {
start: 'node index.js',
};
pkgJson.bin = 'index.js';
pkgJson.pkg = {
assets: ['dist/**', 'snippets/**', 'node_modules/**'],
};
await writeFile(`${outputDir}/package.json`, JSON.stringify(pkgJson, null, 2));
/*
await patchPkgJson('node_modules/@zwave-js/config');
await patchPkgJson('node_modules/zwave-js');
await patchPkgJson('node_modules/@zwave-js/server'); */
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,19 @@
/**
* This function wraps constant case, that turns any string into CONSTANT_CASE
* However, this function has a bug that, if you pass _ to it it will return an empty
* string. This small module fixes that
*
* @param {string*} str
* @return {string}
*/
function FixArrayType(str) {
if (str === 'Array') {
return 'ArrayType';
}
// If result is an empty string, just return the original string
return str;
}
module.exports = FixArrayType;

View File

@@ -0,0 +1,12 @@
{
"assets": [
"./deploy/esbuild/*",
"src/**/*.graphql"
],
"targets": [
"node20-linux-arm64",
"node20-linux-x64"
],
"bin": "deploy/esbuild/index.js",
"outputPath": "./deploy/pre-pack"
}

View File

@@ -18,10 +18,10 @@ x-volumes: &volumes
- ./.env.test:/app/.env.test
- ./.env.development:/app/.env.development
- ./codegen.yml:/app/codegen.yml
- ./fix-array-type.cjs:/app/fix-array-type.cjs
- /var/run/docker.sock:/var/run/docker.sock
- ./unraid-api.js:/app/unraid-api.js
- ./ecosystem.config.json:/app/ecosystem.config.json
- ./configs/:/app/configs/
services:

13365
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "@unraid/api",
"version": "3.11.0",
"main": "src/cli/index.ts",
"main": "index.js",
"type": "module",
"repository": "git@github.com:unraid/api.git",
"author": "Lime Technology, Inc. <unraid.net>",
@@ -12,9 +12,11 @@
"build": "vite build --mode=production",
"postbuild": "chmod +x dist/main.js && chmod +x dist/cli.js",
"build-and-pack": "./scripts/build.mjs",
"codegen": "MOTHERSHIP_GRAPHQL_LINK='https://staging.mothership.unraid.net/ws' graphql-codegen --config codegen.yml -r dotenv/config './.env.staging'",
"codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen --config codegen.yml --watch -r dotenv/config",
"codegen:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOTHERSHIP_GRAPHQL_LINK='https://mothership.localhost/ws' graphql-codegen-esm --config codegen.yml --watch",
"bundle": "node ./configs/esbuild.js",
"build:binary": "pkg --sea --config configs/pkg.config.json deploy/esbuild/index.js",
"codegen": "MOTHERSHIP_GRAPHQL_LINK='https://staging.mothership.unraid.net/ws' graphql-codegen --config configs/codegen.yml -r dotenv/config './.env.staging'",
"codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen --config configs/codegen.yml --watch -r dotenv/config",
"codegen:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOTHERSHIP_GRAPHQL_LINK='https://mothership.localhost/ws' graphql-codegen-esm --config configs/codegen.yml --watch",
"tsc": "tsc --noEmit",
"lint": "eslint --flag unstable_ts_config --config .eslintrc.ts src/",
"lint:fix": "eslint --flag unstable_ts_config --fix --config .eslintrc.ts src/",
@@ -109,7 +111,7 @@
"pino": "^9.5.0",
"pino-http": "^10.3.0",
"pino-pretty": "^11.3.0",
"pm2": "^5.4.2",
"pm2": "^5.4.3",
"reflect-metadata": "^0.1.14",
"request": "^2.88.2",
"semver": "^7.6.3",
@@ -162,6 +164,7 @@
"@types/wtfnode": "^0.7.3",
"@vitest/coverage-v8": "^2.1.4",
"@vitest/ui": "^2.1.4",
"@yao-pkg/pkg": "^6.1.1",
"cz-conventional-changelog": "3.3.0",
"eslint": "^9.14.0",
"graphql-codegen-typescript-validation-schema": "^0.16.0",
@@ -171,8 +174,10 @@
"typescript": "^5.6.3",
"typescript-eslint": "^8.13.0",
"vite": "^5.4.10",
"vite-plugin-native": "^2.2.2",
"vite-plugin-node": "^4.0.0",
"vite-plugin-static-copy": "^2.0.0",
"vite-plugin-top-level-await": "^1.4.4",
"vite-tsconfig-paths": "^5.1.0",
"vitest": "^2.1.4",
"zx": "^8.2.0"

View File

@@ -1,9 +1,15 @@
#!/usr/bin/env zx
import { exit } from 'process';
import { cd, $ } from 'zx';
import { $, cd } from 'zx';
import { getDeploymentVersion } from './get-deployment-version.mjs';
try {
// Enable colours in output
process.env.FORCE_COLOR = '1';
@@ -15,6 +21,7 @@ try {
// Create deployment directories - ignore if they already exist
await $`mkdir -p ./deploy/release`;
await $`mkdir -p ./deploy/pre-pack`;
await $`mkdir -p ./deploy/pre-pack/.configs`;
await $`rm -rf ./deploy/release/*`;
await $`rm -rf ./deploy/pre-pack/*`;
@@ -28,23 +35,23 @@ try {
await $`cp -r ./dist/ ./deploy/pre-pack/dist/`;
// Copy environment to deployment directory
const files = [
'.env.production',
'.env.staging',
'tsconfig.json',
'codegen.yml',
'ecosystem.config.json'
]
const files = ['.env.production', '.env.staging', 'tsconfig.json'];
for (const file of files) {
console.info(`Copying ${file} to deployment directory`);
await $`cp ./${file} ./deploy/pre-pack/${file}`;
}
await $`cp ./configs/codegen.yml ./deploy/pre-pack/codegen.yml`;
await $`cp ./configs/ecosystem.config.json ./deploy/pre-pack/ecosystem.config.json`;
// Get package details
const { name, version, ...rest } = await import('../package.json', {
assert: { type: 'json' },
}).then((pkg) => pkg.default);
console.info(`Building package ${name} v${version}`);
const deploymentVersion = getDeploymentVersion(process.env, version);
// Create deployment package.json
@@ -58,7 +65,14 @@ try {
await $`cp ./README.md ./deploy/pre-pack/`;
await $`cp -r ./node_modules ./deploy/pre-pack/node_modules`;
// Install production dependencies
// Bundle and Pack for Legacy OS Versions
console.info('Bundling app with esbuild');
await $`npm run bundle`;
console.info('Packing app with pkg');
await $`npm run build:binary`;
// Install production dependencies
cd('./deploy/pre-pack');
await $`npm prune --omit=dev`;
@@ -81,4 +95,4 @@ try {
}
exit(error.exitCode);
}
}

View File

@@ -90,7 +90,6 @@ export const logoutUser = createAsyncThunk<
{ state: RootState }
>('config/logout-user', async ({ reason }) => {
logger.info('Logging out user: %s', reason ?? 'No reason provided');
const { pubsub } = await import('@app/core/pubsub');
// Publish to servers endpoint
await pubsub.publish(PUBSUB_CHANNEL.SERVERS, {

View File

@@ -1,14 +1,18 @@
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
import nodeExternals from 'rollup-plugin-node-externals';
import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import nodeResolve from '@rollup/plugin-node-resolve';
import nodeExternals from 'rollup-plugin-node-externals';
import native from 'vite-plugin-native';
import { VitePluginNode } from 'vite-plugin-node';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import topLevelAwait from "vite-plugin-top-level-await";
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
export default defineConfig(({ mode }) => {
return {
plugins: [
native({}),
topLevelAwait(),
tsconfigPaths(),
nodeExternals(),
nodeResolve(),
@@ -51,7 +55,7 @@ export default defineConfig(({ mode }) => {
},
output: {
entryFileNames: '[name].js',
format: 'es', // Change the format to 'es' to support top-level await
format: 'es',
},
},
modulePreload: false,
@@ -73,4 +77,4 @@ export default defineConfig(({ mode }) => {
},
},
};
});
});