@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.
@@ -1,18 +1,21 @@
1
- import type { ServiceConfig } from '../types';
1
+ import type { K8sServiceConfig } from '../runtimes/doks/types';
2
2
  /**
3
- * Auto-discovers all service configurations from a directory.
4
- * Each .ts file (except index.ts) should export a ServiceConfig as default.
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
- * // Relative path (resolved from process.cwd())
12
- * const configs = discoverServices('infra/services')
13
- *
14
- * // Absolute path
15
- * const configs = discoverServices('/absolute/path/to/services')
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 discoverServices(servicesDir: string): ServiceConfig[];
21
+ export declare function discoverServiceConfigs(servicesDir?: string): K8sServiceConfig[];
@@ -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 project-specific config helper instead
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 = readFileSync(path);
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 getImage(args, opts) {
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 = 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: () => getServicePort2,
337018
+ getServicePort: () => getServicePort,
337018
337019
  getImage: () => getImage,
337019
337020
  filterByPlatform: () => filterByPlatform,
337020
337021
  ensureDot: () => ensureDot,
337021
337022
  dockerHubImage: () => dockerHubImage,
337022
- discoverServices: () => discoverServices,
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
- function getServicePort(config) {
337084
- if (config.httpPort)
337085
- return config.httpPort;
337086
- const internalPorts = config.internalPorts;
337087
- if (internalPorts?.[0])
337088
- return internalPorts[0];
337089
- return 8080;
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 port = getServicePort(config);
337095
- const existing = portMap.get(port) || [];
337142
+ const existing = portMap.get(config.containerPort) || [];
337096
337143
  existing.push(config.name);
337097
- portMap.set(port, existing);
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 discoverServices(servicesDir) {
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
- return module2.default ?? module2;
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(`platform/${config.name}`, registryCredentials);
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 getServicePort2(serviceName, defaultPort = 8080) {
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 getServicePort3(config) {
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}:${getServicePort3(config)}`;
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 = getServicePort3(config);
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}:${getServicePort3(config)}`
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(getServicePort3(config))
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 = readFileSync(path);
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 getImage(args, opts) {
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 = 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
- function getServicePort(config) {
337009
- if (config.httpPort)
337010
- return config.httpPort;
337011
- const internalPorts = config.internalPorts;
337012
- if (internalPorts?.[0])
337013
- return internalPorts[0];
337014
- return 8080;
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 port = getServicePort(config);
337020
- const existing = portMap.get(port) || [];
337066
+ const existing = portMap.get(config.containerPort) || [];
337021
337067
  existing.push(config.name);
337022
- portMap.set(port, existing);
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 discoverServices(servicesDir) {
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
- return module.default ?? module;
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(`platform/${config.name}`, registryCredentials);
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 getServicePort2(serviceName, defaultPort = 8080) {
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 getServicePort3(config) {
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}:${getServicePort3(config)}`;
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 = getServicePort3(config);
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}:${getServicePort3(config)}`
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(getServicePort3(config))
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
- getServicePort2 as getServicePort,
338077
+ getServicePort,
338060
338078
  getImage,
338061
338079
  filterByPlatform,
338062
338080
  ensureDot,
338063
338081
  dockerHubImage,
338064
- discoverServices,
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
- /** Container image (e.g., 'ghcr.io/orderboss/platform/storefront:latest') */
201
- image: string;
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) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/infrastructure",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {