@crossdelta/infrastructure 0.2.21 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/discover-services.d.ts +13 -10
- package/dist/helpers/image.d.ts +18 -2
- package/dist/helpers/service-builder.d.ts +2 -0
- package/dist/index.cjs +85 -67
- package/dist/index.js +86 -68
- package/dist/runtimes/doks/types.d.ts +5 -2
- package/package.json +1 -1
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { K8sServiceConfig } from '../runtimes/doks/types';
|
|
2
2
|
/**
|
|
3
|
-
* Auto-discovers all service configurations from
|
|
4
|
-
* Each .ts file (except index.ts) should export a
|
|
3
|
+
* Auto-discovers all K8s service configurations from infra/services directory.
|
|
4
|
+
* Each .ts file (except index.ts) should export a K8sServiceConfig as default.
|
|
5
|
+
*
|
|
6
|
+
* If a config does not specify `image`, it will be auto-generated using
|
|
7
|
+
* the registry from root package.json's `pf.registry` config.
|
|
5
8
|
*
|
|
6
9
|
* @param servicesDir - Path to services directory (relative to cwd or absolute)
|
|
7
|
-
* @throws Error if duplicate ports are detected
|
|
10
|
+
* @throws Error if duplicate ports are detected or pf.registry is missing
|
|
8
11
|
*
|
|
9
12
|
* @example
|
|
10
13
|
* ```typescript
|
|
11
|
-
* //
|
|
12
|
-
* const
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* // Config without image - will use pf.registry automatically
|
|
15
|
+
* const config: K8sServiceConfig = {
|
|
16
|
+
* name: 'my-service',
|
|
17
|
+
* containerPort: 4001,
|
|
18
|
+
* }
|
|
16
19
|
* ```
|
|
17
20
|
*/
|
|
18
|
-
export declare function
|
|
21
|
+
export declare function discoverServiceConfigs(servicesDir?: string): K8sServiceConfig[];
|
package/dist/helpers/image.d.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import type { Output } from '@pulumi/pulumi';
|
|
2
|
+
/**
|
|
3
|
+
* Gets the full GHCR image URL for a service.
|
|
4
|
+
*
|
|
5
|
+
* @param registry - The GHCR registry/org name (e.g., 'orderboss')
|
|
6
|
+
* @param serviceName - The service name (e.g., 'storefront', 'api-gateway')
|
|
7
|
+
* @returns Full image URL (e.g., 'ghcr.io/orderboss/storefront:abc123')
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const config: K8sServiceConfig = {
|
|
12
|
+
* name: 'storefront',
|
|
13
|
+
* image: ghcrImage('orderboss', 'storefront'),
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const ghcrImage: (registry: string, serviceName: string) => string;
|
|
2
18
|
/**
|
|
3
19
|
* Gets the image configuration for a given repository.
|
|
4
20
|
* @param repository - The name of the repository.
|
|
5
21
|
* @returns The image configuration.
|
|
6
|
-
* @deprecated Use
|
|
22
|
+
* @deprecated Use ghcrImage instead
|
|
7
23
|
*/
|
|
8
|
-
export declare const getImage: (repository: string, registryCredentials: Output<string>) => {
|
|
24
|
+
export declare const getImage: (registry: string, repository: string, registryCredentials: Output<string>) => {
|
|
9
25
|
registryType: any;
|
|
10
26
|
registry: string;
|
|
11
27
|
repository: string;
|
|
@@ -5,6 +5,8 @@ export interface BuildServicesOptions {
|
|
|
5
5
|
serviceConfigs: ServiceConfig[];
|
|
6
6
|
registryCredentials: Output<string>;
|
|
7
7
|
logtailToken: Output<string>;
|
|
8
|
+
/** GHCR registry/org name (e.g., 'orderboss') */
|
|
9
|
+
registry: string;
|
|
8
10
|
}
|
|
9
11
|
/**
|
|
10
12
|
* Builds the services array from service configs.
|
package/dist/index.cjs
CHANGED
|
@@ -289067,7 +289067,7 @@ var require_lib38 = __commonJS((exports2, module2) => {
|
|
|
289067
289067
|
var require_lib39 = __commonJS((exports2, module2) => {
|
|
289068
289068
|
var fs = require("fs");
|
|
289069
289069
|
var { promisify } = require("util");
|
|
289070
|
-
var { readFileSync } = fs;
|
|
289070
|
+
var { readFileSync: readFileSync2 } = fs;
|
|
289071
289071
|
var readFile = promisify(fs.readFile);
|
|
289072
289072
|
var extractPath = (path, cmdshimContents) => {
|
|
289073
289073
|
if (/[.]cmd$/.test(path)) {
|
|
@@ -289119,7 +289119,7 @@ var require_lib39 = __commonJS((exports2, module2) => {
|
|
|
289119
289119
|
});
|
|
289120
289120
|
};
|
|
289121
289121
|
var readCmdShimSync = (path) => {
|
|
289122
|
-
const contents =
|
|
289122
|
+
const contents = readFileSync2(path);
|
|
289123
289123
|
const destination = extractPath(path, contents.toString());
|
|
289124
289124
|
if (!destination) {
|
|
289125
289125
|
throw notaShim(path);
|
|
@@ -308309,7 +308309,7 @@ var require_getImage = __commonJS((exports2) => {
|
|
|
308309
308309
|
exports2.getImageOutput = exports2.getImage = undefined;
|
|
308310
308310
|
var pulumi = require_pulumi();
|
|
308311
308311
|
var utilities = require_utilities();
|
|
308312
|
-
function
|
|
308312
|
+
function getImage2(args, opts) {
|
|
308313
308313
|
args = args || {};
|
|
308314
308314
|
opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
|
|
308315
308315
|
return pulumi.runtime.invoke("digitalocean:index/getImage:getImage", {
|
|
@@ -308319,7 +308319,7 @@ var require_getImage = __commonJS((exports2) => {
|
|
|
308319
308319
|
source: args.source
|
|
308320
308320
|
}, opts);
|
|
308321
308321
|
}
|
|
308322
|
-
exports2.getImage =
|
|
308322
|
+
exports2.getImage = getImage2;
|
|
308323
308323
|
function getImageOutput(args, opts) {
|
|
308324
308324
|
args = args || {};
|
|
308325
308325
|
opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
|
|
@@ -337013,13 +337013,14 @@ var require_kubernetes = __commonJS((exports2) => {
|
|
|
337013
337013
|
// lib/index.ts
|
|
337014
337014
|
var exports_lib = {};
|
|
337015
337015
|
__export(exports_lib, {
|
|
337016
|
+
ghcrImage: () => ghcrImage,
|
|
337016
337017
|
getServiceUrl: () => getServiceUrl,
|
|
337017
|
-
getServicePort: () =>
|
|
337018
|
+
getServicePort: () => getServicePort,
|
|
337018
337019
|
getImage: () => getImage,
|
|
337019
337020
|
filterByPlatform: () => filterByPlatform,
|
|
337020
337021
|
ensureDot: () => ensureDot,
|
|
337021
337022
|
dockerHubImage: () => dockerHubImage,
|
|
337022
|
-
|
|
337023
|
+
discoverServiceConfigs: () => discoverServiceConfigs,
|
|
337023
337024
|
deployNginxIngress: () => deployNginxIngress,
|
|
337024
337025
|
deployNats: () => deployNats,
|
|
337025
337026
|
deployK8sServices: () => deployK8sServices,
|
|
@@ -337080,21 +337081,67 @@ var defaultHealthCheck = {
|
|
|
337080
337081
|
// lib/helpers/discover-services.ts
|
|
337081
337082
|
var import_node_fs = require("node:fs");
|
|
337082
337083
|
var import_node_path = require("node:path");
|
|
337083
|
-
|
|
337084
|
-
|
|
337085
|
-
|
|
337086
|
-
|
|
337087
|
-
if (
|
|
337088
|
-
return
|
|
337089
|
-
|
|
337084
|
+
|
|
337085
|
+
// lib/helpers/image.ts
|
|
337086
|
+
var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
|
|
337087
|
+
var scopeImageTags = (() => {
|
|
337088
|
+
if (!scopeImageTagsRaw.trim()) {
|
|
337089
|
+
return {};
|
|
337090
|
+
}
|
|
337091
|
+
try {
|
|
337092
|
+
const parsed = JSON.parse(scopeImageTagsRaw);
|
|
337093
|
+
const tags = {};
|
|
337094
|
+
for (const [key, value] of Object.entries(parsed ?? {})) {
|
|
337095
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
337096
|
+
tags[key] = value.trim();
|
|
337097
|
+
}
|
|
337098
|
+
}
|
|
337099
|
+
return tags;
|
|
337100
|
+
} catch (error) {
|
|
337101
|
+
console.warn("Unable to parse scope image tags from environment:", error);
|
|
337102
|
+
return {};
|
|
337103
|
+
}
|
|
337104
|
+
})();
|
|
337105
|
+
var resolveImageTag = (scopeName) => {
|
|
337106
|
+
const tag = scopeImageTags[scopeName];
|
|
337107
|
+
if (!tag) {
|
|
337108
|
+
return "latest";
|
|
337109
|
+
}
|
|
337110
|
+
return tag;
|
|
337111
|
+
};
|
|
337112
|
+
var ghcrImage = (registry, serviceName) => {
|
|
337113
|
+
const tag = resolveImageTag(serviceName);
|
|
337114
|
+
return `ghcr.io/${registry}/${serviceName}:${tag}`;
|
|
337115
|
+
};
|
|
337116
|
+
var getImage = (registry, repository, registryCredentials) => {
|
|
337117
|
+
const scopeName = repository.split("/").pop();
|
|
337118
|
+
if (!scopeName) {
|
|
337119
|
+
throw new Error(`Invalid repository name: ${repository}`);
|
|
337120
|
+
}
|
|
337121
|
+
return {
|
|
337122
|
+
registryType: "GHCR",
|
|
337123
|
+
registry,
|
|
337124
|
+
repository,
|
|
337125
|
+
registryCredentials,
|
|
337126
|
+
tag: resolveImageTag(scopeName)
|
|
337127
|
+
};
|
|
337128
|
+
};
|
|
337129
|
+
|
|
337130
|
+
// lib/helpers/discover-services.ts
|
|
337131
|
+
function getRegistryFromPackageJson(workspaceRoot) {
|
|
337132
|
+
const pkgPath = import_node_path.join(workspaceRoot, "package.json");
|
|
337133
|
+
const pkg = JSON.parse(import_node_fs.readFileSync(pkgPath, "utf-8"));
|
|
337134
|
+
if (!pkg.pf?.registry) {
|
|
337135
|
+
throw new Error(`Missing "pf.registry" in ${pkgPath}. Add it to your root package.json, e.g.: "pf": { "registry": "orgname/reponame" }`);
|
|
337136
|
+
}
|
|
337137
|
+
return pkg.pf.registry;
|
|
337090
337138
|
}
|
|
337091
337139
|
function validateNoDuplicatePorts(configs) {
|
|
337092
337140
|
const portMap = new Map;
|
|
337093
337141
|
for (const config of configs) {
|
|
337094
|
-
const
|
|
337095
|
-
const existing = portMap.get(port) || [];
|
|
337142
|
+
const existing = portMap.get(config.containerPort) || [];
|
|
337096
337143
|
existing.push(config.name);
|
|
337097
|
-
portMap.set(
|
|
337144
|
+
portMap.set(config.containerPort, existing);
|
|
337098
337145
|
}
|
|
337099
337146
|
const conflicts = [...portMap.entries()].filter(([, services]) => services.length > 1).map(([port, services]) => `Port ${port}: ${services.join(", ")}`);
|
|
337100
337147
|
if (conflicts.length > 0) {
|
|
@@ -337103,13 +337150,22 @@ ${conflicts.join(`
|
|
|
337103
337150
|
`)}`);
|
|
337104
337151
|
}
|
|
337105
337152
|
}
|
|
337106
|
-
function
|
|
337153
|
+
function discoverServiceConfigs(servicesDir = "infra/services") {
|
|
337107
337154
|
const resolvedDir = import_node_path.isAbsolute(servicesDir) ? servicesDir : import_node_path.resolve(process.cwd(), servicesDir);
|
|
337155
|
+
const workspaceRoot = import_node_path.resolve(resolvedDir, "..", "..");
|
|
337156
|
+
const registry = getRegistryFromPackageJson(workspaceRoot);
|
|
337108
337157
|
const files = import_node_fs.readdirSync(resolvedDir).filter((file) => file.endsWith(".ts") && file !== "index.ts");
|
|
337109
337158
|
const configs = files.map((file) => {
|
|
337110
337159
|
const filePath = import_node_path.join(resolvedDir, file);
|
|
337111
337160
|
const module2 = require(filePath);
|
|
337112
|
-
|
|
337161
|
+
const config = module2.default ?? module2;
|
|
337162
|
+
if (!config.image) {
|
|
337163
|
+
return {
|
|
337164
|
+
...config,
|
|
337165
|
+
image: ghcrImage(registry, config.name)
|
|
337166
|
+
};
|
|
337167
|
+
}
|
|
337168
|
+
return config;
|
|
337113
337169
|
});
|
|
337114
337170
|
validateNoDuplicatePorts(configs);
|
|
337115
337171
|
return configs;
|
|
@@ -337337,50 +337393,9 @@ function buildDropletServices(options) {
|
|
|
337337
337393
|
function filterByPlatform(configs, platform) {
|
|
337338
337394
|
return configs.filter((c) => (c.platform || "app-platform") === platform);
|
|
337339
337395
|
}
|
|
337340
|
-
// lib/helpers/image.ts
|
|
337341
|
-
var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
|
|
337342
|
-
var scopeImageTags = (() => {
|
|
337343
|
-
if (!scopeImageTagsRaw.trim()) {
|
|
337344
|
-
return {};
|
|
337345
|
-
}
|
|
337346
|
-
try {
|
|
337347
|
-
const parsed = JSON.parse(scopeImageTagsRaw);
|
|
337348
|
-
const tags = {};
|
|
337349
|
-
for (const [key, value] of Object.entries(parsed ?? {})) {
|
|
337350
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
337351
|
-
tags[key] = value.trim();
|
|
337352
|
-
}
|
|
337353
|
-
}
|
|
337354
|
-
return tags;
|
|
337355
|
-
} catch (error) {
|
|
337356
|
-
console.warn("Unable to parse scope image tags from environment:", error);
|
|
337357
|
-
return {};
|
|
337358
|
-
}
|
|
337359
|
-
})();
|
|
337360
|
-
var resolveImageTag = (scopeName) => {
|
|
337361
|
-
const tag = scopeImageTags[scopeName];
|
|
337362
|
-
if (!tag) {
|
|
337363
|
-
console.warn(`No image tag for "${scopeName}", using "latest" as fallback`);
|
|
337364
|
-
return "latest";
|
|
337365
|
-
}
|
|
337366
|
-
return tag;
|
|
337367
|
-
};
|
|
337368
|
-
var getImage = (repository, registryCredentials) => {
|
|
337369
|
-
const scopeName = repository.split("/").pop();
|
|
337370
|
-
if (!scopeName) {
|
|
337371
|
-
throw new Error(`Invalid repository name: ${repository}`);
|
|
337372
|
-
}
|
|
337373
|
-
return {
|
|
337374
|
-
registryType: "GHCR",
|
|
337375
|
-
registry: "orderboss",
|
|
337376
|
-
repository,
|
|
337377
|
-
registryCredentials,
|
|
337378
|
-
tag: resolveImageTag(scopeName)
|
|
337379
|
-
};
|
|
337380
|
-
};
|
|
337381
337396
|
// lib/helpers/service-builder.ts
|
|
337382
337397
|
function buildServices(options) {
|
|
337383
|
-
const { serviceConfigs, registryCredentials, logtailToken } = options;
|
|
337398
|
+
const { serviceConfigs, registryCredentials, logtailToken, registry } = options;
|
|
337384
337399
|
const logDestinations = createLogDestinations(logtailToken);
|
|
337385
337400
|
const sortedConfigs = [...serviceConfigs].sort((a, b) => {
|
|
337386
337401
|
if (a.ingressPrefix === "/")
|
|
@@ -337400,7 +337415,7 @@ function buildServices(options) {
|
|
|
337400
337415
|
runCommand: _runCommand,
|
|
337401
337416
|
...serviceConfig
|
|
337402
337417
|
} = config;
|
|
337403
|
-
const image = configImage ?? getImage(
|
|
337418
|
+
const image = configImage ?? getImage(registry, config.name, registryCredentials);
|
|
337404
337419
|
return {
|
|
337405
337420
|
healthCheck: defaultHealthCheck,
|
|
337406
337421
|
alerts: defaultAlerts,
|
|
@@ -337422,7 +337437,7 @@ function buildIngressRules(serviceConfigs) {
|
|
|
337422
337437
|
}
|
|
337423
337438
|
// lib/helpers/service-runtime.ts
|
|
337424
337439
|
var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
|
|
337425
|
-
function
|
|
337440
|
+
function getServicePort(serviceName, defaultPort = 8080) {
|
|
337426
337441
|
const envKey = `${toEnvKey(serviceName)}_PORT`;
|
|
337427
337442
|
const envValue = process.env[envKey];
|
|
337428
337443
|
if (envValue) {
|
|
@@ -337438,7 +337453,7 @@ function getServiceUrl(serviceName) {
|
|
|
337438
337453
|
return process.env[envKey];
|
|
337439
337454
|
}
|
|
337440
337455
|
// lib/helpers/service-urls.ts
|
|
337441
|
-
function
|
|
337456
|
+
function getServicePort2(config) {
|
|
337442
337457
|
if (config.httpPort)
|
|
337443
337458
|
return config.httpPort;
|
|
337444
337459
|
const internalPorts = config.internalPorts;
|
|
@@ -337448,7 +337463,7 @@ function getServicePort3(config) {
|
|
|
337448
337463
|
}
|
|
337449
337464
|
function buildInternalUrls(serviceConfigs) {
|
|
337450
337465
|
return Object.fromEntries(serviceConfigs.map((config) => {
|
|
337451
|
-
const url = config.internalUrl ?? `http://${config.name}:${
|
|
337466
|
+
const url = config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`;
|
|
337452
337467
|
return [config.name, url];
|
|
337453
337468
|
}));
|
|
337454
337469
|
}
|
|
@@ -337460,7 +337475,7 @@ function buildExternalUrls(serviceConfigs, baseUrl) {
|
|
|
337460
337475
|
}
|
|
337461
337476
|
function buildLocalUrls(serviceConfigs) {
|
|
337462
337477
|
return Object.fromEntries(serviceConfigs.map((config) => {
|
|
337463
|
-
const port =
|
|
337478
|
+
const port = getServicePort2(config);
|
|
337464
337479
|
return [config.name, `http://localhost:${port}`];
|
|
337465
337480
|
}));
|
|
337466
337481
|
}
|
|
@@ -337468,14 +337483,14 @@ function buildServiceUrlEnvs(serviceConfigs) {
|
|
|
337468
337483
|
return serviceConfigs.map((config) => ({
|
|
337469
337484
|
key: `${config.name.toUpperCase().replace(/-/g, "_")}_URL`,
|
|
337470
337485
|
scope: "RUN_TIME",
|
|
337471
|
-
value: config.internalUrl ?? `http://${config.name}:${
|
|
337486
|
+
value: config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`
|
|
337472
337487
|
}));
|
|
337473
337488
|
}
|
|
337474
337489
|
function buildServicePortEnvs(serviceConfigs) {
|
|
337475
337490
|
return serviceConfigs.map((config) => ({
|
|
337476
337491
|
key: `${config.name.toUpperCase().replace(/-/g, "_")}_PORT`,
|
|
337477
337492
|
scope: "RUN_TIME",
|
|
337478
|
-
value: String(
|
|
337493
|
+
value: String(getServicePort2(config))
|
|
337479
337494
|
}));
|
|
337480
337495
|
}
|
|
337481
337496
|
// lib/runtimes/doks/cert-manager.ts
|
|
@@ -337841,6 +337856,9 @@ function getImagePullPolicy(image2, explicit) {
|
|
|
337841
337856
|
return image2.endsWith(":latest") ? "Always" : "IfNotPresent";
|
|
337842
337857
|
}
|
|
337843
337858
|
function deployK8sService(provider, namespace, config2) {
|
|
337859
|
+
if (!config2.image) {
|
|
337860
|
+
throw new Error(`Missing "image" for service "${config2.name}". Either specify image explicitly or use discoverServiceConfigs() which auto-generates it from pf.registry.`);
|
|
337861
|
+
}
|
|
337844
337862
|
const labels = buildLabels(config2.name, config2.labels);
|
|
337845
337863
|
const replicas = config2.replicas ?? DEFAULTS.replicas;
|
|
337846
337864
|
const imagePullPolicy = getImagePullPolicy(config2.image, config2.imagePullPolicy);
|
package/dist/index.js
CHANGED
|
@@ -289030,7 +289030,7 @@ var require_lib38 = __commonJS((exports, module) => {
|
|
|
289030
289030
|
var require_lib39 = __commonJS((exports, module) => {
|
|
289031
289031
|
var fs = __require("fs");
|
|
289032
289032
|
var { promisify } = __require("util");
|
|
289033
|
-
var { readFileSync } = fs;
|
|
289033
|
+
var { readFileSync: readFileSync2 } = fs;
|
|
289034
289034
|
var readFile = promisify(fs.readFile);
|
|
289035
289035
|
var extractPath = (path, cmdshimContents) => {
|
|
289036
289036
|
if (/[.]cmd$/.test(path)) {
|
|
@@ -289082,7 +289082,7 @@ var require_lib39 = __commonJS((exports, module) => {
|
|
|
289082
289082
|
});
|
|
289083
289083
|
};
|
|
289084
289084
|
var readCmdShimSync = (path) => {
|
|
289085
|
-
const contents =
|
|
289085
|
+
const contents = readFileSync2(path);
|
|
289086
289086
|
const destination = extractPath(path, contents.toString());
|
|
289087
289087
|
if (!destination) {
|
|
289088
289088
|
throw notaShim(path);
|
|
@@ -308272,7 +308272,7 @@ var require_getImage = __commonJS((exports) => {
|
|
|
308272
308272
|
exports.getImageOutput = exports.getImage = undefined;
|
|
308273
308273
|
var pulumi = require_pulumi();
|
|
308274
308274
|
var utilities = require_utilities();
|
|
308275
|
-
function
|
|
308275
|
+
function getImage2(args, opts) {
|
|
308276
308276
|
args = args || {};
|
|
308277
308277
|
opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
|
|
308278
308278
|
return pulumi.runtime.invoke("digitalocean:index/getImage:getImage", {
|
|
@@ -308282,7 +308282,7 @@ var require_getImage = __commonJS((exports) => {
|
|
|
308282
308282
|
source: args.source
|
|
308283
308283
|
}, opts);
|
|
308284
308284
|
}
|
|
308285
|
-
exports.getImage =
|
|
308285
|
+
exports.getImage = getImage2;
|
|
308286
308286
|
function getImageOutput(args, opts) {
|
|
308287
308287
|
args = args || {};
|
|
308288
308288
|
opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts || {});
|
|
@@ -337003,23 +337003,69 @@ var defaultHealthCheck = {
|
|
|
337003
337003
|
httpPath: "/health"
|
|
337004
337004
|
};
|
|
337005
337005
|
// lib/helpers/discover-services.ts
|
|
337006
|
-
import { readdirSync } from "node:fs";
|
|
337006
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
337007
337007
|
import { isAbsolute, join, resolve } from "node:path";
|
|
337008
|
-
|
|
337009
|
-
|
|
337010
|
-
|
|
337011
|
-
|
|
337012
|
-
if (
|
|
337013
|
-
return
|
|
337014
|
-
|
|
337008
|
+
|
|
337009
|
+
// lib/helpers/image.ts
|
|
337010
|
+
var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
|
|
337011
|
+
var scopeImageTags = (() => {
|
|
337012
|
+
if (!scopeImageTagsRaw.trim()) {
|
|
337013
|
+
return {};
|
|
337014
|
+
}
|
|
337015
|
+
try {
|
|
337016
|
+
const parsed = JSON.parse(scopeImageTagsRaw);
|
|
337017
|
+
const tags = {};
|
|
337018
|
+
for (const [key, value] of Object.entries(parsed ?? {})) {
|
|
337019
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
337020
|
+
tags[key] = value.trim();
|
|
337021
|
+
}
|
|
337022
|
+
}
|
|
337023
|
+
return tags;
|
|
337024
|
+
} catch (error) {
|
|
337025
|
+
console.warn("Unable to parse scope image tags from environment:", error);
|
|
337026
|
+
return {};
|
|
337027
|
+
}
|
|
337028
|
+
})();
|
|
337029
|
+
var resolveImageTag = (scopeName) => {
|
|
337030
|
+
const tag = scopeImageTags[scopeName];
|
|
337031
|
+
if (!tag) {
|
|
337032
|
+
return "latest";
|
|
337033
|
+
}
|
|
337034
|
+
return tag;
|
|
337035
|
+
};
|
|
337036
|
+
var ghcrImage = (registry, serviceName) => {
|
|
337037
|
+
const tag = resolveImageTag(serviceName);
|
|
337038
|
+
return `ghcr.io/${registry}/${serviceName}:${tag}`;
|
|
337039
|
+
};
|
|
337040
|
+
var getImage = (registry, repository, registryCredentials) => {
|
|
337041
|
+
const scopeName = repository.split("/").pop();
|
|
337042
|
+
if (!scopeName) {
|
|
337043
|
+
throw new Error(`Invalid repository name: ${repository}`);
|
|
337044
|
+
}
|
|
337045
|
+
return {
|
|
337046
|
+
registryType: "GHCR",
|
|
337047
|
+
registry,
|
|
337048
|
+
repository,
|
|
337049
|
+
registryCredentials,
|
|
337050
|
+
tag: resolveImageTag(scopeName)
|
|
337051
|
+
};
|
|
337052
|
+
};
|
|
337053
|
+
|
|
337054
|
+
// lib/helpers/discover-services.ts
|
|
337055
|
+
function getRegistryFromPackageJson(workspaceRoot) {
|
|
337056
|
+
const pkgPath = join(workspaceRoot, "package.json");
|
|
337057
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
337058
|
+
if (!pkg.pf?.registry) {
|
|
337059
|
+
throw new Error(`Missing "pf.registry" in ${pkgPath}. Add it to your root package.json, e.g.: "pf": { "registry": "orgname/reponame" }`);
|
|
337060
|
+
}
|
|
337061
|
+
return pkg.pf.registry;
|
|
337015
337062
|
}
|
|
337016
337063
|
function validateNoDuplicatePorts(configs) {
|
|
337017
337064
|
const portMap = new Map;
|
|
337018
337065
|
for (const config of configs) {
|
|
337019
|
-
const
|
|
337020
|
-
const existing = portMap.get(port) || [];
|
|
337066
|
+
const existing = portMap.get(config.containerPort) || [];
|
|
337021
337067
|
existing.push(config.name);
|
|
337022
|
-
portMap.set(
|
|
337068
|
+
portMap.set(config.containerPort, existing);
|
|
337023
337069
|
}
|
|
337024
337070
|
const conflicts = [...portMap.entries()].filter(([, services]) => services.length > 1).map(([port, services]) => `Port ${port}: ${services.join(", ")}`);
|
|
337025
337071
|
if (conflicts.length > 0) {
|
|
@@ -337028,13 +337074,22 @@ ${conflicts.join(`
|
|
|
337028
337074
|
`)}`);
|
|
337029
337075
|
}
|
|
337030
337076
|
}
|
|
337031
|
-
function
|
|
337077
|
+
function discoverServiceConfigs(servicesDir = "infra/services") {
|
|
337032
337078
|
const resolvedDir = isAbsolute(servicesDir) ? servicesDir : resolve(process.cwd(), servicesDir);
|
|
337079
|
+
const workspaceRoot = resolve(resolvedDir, "..", "..");
|
|
337080
|
+
const registry = getRegistryFromPackageJson(workspaceRoot);
|
|
337033
337081
|
const files = readdirSync(resolvedDir).filter((file) => file.endsWith(".ts") && file !== "index.ts");
|
|
337034
337082
|
const configs = files.map((file) => {
|
|
337035
337083
|
const filePath = join(resolvedDir, file);
|
|
337036
337084
|
const module = __require(filePath);
|
|
337037
|
-
|
|
337085
|
+
const config = module.default ?? module;
|
|
337086
|
+
if (!config.image) {
|
|
337087
|
+
return {
|
|
337088
|
+
...config,
|
|
337089
|
+
image: ghcrImage(registry, config.name)
|
|
337090
|
+
};
|
|
337091
|
+
}
|
|
337092
|
+
return config;
|
|
337038
337093
|
});
|
|
337039
337094
|
validateNoDuplicatePorts(configs);
|
|
337040
337095
|
return configs;
|
|
@@ -337262,50 +337317,9 @@ function buildDropletServices(options) {
|
|
|
337262
337317
|
function filterByPlatform(configs, platform) {
|
|
337263
337318
|
return configs.filter((c) => (c.platform || "app-platform") === platform);
|
|
337264
337319
|
}
|
|
337265
|
-
// lib/helpers/image.ts
|
|
337266
|
-
var scopeImageTagsRaw = process.env.SCOPE_IMAGE_TAGS ?? "";
|
|
337267
|
-
var scopeImageTags = (() => {
|
|
337268
|
-
if (!scopeImageTagsRaw.trim()) {
|
|
337269
|
-
return {};
|
|
337270
|
-
}
|
|
337271
|
-
try {
|
|
337272
|
-
const parsed = JSON.parse(scopeImageTagsRaw);
|
|
337273
|
-
const tags = {};
|
|
337274
|
-
for (const [key, value] of Object.entries(parsed ?? {})) {
|
|
337275
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
337276
|
-
tags[key] = value.trim();
|
|
337277
|
-
}
|
|
337278
|
-
}
|
|
337279
|
-
return tags;
|
|
337280
|
-
} catch (error) {
|
|
337281
|
-
console.warn("Unable to parse scope image tags from environment:", error);
|
|
337282
|
-
return {};
|
|
337283
|
-
}
|
|
337284
|
-
})();
|
|
337285
|
-
var resolveImageTag = (scopeName) => {
|
|
337286
|
-
const tag = scopeImageTags[scopeName];
|
|
337287
|
-
if (!tag) {
|
|
337288
|
-
console.warn(`No image tag for "${scopeName}", using "latest" as fallback`);
|
|
337289
|
-
return "latest";
|
|
337290
|
-
}
|
|
337291
|
-
return tag;
|
|
337292
|
-
};
|
|
337293
|
-
var getImage = (repository, registryCredentials) => {
|
|
337294
|
-
const scopeName = repository.split("/").pop();
|
|
337295
|
-
if (!scopeName) {
|
|
337296
|
-
throw new Error(`Invalid repository name: ${repository}`);
|
|
337297
|
-
}
|
|
337298
|
-
return {
|
|
337299
|
-
registryType: "GHCR",
|
|
337300
|
-
registry: "orderboss",
|
|
337301
|
-
repository,
|
|
337302
|
-
registryCredentials,
|
|
337303
|
-
tag: resolveImageTag(scopeName)
|
|
337304
|
-
};
|
|
337305
|
-
};
|
|
337306
337320
|
// lib/helpers/service-builder.ts
|
|
337307
337321
|
function buildServices(options) {
|
|
337308
|
-
const { serviceConfigs, registryCredentials, logtailToken } = options;
|
|
337322
|
+
const { serviceConfigs, registryCredentials, logtailToken, registry } = options;
|
|
337309
337323
|
const logDestinations = createLogDestinations(logtailToken);
|
|
337310
337324
|
const sortedConfigs = [...serviceConfigs].sort((a, b) => {
|
|
337311
337325
|
if (a.ingressPrefix === "/")
|
|
@@ -337325,7 +337339,7 @@ function buildServices(options) {
|
|
|
337325
337339
|
runCommand: _runCommand,
|
|
337326
337340
|
...serviceConfig
|
|
337327
337341
|
} = config;
|
|
337328
|
-
const image = configImage ?? getImage(
|
|
337342
|
+
const image = configImage ?? getImage(registry, config.name, registryCredentials);
|
|
337329
337343
|
return {
|
|
337330
337344
|
healthCheck: defaultHealthCheck,
|
|
337331
337345
|
alerts: defaultAlerts,
|
|
@@ -337347,7 +337361,7 @@ function buildIngressRules(serviceConfigs) {
|
|
|
337347
337361
|
}
|
|
337348
337362
|
// lib/helpers/service-runtime.ts
|
|
337349
337363
|
var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
|
|
337350
|
-
function
|
|
337364
|
+
function getServicePort(serviceName, defaultPort = 8080) {
|
|
337351
337365
|
const envKey = `${toEnvKey(serviceName)}_PORT`;
|
|
337352
337366
|
const envValue = process.env[envKey];
|
|
337353
337367
|
if (envValue) {
|
|
@@ -337363,7 +337377,7 @@ function getServiceUrl(serviceName) {
|
|
|
337363
337377
|
return process.env[envKey];
|
|
337364
337378
|
}
|
|
337365
337379
|
// lib/helpers/service-urls.ts
|
|
337366
|
-
function
|
|
337380
|
+
function getServicePort2(config) {
|
|
337367
337381
|
if (config.httpPort)
|
|
337368
337382
|
return config.httpPort;
|
|
337369
337383
|
const internalPorts = config.internalPorts;
|
|
@@ -337373,7 +337387,7 @@ function getServicePort3(config) {
|
|
|
337373
337387
|
}
|
|
337374
337388
|
function buildInternalUrls(serviceConfigs) {
|
|
337375
337389
|
return Object.fromEntries(serviceConfigs.map((config) => {
|
|
337376
|
-
const url = config.internalUrl ?? `http://${config.name}:${
|
|
337390
|
+
const url = config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`;
|
|
337377
337391
|
return [config.name, url];
|
|
337378
337392
|
}));
|
|
337379
337393
|
}
|
|
@@ -337385,7 +337399,7 @@ function buildExternalUrls(serviceConfigs, baseUrl) {
|
|
|
337385
337399
|
}
|
|
337386
337400
|
function buildLocalUrls(serviceConfigs) {
|
|
337387
337401
|
return Object.fromEntries(serviceConfigs.map((config) => {
|
|
337388
|
-
const port =
|
|
337402
|
+
const port = getServicePort2(config);
|
|
337389
337403
|
return [config.name, `http://localhost:${port}`];
|
|
337390
337404
|
}));
|
|
337391
337405
|
}
|
|
@@ -337393,14 +337407,14 @@ function buildServiceUrlEnvs(serviceConfigs) {
|
|
|
337393
337407
|
return serviceConfigs.map((config) => ({
|
|
337394
337408
|
key: `${config.name.toUpperCase().replace(/-/g, "_")}_URL`,
|
|
337395
337409
|
scope: "RUN_TIME",
|
|
337396
|
-
value: config.internalUrl ?? `http://${config.name}:${
|
|
337410
|
+
value: config.internalUrl ?? `http://${config.name}:${getServicePort2(config)}`
|
|
337397
337411
|
}));
|
|
337398
337412
|
}
|
|
337399
337413
|
function buildServicePortEnvs(serviceConfigs) {
|
|
337400
337414
|
return serviceConfigs.map((config) => ({
|
|
337401
337415
|
key: `${config.name.toUpperCase().replace(/-/g, "_")}_PORT`,
|
|
337402
337416
|
scope: "RUN_TIME",
|
|
337403
|
-
value: String(
|
|
337417
|
+
value: String(getServicePort2(config))
|
|
337404
337418
|
}));
|
|
337405
337419
|
}
|
|
337406
337420
|
// lib/runtimes/doks/cert-manager.ts
|
|
@@ -337766,6 +337780,9 @@ function getImagePullPolicy(image2, explicit) {
|
|
|
337766
337780
|
return image2.endsWith(":latest") ? "Always" : "IfNotPresent";
|
|
337767
337781
|
}
|
|
337768
337782
|
function deployK8sService(provider, namespace, config2) {
|
|
337783
|
+
if (!config2.image) {
|
|
337784
|
+
throw new Error(`Missing "image" for service "${config2.name}". Either specify image explicitly or use discoverServiceConfigs() which auto-generates it from pf.registry.`);
|
|
337785
|
+
}
|
|
337769
337786
|
const labels = buildLabels(config2.name, config2.labels);
|
|
337770
337787
|
const replicas = config2.replicas ?? DEFAULTS.replicas;
|
|
337771
337788
|
const imagePullPolicy = getImagePullPolicy(config2.image, config2.imagePullPolicy);
|
|
@@ -338055,13 +338072,14 @@ function buildInternalUrl(serviceName, namespace, port) {
|
|
|
338055
338072
|
return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`;
|
|
338056
338073
|
}
|
|
338057
338074
|
export {
|
|
338075
|
+
ghcrImage,
|
|
338058
338076
|
getServiceUrl,
|
|
338059
|
-
|
|
338077
|
+
getServicePort,
|
|
338060
338078
|
getImage,
|
|
338061
338079
|
filterByPlatform,
|
|
338062
338080
|
ensureDot,
|
|
338063
338081
|
dockerHubImage,
|
|
338064
|
-
|
|
338082
|
+
discoverServiceConfigs,
|
|
338065
338083
|
deployNginxIngress,
|
|
338066
338084
|
deployNats,
|
|
338067
338085
|
deployK8sServices,
|
|
@@ -197,8 +197,11 @@ export interface K8sVolumeMount {
|
|
|
197
197
|
export interface K8sServiceConfig {
|
|
198
198
|
/** Unique name of the service (used for deployment, service, and labels) */
|
|
199
199
|
name: string;
|
|
200
|
-
/**
|
|
201
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Container image (e.g., 'ghcr.io/orderboss/platform/storefront:latest').
|
|
202
|
+
* If not specified, auto-generated from pf.registry config + service name.
|
|
203
|
+
*/
|
|
204
|
+
image?: string;
|
|
202
205
|
/** Port the container listens on */
|
|
203
206
|
containerPort: number;
|
|
204
207
|
/** Number of replicas (defaults to 1) */
|