Fixes to NodeJS version.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev": "tsx watch src/server.ts",
|
||||
"start": "node ../../dist/backend/server.js",
|
||||
"start": "node ../dist/backend/server.js",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -32,15 +32,28 @@ export class AzureImageService {
|
||||
}
|
||||
});
|
||||
|
||||
const payload = (await response.json()) as { value?: Array<{ name?: string; displayName?: string; metadata?: { regionType?: string } }> };
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch Azure locations: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const locations = (payload.value ?? [])
|
||||
.filter((loc) => loc.metadata?.regionType === "Physical" && Boolean(loc.name))
|
||||
const payload = (await response.json()) as {
|
||||
value?: Array<{ name?: string; displayName?: string; metadata?: { regionType?: string } }>;
|
||||
};
|
||||
|
||||
const allLocations = (payload.value ?? [])
|
||||
.filter((loc) => Boolean(loc.name))
|
||||
.map((loc) => ({
|
||||
name: loc.name as string,
|
||||
displayName: loc.displayName ?? (loc.name as string)
|
||||
displayName: loc.displayName ?? (loc.name as string),
|
||||
regionType: loc.metadata?.regionType
|
||||
}));
|
||||
|
||||
const physical = allLocations.filter((loc) => loc.regionType?.toLowerCase() === "physical");
|
||||
const locations = (physical.length > 0 ? physical : allLocations).map((loc) => ({
|
||||
name: loc.name,
|
||||
displayName: loc.displayName
|
||||
}));
|
||||
|
||||
locations.sort((a, b) => a.name.localeCompare(b.name));
|
||||
this.cache.set(cacheKey, locations, CACHE_TTL_MS);
|
||||
return locations;
|
||||
@@ -54,7 +67,7 @@ export class AzureImageService {
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.listPublishers(location);
|
||||
const publishers = this.extractNames(response);
|
||||
const publishers = this.extractNames(response).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
this.cache.set(cacheKey, publishers, CACHE_TTL_MS);
|
||||
return publishers;
|
||||
@@ -68,7 +81,7 @@ export class AzureImageService {
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.listOffers(location, publisher);
|
||||
const offers = this.extractNames(response);
|
||||
const offers = this.extractNames(response).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
this.cache.set(cacheKey, offers, CACHE_TTL_MS);
|
||||
return offers;
|
||||
@@ -82,7 +95,7 @@ export class AzureImageService {
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.listSkus(location, publisher, offer);
|
||||
const skus = this.extractNames(response);
|
||||
const skus = this.extractNames(response).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
this.cache.set(cacheKey, skus, CACHE_TTL_MS);
|
||||
return skus;
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import cors from "cors";
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import { z } from "zod";
|
||||
import { AzureImageService } from "./azure-service";
|
||||
import { TemplateService } from "./template-service";
|
||||
|
||||
const findAppNewRoot = (): string => {
|
||||
const candidates = [join(__dirname, "../../.."), join(__dirname, "../..")];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(join(candidate, "templates.json"))) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unable to resolve app-new root");
|
||||
};
|
||||
|
||||
const queryLocation = z.object({ location: z.string().min(1) });
|
||||
const queryOffer = z.object({ location: z.string().min(1), publisher: z.string().min(1) });
|
||||
const querySku = z.object({ location: z.string().min(1), publisher: z.string().min(1), offer: z.string().min(1) });
|
||||
@@ -127,7 +138,7 @@ const makeApp = () => {
|
||||
res.status(500).json({ message });
|
||||
});
|
||||
|
||||
const frontendRoot = join(process.cwd(), "dist/frontend");
|
||||
const frontendRoot = join(findAppNewRoot(), "dist/frontend");
|
||||
if (existsSync(frontendRoot)) {
|
||||
app.use(express.static(frontendRoot));
|
||||
app.get(/^(?!\/api).*/, (_req, res) => {
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import nunjucks from "nunjucks";
|
||||
import type { ImageSelection, UsageTemplate } from "./types";
|
||||
|
||||
const findAppRoot = (): string => {
|
||||
// Supports running from src/backend (dev) and dist/backend (compiled).
|
||||
const devPath = join(__dirname, "../../../app");
|
||||
const buildPath = join(__dirname, "../../app");
|
||||
try {
|
||||
readFileSync(join(devPath, "templates.json"), "utf8");
|
||||
return devPath;
|
||||
} catch {
|
||||
return buildPath;
|
||||
const findAppNewRoot = (): string => {
|
||||
const candidates = [join(__dirname, "../../.."), join(__dirname, "../..")];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(join(candidate, "templates.json"))) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unable to resolve app-new template root");
|
||||
};
|
||||
|
||||
export class TemplateService {
|
||||
private readonly appRoot = findAppRoot();
|
||||
private readonly appNewRoot = findAppNewRoot();
|
||||
|
||||
private readonly env = nunjucks.configure(join(this.appRoot, "templates"), {
|
||||
private readonly env = nunjucks.configure(join(this.appNewRoot, "templates"), {
|
||||
autoescape: false,
|
||||
noCache: true
|
||||
});
|
||||
|
||||
private readonly templates: UsageTemplate[] = JSON.parse(
|
||||
readFileSync(join(this.appRoot, "templates.json"), "utf8")
|
||||
readFileSync(join(this.appNewRoot, "templates.json"), "utf8")
|
||||
) as UsageTemplate[];
|
||||
|
||||
public getTemplates(): UsageTemplate[] {
|
||||
|
||||
@@ -4,7 +4,7 @@ const semverSortKey = (value: string): number[] => value.split(".").map((part) =
|
||||
|
||||
export const sortImageVersionsIfSemantic = (versions: string[]): string[] => {
|
||||
if (!versions.every((value) => SEMVER_PATTERN.test(value))) {
|
||||
return versions;
|
||||
return [...versions].sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }));
|
||||
}
|
||||
|
||||
return [...versions].sort((a, b) => {
|
||||
|
||||
@@ -7,8 +7,8 @@ describe("sortImageVersionsIfSemantic", () => {
|
||||
expect(sorted).toEqual(["1.2.0", "1.10.0", "2.0.0"]);
|
||||
});
|
||||
|
||||
it("returns source order for non-semantic versions", () => {
|
||||
it("sorts non-semantic versions naturally", () => {
|
||||
const original = ["latest", "1.0.0", "beta"];
|
||||
expect(sortImageVersionsIfSemantic(original)).toEqual(original);
|
||||
expect(sortImageVersionsIfSemantic(original)).toEqual(["1.0.0", "beta", "latest"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "../../dist/backend",
|
||||
"outDir": "../dist/backend",
|
||||
"types": ["node", "vitest/globals"]
|
||||
},
|
||||
"include": ["src"]
|
||||
|
||||
Reference in New Issue
Block a user