@trackunit/iris-app-build-utilities 1.12.14 → 1.12.16

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.12.16 (2026-01-30)
2
+
3
+ ### 🧱 Updated Dependencies
4
+
5
+ - Updated iris-app-api to 1.14.15
6
+ - Updated shared-utils to 1.13.16
7
+
8
+ ## 1.12.15 (2026-01-30)
9
+
10
+ ### 🧱 Updated Dependencies
11
+
12
+ - Updated iris-app-api to 1.14.14
13
+ - Updated shared-utils to 1.13.15
14
+
1
15
  ## 1.12.14 (2026-01-29)
2
16
 
3
17
  ### 🧱 Updated Dependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/iris-app-build-utilities",
3
- "version": "1.12.14",
3
+ "version": "1.12.16",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "repository": "https://github.com/Trackunit/manager",
6
6
  "engines": {
@@ -17,9 +17,10 @@
17
17
  "tslib": "^2.6.2",
18
18
  "csp-header": "^5.2.1",
19
19
  "@rspack/core": "1.6.7",
20
- "@trackunit/iris-app-api": "1.14.13",
20
+ "@trackunit/iris-app-api": "1.14.15",
21
21
  "@nx/devkit": "22.0.4",
22
- "@trackunit/shared-utils": "1.13.14",
22
+ "@trackunit/shared-utils": "1.13.16",
23
+ "http-proxy-middleware": "3.0.5",
23
24
  "pacote": "^21.0.4",
24
25
  "semver": "7.5.4"
25
26
  },
@@ -1,16 +1,10 @@
1
1
  import type { DevServer as RspackDevServer } from "@rspack/core";
2
2
  import type { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server";
3
- /**
4
- * Generates a dev server configuration for webpack configuration.
5
- *
6
- * @param webpackDevServerConfiguration The configuration to extend.
7
- * @returns {WebpackDevServerConfiguration} The dev server for webpack.
8
- */
9
- export declare const getIrisAppWebpackDevServer: (webpackDevServerConfiguration?: WebpackDevServerConfiguration) => Promise<WebpackDevServerConfiguration>;
10
- /**
11
- * Generates a dev server configuration for rspack configuration.
12
- *
13
- * @param rspackDevServerConfiguration The configuration to extend.
14
- * @returns {RspackDevServer} The dev server for rspack.
15
- */
16
- export declare const getIrisAppRspackDevServer: (rspackDevServerConfiguration?: RspackDevServer) => Promise<RspackDevServer>;
3
+ import type { ServerlessPortMap } from "./spawnServerlessExtensions";
4
+ export interface DevServerOptions {
5
+ serverlessPortMap?: ServerlessPortMap;
6
+ }
7
+ /** Generates a dev server configuration for webpack. */
8
+ export declare const getIrisAppWebpackDevServer: (webpackDevServerConfiguration?: WebpackDevServerConfiguration, options?: DevServerOptions) => Promise<WebpackDevServerConfiguration>;
9
+ /** Generates a dev server configuration for rspack. */
10
+ export declare const getIrisAppRspackDevServer: (rspackDevServerConfiguration?: RspackDevServer, options?: DevServerOptions) => Promise<RspackDevServer>;
@@ -1,98 +1,171 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getIrisAppRspackDevServer = exports.getIrisAppWebpackDevServer = void 0;
4
+ const http_proxy_middleware_1 = require("http-proxy-middleware");
4
5
  const getAvailablePort_1 = require("./getAvailablePort");
5
- /**
6
- * Creates the base Iris App dev server configuration.
7
- *
8
- * Returns the config without explicit typing because webpack-dev-server's Configuration
9
- * and rspack's DevServer are nominally different types despite being structurally identical.
10
- * The rspack team acknowledges this type incompatibility in their own codebase with @ts-ignore.
11
- *
12
- * @see https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts
13
- */
14
- const createIrisAppDevServerConfig = async (port) => ({
6
+ /** Parses the extension ID from an invoke URL path. Format: /invoke/@{org}/{app}/{extension-id}/{endpoint} */
7
+ const parseExtensionIdFromPath = (path) => {
8
+ const match = path.match(/^\/invoke\/@[^/]+\/[^/]+\/([^/]+)/);
9
+ return match?.[1];
10
+ };
11
+ /** Extracts the endpoint path from an invoke URL. Format: /invoke/@{org}/{app}/{extension-id}/{endpoint} */
12
+ const extractEndpointPath = (path) => {
13
+ const match = path.match(/^\/invoke\/@[^/]+\/[^/]+\/[^/]+(.*)$/);
14
+ return match?.[1] || "/";
15
+ };
16
+ const CORS_ALLOWED_HEADERS = "X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion";
17
+ const DEFAULT_ORIGIN = "https://dev.manager.trackunit.com";
18
+ const getOrigin = (request) => request.headers.origin || DEFAULT_ORIGIN;
19
+ /** Sets CORS headers on the response. */
20
+ const setCorsHeaders = (response, origin) => {
21
+ response.header("Access-Control-Allow-Origin", origin);
22
+ response.header("Access-Control-Allow-Headers", CORS_ALLOWED_HEADERS);
23
+ };
24
+ /** Creates a proxy for the remote Iris platform (used when extension is not running locally). */
25
+ const createRemoteIrisProxy = () => (0, http_proxy_middleware_1.createProxyMiddleware)({
26
+ target: "https://dev.iris.trackunit.app",
27
+ changeOrigin: true,
28
+ secure: true,
29
+ });
30
+ /** Creates a proxy for a local serverless extension. */
31
+ const createLocalExtensionProxy = (target, extensionId) => (0, http_proxy_middleware_1.createProxyMiddleware)({
32
+ target,
33
+ changeOrigin: true,
34
+ pathRewrite: path => extractEndpointPath(path),
35
+ on: {
36
+ error: (err, _proxyReq, proxyRes) => {
37
+ // eslint-disable-next-line no-console
38
+ console.error(`[Proxy] Error proxying to ${extensionId}:`, err.message);
39
+ if ("writeHead" in proxyRes) {
40
+ proxyRes.writeHead(502, { "Content-Type": "application/json" });
41
+ proxyRes.end(JSON.stringify({ error: `Serverless extension ${extensionId} unavailable` }));
42
+ }
43
+ },
44
+ },
45
+ });
46
+ /** Gets or creates a cached proxy for a local extension. */
47
+ const getOrCreateLocalProxy = (cache, extensionId, port) => {
48
+ const existing = cache.get(extensionId);
49
+ if (existing !== undefined) {
50
+ return existing;
51
+ }
52
+ const target = `http://localhost:${port}`;
53
+ const newProxy = createLocalExtensionProxy(target, extensionId);
54
+ cache.set(extensionId, newProxy);
55
+ return newProxy;
56
+ };
57
+ /** Creates the /invoke proxy middleware that routes to local or remote extensions. */
58
+ const createInvokeProxyHandler = (serverlessPortMap, localProxyCache, remoteProxy) => {
59
+ return (req, res, next) => {
60
+ const fullPath = req.originalUrl ?? `/invoke${req.url ?? ""}`;
61
+ const extensionId = parseExtensionIdFromPath(fullPath);
62
+ const localPort = extensionId !== undefined ? serverlessPortMap?.get(extensionId) : undefined;
63
+ if (localPort !== undefined && extensionId !== undefined) {
64
+ const endpointPath = extractEndpointPath(fullPath);
65
+ // eslint-disable-next-line no-console
66
+ console.log(`[Proxy] ${fullPath} -> http://localhost:${localPort}${endpointPath}`);
67
+ const proxy = getOrCreateLocalProxy(localProxyCache, extensionId, localPort);
68
+ proxy(req, res, next);
69
+ }
70
+ else {
71
+ remoteProxy(req, res, next);
72
+ }
73
+ };
74
+ };
75
+ /** CORS preflight middleware - responds to non-GET requests with allowed methods. */
76
+ const corsPreflightMiddleware = (req, res, next) => {
77
+ if (req.method === "GET") {
78
+ next();
79
+ }
80
+ else {
81
+ res.end("GET, HEAD");
82
+ }
83
+ };
84
+ /** CORS origin header middleware - sets Access-Control-Allow-Origin on all requests. */
85
+ const createCorsOriginHandler = () => {
86
+ return (request, response, next) => {
87
+ response.header("Access-Control-Allow-Origin", getOrigin(request));
88
+ next();
89
+ };
90
+ };
91
+ /** Creates the /manifestAndToken endpoint handler. */
92
+ const createManifestAndTokenHandler = (port) => {
93
+ return async (request, response) => {
94
+ setCorsHeaders(response, getOrigin(request));
95
+ try {
96
+ const resp = await fetch(`http://localhost:${port}/manifest.json`);
97
+ const body = await resp.json();
98
+ response.send({ manifest: body });
99
+ }
100
+ catch (e) {
101
+ // eslint-disable-next-line no-console
102
+ console.error("ERROR: ", e);
103
+ response.status(500);
104
+ response.send(e);
105
+ }
106
+ };
107
+ };
108
+ /** Creates the /extensionloader.js endpoint handler. */
109
+ const createExtensionLoaderHandler = () => {
110
+ return async (request, response) => {
111
+ setCorsHeaders(response, getOrigin(request));
112
+ try {
113
+ const url = process.env.LOCAL === "true"
114
+ ? "http://localhost:3000/extensionloader.js"
115
+ : "https://iris.trackunit.app/extensionloader.js";
116
+ const resp = await fetch(url);
117
+ const body = await resp.text();
118
+ response.send(body);
119
+ }
120
+ catch (e) {
121
+ // eslint-disable-next-line no-console
122
+ console.error("ERROR: ", e);
123
+ response.status(500);
124
+ response.send(e);
125
+ }
126
+ };
127
+ };
128
+ /** Registers all middlewares on the dev server app. */
129
+ const registerMiddlewares = (app, middlewares, port, options) => {
130
+ const localProxyCache = new Map();
131
+ const remoteProxy = createRemoteIrisProxy();
132
+ // Register /invoke proxy
133
+ app.use("/invoke", createInvokeProxyHandler(options.serverlessPortMap, localProxyCache, remoteProxy));
134
+ // Register CORS origin handler on all routes
135
+ app.use("/", createCorsOriginHandler());
136
+ // Register endpoint handlers
137
+ app.use("/manifestAndToken", createManifestAndTokenHandler(port));
138
+ app.use("/extensionloader.js", createExtensionLoaderHandler());
139
+ // Add CORS preflight to middleware array
140
+ middlewares.push({
141
+ name: "cors-preflight",
142
+ path: "/",
143
+ middleware: corsPreflightMiddleware,
144
+ });
145
+ return middlewares;
146
+ };
147
+ /** Creates the base Iris App dev server configuration. */
148
+ const createIrisAppDevServerConfig = async (port, options = {}) => ({
15
149
  port,
16
150
  historyApiFallback: true,
17
151
  headers: {
18
152
  "Access-Control-Allow-Credentials": "true",
19
153
  "Access-Control-Max-Age": "3600",
20
154
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
21
- "Access-Control-Allow-Headers": "X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion",
155
+ "Access-Control-Allow-Headers": CORS_ALLOWED_HEADERS,
22
156
  },
23
- proxy: [
24
- {
25
- context: ["/invoke"],
26
- target: "https://dev.iris.trackunit.app",
27
- changeOrigin: true,
28
- secure: true,
29
- logLevel: "debug",
30
- },
31
- ],
32
157
  onListening: () => { },
33
158
  setupMiddlewares: (middlewares, devServer) => {
34
- middlewares.push({
35
- name: "cors-preflight",
36
- path: "/",
37
- middleware: (req, res, next) => {
38
- if (req.method === "GET") {
39
- next();
40
- }
41
- else {
42
- res.end?.("GET, HEAD");
43
- }
44
- },
45
- });
46
- devServer.app?.use("/", (request, response, next) => {
47
- response.header("Access-Control-Allow-Origin", request.headers.origin || "https://dev.manager.trackunit.com");
48
- next();
49
- });
50
- devServer.app?.use("/manifestAndToken", async (request, response) => {
51
- response.header("Access-Control-Allow-Origin", request.headers.origin || "https://dev.manager.trackunit.com");
52
- response.header("Access-Control-Allow-Headers", "X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion");
53
- try {
54
- const resp = await fetch(`http://localhost:${port}/manifest.json`);
55
- const body = await resp.json();
56
- response.send({ manifest: body });
57
- }
58
- catch (e) {
59
- // eslint-disable-next-line no-console
60
- console.error("ERROR: ", e);
61
- response.status(500);
62
- response.send(e);
63
- }
64
- });
65
- devServer.app?.use("/extensionloader.js", async (request, response) => {
66
- response.header("Access-Control-Allow-Origin", request.headers.origin || "https://dev.manager.trackunit.com");
67
- response.header("Access-Control-Allow-Headers", "X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion");
68
- try {
69
- let url = "https://iris.trackunit.app/extensionloader.js";
70
- if (process.env.LOCAL === "true") {
71
- url = "http://localhost:3000/extensionloader.js";
72
- }
73
- const resp = await fetch(url);
74
- const body = await resp.text();
75
- response.send(body);
76
- }
77
- catch (e) {
78
- // eslint-disable-next-line no-console
79
- console.error("ERROR: ", e);
80
- response.status(500);
81
- response.send(e);
82
- }
83
- });
159
+ if (devServer.app) {
160
+ return registerMiddlewares(devServer.app, middlewares, port, options);
161
+ }
84
162
  return middlewares;
85
163
  },
86
164
  });
87
- /**
88
- * Generates a dev server configuration for webpack configuration.
89
- *
90
- * @param webpackDevServerConfiguration The configuration to extend.
91
- * @returns {WebpackDevServerConfiguration} The dev server for webpack.
92
- */
93
- const getIrisAppWebpackDevServer = async (webpackDevServerConfiguration = {}) => {
165
+ /** Generates a dev server configuration for webpack. */
166
+ const getIrisAppWebpackDevServer = async (webpackDevServerConfiguration = {}, options = {}) => {
94
167
  const port = await (0, getAvailablePort_1.getAvailablePort)(22220, 22229);
95
- const baseConfig = await createIrisAppDevServerConfig(port);
168
+ const baseConfig = await createIrisAppDevServerConfig(port, options);
96
169
  // Type assertion required: webpack-dev-server and rspack have nominally different but
97
170
  // structurally identical DevServer types. This is a known limitation acknowledged by
98
171
  // the rspack team. See: https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts
@@ -100,15 +173,10 @@ const getIrisAppWebpackDevServer = async (webpackDevServerConfiguration = {}) =>
100
173
  return { ...baseConfig, ...webpackDevServerConfiguration };
101
174
  };
102
175
  exports.getIrisAppWebpackDevServer = getIrisAppWebpackDevServer;
103
- /**
104
- * Generates a dev server configuration for rspack configuration.
105
- *
106
- * @param rspackDevServerConfiguration The configuration to extend.
107
- * @returns {RspackDevServer} The dev server for rspack.
108
- */
109
- const getIrisAppRspackDevServer = async (rspackDevServerConfiguration = {}) => {
176
+ /** Generates a dev server configuration for rspack. */
177
+ const getIrisAppRspackDevServer = async (rspackDevServerConfiguration = {}, options = {}) => {
110
178
  const port = await (0, getAvailablePort_1.getAvailablePort)(22220, 22229);
111
- const baseConfig = await createIrisAppDevServerConfig(port);
179
+ const baseConfig = await createIrisAppDevServerConfig(port, options);
112
180
  // Type assertion required: webpack-dev-server and rspack have nominally different but
113
181
  // structurally identical DevServer types. This is a known limitation acknowledged by
114
182
  // the rspack team. See: https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts
@@ -1 +1 @@
1
- {"version":3,"file":"getIrisAppDevServer.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-build-utilities/src/getIrisAppDevServer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DevServer as RspackDevServer } from \"@rspack/core\";\nimport type { Configuration as WebpackDevServerConfiguration } from \"webpack-dev-server\";\nimport { getAvailablePort } from \"./getAvailablePort\";\n\n/**\n * Creates the base Iris App dev server configuration.\n *\n * Returns the config without explicit typing because webpack-dev-server's Configuration\n * and rspack's DevServer are nominally different types despite being structurally identical.\n * The rspack team acknowledges this type incompatibility in their own codebase with @ts-ignore.\n *\n * @see https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts\n */\nconst createIrisAppDevServerConfig = async (port: number) => ({\n port,\n historyApiFallback: true,\n headers: {\n \"Access-Control-Allow-Credentials\": \"true\",\n \"Access-Control-Max-Age\": \"3600\",\n \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n \"Access-Control-Allow-Headers\":\n \"X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion\",\n },\n proxy: [\n {\n context: [\"/invoke\"],\n target: \"https://dev.iris.trackunit.app\",\n changeOrigin: true,\n secure: true,\n logLevel: \"debug\",\n },\n ],\n onListening: () => {},\n setupMiddlewares: (\n middlewares: Array<{ name?: string; path?: string; middleware: unknown }>,\n devServer: { app?: { use: (path: string, handler: unknown) => void } }\n ) => {\n middlewares.push({\n name: \"cors-preflight\",\n path: \"/\",\n middleware: (\n req: { method?: string },\n res: { send?: (body: unknown) => void; end?: (body: string) => void },\n next: () => void\n ) => {\n if (req.method === \"GET\") {\n next();\n } else {\n res.end?.(\"GET, HEAD\");\n }\n },\n });\n devServer.app?.use(\n \"/\",\n (\n request: { headers: { origin?: string } },\n response: { header: (name: string, value: string) => void },\n next: () => void\n ) => {\n response.header(\"Access-Control-Allow-Origin\", request.headers.origin || \"https://dev.manager.trackunit.com\");\n next();\n }\n );\n devServer.app?.use(\n \"/manifestAndToken\",\n async (\n request: { headers: { origin?: string } },\n response: {\n header: (name: string, value: string) => void;\n send: (body: unknown) => void;\n status: (code: number) => void;\n }\n ) => {\n response.header(\"Access-Control-Allow-Origin\", request.headers.origin || \"https://dev.manager.trackunit.com\");\n response.header(\n \"Access-Control-Allow-Headers\",\n \"X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion\"\n );\n try {\n const resp = await fetch(`http://localhost:${port}/manifest.json`);\n const body = await resp.json();\n response.send({ manifest: body });\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error(\"ERROR: \", e);\n response.status(500);\n response.send(e);\n }\n }\n );\n devServer.app?.use(\n \"/extensionloader.js\",\n async (\n request: { headers: { origin?: string } },\n response: {\n header: (name: string, value: string) => void;\n send: (body: unknown) => void;\n status: (code: number) => void;\n }\n ) => {\n response.header(\"Access-Control-Allow-Origin\", request.headers.origin || \"https://dev.manager.trackunit.com\");\n response.header(\n \"Access-Control-Allow-Headers\",\n \"X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion\"\n );\n try {\n let url = \"https://iris.trackunit.app/extensionloader.js\";\n if (process.env.LOCAL === \"true\") {\n url = \"http://localhost:3000/extensionloader.js\";\n }\n const resp = await fetch(url);\n const body = await resp.text();\n response.send(body);\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error(\"ERROR: \", e);\n response.status(500);\n response.send(e);\n }\n }\n );\n return middlewares;\n },\n});\n\n/**\n * Generates a dev server configuration for webpack configuration.\n *\n * @param webpackDevServerConfiguration The configuration to extend.\n * @returns {WebpackDevServerConfiguration} The dev server for webpack.\n */\nexport const getIrisAppWebpackDevServer = async (\n webpackDevServerConfiguration: WebpackDevServerConfiguration = {}\n): Promise<WebpackDevServerConfiguration> => {\n const port = await getAvailablePort(22220, 22229);\n const baseConfig = await createIrisAppDevServerConfig(port);\n\n // Type assertion required: webpack-dev-server and rspack have nominally different but\n // structurally identical DevServer types. This is a known limitation acknowledged by\n // the rspack team. See: https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts\n // eslint-disable-next-line local-rules/no-typescript-assertion\n return { ...baseConfig, ...webpackDevServerConfiguration } as unknown as WebpackDevServerConfiguration;\n};\n\n/**\n * Generates a dev server configuration for rspack configuration.\n *\n * @param rspackDevServerConfiguration The configuration to extend.\n * @returns {RspackDevServer} The dev server for rspack.\n */\nexport const getIrisAppRspackDevServer = async (\n rspackDevServerConfiguration: RspackDevServer = {}\n): Promise<RspackDevServer> => {\n const port = await getAvailablePort(22220, 22229);\n const baseConfig = await createIrisAppDevServerConfig(port);\n\n // Type assertion required: webpack-dev-server and rspack have nominally different but\n // structurally identical DevServer types. This is a known limitation acknowledged by\n // the rspack team. See: https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts\n // eslint-disable-next-line local-rules/no-typescript-assertion\n return { ...baseConfig, ...rspackDevServerConfiguration } as unknown as RspackDevServer;\n};\n"]}
1
+ {"version":3,"file":"getIrisAppDevServer.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-build-utilities/src/getIrisAppDevServer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DevServer as RspackDevServer } from \"@rspack/core\";\nimport { createProxyMiddleware } from \"http-proxy-middleware\";\nimport type { Configuration as WebpackDevServerConfiguration } from \"webpack-dev-server\";\nimport { getAvailablePort } from \"./getAvailablePort\";\nimport type { ServerlessPortMap } from \"./spawnServerlessExtensions\";\n\nexport interface DevServerOptions {\n serverlessPortMap?: ServerlessPortMap;\n}\n\ntype MiddlewareRequest = {\n url?: string;\n originalUrl?: string;\n method?: string;\n headers: { origin?: string } & Record<string, string | undefined>;\n};\n\ntype MiddlewareResponse = {\n header: (name: string, value: string) => void;\n writeHead: (status: number, headers?: Record<string, string>) => void;\n end: (body?: string) => void;\n send: (body: unknown) => void;\n status: (code: number) => void;\n};\n\ntype MiddlewareNext = () => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- http-proxy-middleware types are complex\ntype ProxyHandler = (req: any, res: any, next: MiddlewareNext) => void;\n\ntype DevServerApp = { use: (path: string, handler: unknown) => void };\n\ntype MiddlewareEntry = { name?: string; path?: string; middleware: unknown };\n\n/** Parses the extension ID from an invoke URL path. Format: /invoke/@{org}/{app}/{extension-id}/{endpoint} */\nconst parseExtensionIdFromPath = (path: string): string | undefined => {\n const match = path.match(/^\\/invoke\\/@[^/]+\\/[^/]+\\/([^/]+)/);\n return match?.[1];\n};\n\n/** Extracts the endpoint path from an invoke URL. Format: /invoke/@{org}/{app}/{extension-id}/{endpoint} */\nconst extractEndpointPath = (path: string): string => {\n const match = path.match(/^\\/invoke\\/@[^/]+\\/[^/]+\\/[^/]+(.*)$/);\n return match?.[1] || \"/\";\n};\n\nconst CORS_ALLOWED_HEADERS =\n \"X-Requested-With, baggage, content-type, Authorization, sentry-trace, session-id, commit-number, x-trackunitappversion\";\n\nconst DEFAULT_ORIGIN = \"https://dev.manager.trackunit.com\";\n\nconst getOrigin = (request: { headers: { origin?: string } }): string => request.headers.origin || DEFAULT_ORIGIN;\n\n/** Sets CORS headers on the response. */\nconst setCorsHeaders = (response: Pick<MiddlewareResponse, \"header\">, origin: string): void => {\n response.header(\"Access-Control-Allow-Origin\", origin);\n response.header(\"Access-Control-Allow-Headers\", CORS_ALLOWED_HEADERS);\n};\n\n/** Creates a proxy for the remote Iris platform (used when extension is not running locally). */\nconst createRemoteIrisProxy = (): ProxyHandler =>\n createProxyMiddleware({\n target: \"https://dev.iris.trackunit.app\",\n changeOrigin: true,\n secure: true,\n });\n\n/** Creates a proxy for a local serverless extension. */\nconst createLocalExtensionProxy = (target: string, extensionId: string): ProxyHandler =>\n createProxyMiddleware({\n target,\n changeOrigin: true,\n pathRewrite: path => extractEndpointPath(path),\n on: {\n error: (err, _proxyReq, proxyRes) => {\n // eslint-disable-next-line no-console\n console.error(`[Proxy] Error proxying to ${extensionId}:`, err.message);\n if (\"writeHead\" in proxyRes) {\n proxyRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n proxyRes.end(JSON.stringify({ error: `Serverless extension ${extensionId} unavailable` }));\n }\n },\n },\n });\n\n/** Gets or creates a cached proxy for a local extension. */\nconst getOrCreateLocalProxy = (cache: Map<string, ProxyHandler>, extensionId: string, port: number): ProxyHandler => {\n const existing = cache.get(extensionId);\n if (existing !== undefined) {\n return existing;\n }\n\n const target = `http://localhost:${port}`;\n const newProxy = createLocalExtensionProxy(target, extensionId);\n cache.set(extensionId, newProxy);\n return newProxy;\n};\n\n/** Creates the /invoke proxy middleware that routes to local or remote extensions. */\nconst createInvokeProxyHandler = (\n serverlessPortMap: ServerlessPortMap | undefined,\n localProxyCache: Map<string, ProxyHandler>,\n remoteProxy: ProxyHandler\n) => {\n return (req: MiddlewareRequest, res: MiddlewareResponse, next: MiddlewareNext): void => {\n const fullPath = req.originalUrl ?? `/invoke${req.url ?? \"\"}`;\n const extensionId = parseExtensionIdFromPath(fullPath);\n const localPort = extensionId !== undefined ? serverlessPortMap?.get(extensionId) : undefined;\n\n if (localPort !== undefined && extensionId !== undefined) {\n const endpointPath = extractEndpointPath(fullPath);\n // eslint-disable-next-line no-console\n console.log(`[Proxy] ${fullPath} -> http://localhost:${localPort}${endpointPath}`);\n\n const proxy = getOrCreateLocalProxy(localProxyCache, extensionId, localPort);\n proxy(req, res, next);\n } else {\n remoteProxy(req, res, next);\n }\n };\n};\n\n/** CORS preflight middleware - responds to non-GET requests with allowed methods. */\nconst corsPreflightMiddleware = (\n req: Pick<MiddlewareRequest, \"method\">,\n res: Pick<MiddlewareResponse, \"end\">,\n next: MiddlewareNext\n): void => {\n if (req.method === \"GET\") {\n next();\n } else {\n res.end(\"GET, HEAD\");\n }\n};\n\n/** CORS origin header middleware - sets Access-Control-Allow-Origin on all requests. */\nconst createCorsOriginHandler = () => {\n return (\n request: Pick<MiddlewareRequest, \"headers\">,\n response: Pick<MiddlewareResponse, \"header\">,\n next: MiddlewareNext\n ): void => {\n response.header(\"Access-Control-Allow-Origin\", getOrigin(request));\n next();\n };\n};\n\n/** Creates the /manifestAndToken endpoint handler. */\nconst createManifestAndTokenHandler = (port: number) => {\n return async (\n request: Pick<MiddlewareRequest, \"headers\">,\n response: Pick<MiddlewareResponse, \"header\" | \"send\" | \"status\">\n ): Promise<void> => {\n setCorsHeaders(response, getOrigin(request));\n try {\n const resp = await fetch(`http://localhost:${port}/manifest.json`);\n const body = await resp.json();\n response.send({ manifest: body });\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error(\"ERROR: \", e);\n response.status(500);\n response.send(e);\n }\n };\n};\n\n/** Creates the /extensionloader.js endpoint handler. */\nconst createExtensionLoaderHandler = () => {\n return async (\n request: Pick<MiddlewareRequest, \"headers\">,\n response: Pick<MiddlewareResponse, \"header\" | \"send\" | \"status\">\n ): Promise<void> => {\n setCorsHeaders(response, getOrigin(request));\n try {\n const url =\n process.env.LOCAL === \"true\"\n ? \"http://localhost:3000/extensionloader.js\"\n : \"https://iris.trackunit.app/extensionloader.js\";\n const resp = await fetch(url);\n const body = await resp.text();\n response.send(body);\n } catch (e) {\n // eslint-disable-next-line no-console\n console.error(\"ERROR: \", e);\n response.status(500);\n response.send(e);\n }\n };\n};\n\n/** Registers all middlewares on the dev server app. */\nconst registerMiddlewares = (\n app: DevServerApp,\n middlewares: Array<MiddlewareEntry>,\n port: number,\n options: DevServerOptions\n): Array<MiddlewareEntry> => {\n const localProxyCache = new Map<string, ProxyHandler>();\n const remoteProxy = createRemoteIrisProxy();\n\n // Register /invoke proxy\n app.use(\"/invoke\", createInvokeProxyHandler(options.serverlessPortMap, localProxyCache, remoteProxy));\n\n // Register CORS origin handler on all routes\n app.use(\"/\", createCorsOriginHandler());\n\n // Register endpoint handlers\n app.use(\"/manifestAndToken\", createManifestAndTokenHandler(port));\n app.use(\"/extensionloader.js\", createExtensionLoaderHandler());\n\n // Add CORS preflight to middleware array\n middlewares.push({\n name: \"cors-preflight\",\n path: \"/\",\n middleware: corsPreflightMiddleware,\n });\n\n return middlewares;\n};\n\n/** Creates the base Iris App dev server configuration. */\nconst createIrisAppDevServerConfig = async (port: number, options: DevServerOptions = {}) => ({\n port,\n historyApiFallback: true,\n headers: {\n \"Access-Control-Allow-Credentials\": \"true\",\n \"Access-Control-Max-Age\": \"3600\",\n \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n \"Access-Control-Allow-Headers\": CORS_ALLOWED_HEADERS,\n },\n onListening: () => {},\n setupMiddlewares: (middlewares: Array<MiddlewareEntry>, devServer: { app?: DevServerApp }) => {\n if (devServer.app) {\n return registerMiddlewares(devServer.app, middlewares, port, options);\n }\n return middlewares;\n },\n});\n\n/** Generates a dev server configuration for webpack. */\nexport const getIrisAppWebpackDevServer = async (\n webpackDevServerConfiguration: WebpackDevServerConfiguration = {},\n options: DevServerOptions = {}\n): Promise<WebpackDevServerConfiguration> => {\n const port = await getAvailablePort(22220, 22229);\n const baseConfig = await createIrisAppDevServerConfig(port, options);\n\n // Type assertion required: webpack-dev-server and rspack have nominally different but\n // structurally identical DevServer types. This is a known limitation acknowledged by\n // the rspack team. See: https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts\n // eslint-disable-next-line local-rules/no-typescript-assertion\n return { ...baseConfig, ...webpackDevServerConfiguration } as unknown as WebpackDevServerConfiguration;\n};\n\n/** Generates a dev server configuration for rspack. */\nexport const getIrisAppRspackDevServer = async (\n rspackDevServerConfiguration: RspackDevServer = {},\n options: DevServerOptions = {}\n): Promise<RspackDevServer> => {\n const port = await getAvailablePort(22220, 22229);\n const baseConfig = await createIrisAppDevServerConfig(port, options);\n\n // Type assertion required: webpack-dev-server and rspack have nominally different but\n // structurally identical DevServer types. This is a known limitation acknowledged by\n // the rspack team. See: https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-dev-server/src/server.ts\n // eslint-disable-next-line local-rules/no-typescript-assertion\n return { ...baseConfig, ...rspackDevServerConfiguration } as unknown as RspackDevServer;\n};\n"]}
package/src/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export * from "./plugin-shared/consoleUtils";
12
12
  export * from "./plugin-shared/customFieldUtil";
13
13
  export * from "./plugin-shared/extensionUtil";
14
14
  export * from "./plugin-shared/indexHtmlUtil";
15
+ export * from "./spawnServerlessExtensions";
package/src/index.js CHANGED
@@ -15,4 +15,5 @@ tslib_1.__exportStar(require("./plugin-shared/consoleUtils"), exports);
15
15
  tslib_1.__exportStar(require("./plugin-shared/customFieldUtil"), exports);
16
16
  tslib_1.__exportStar(require("./plugin-shared/extensionUtil"), exports);
17
17
  tslib_1.__exportStar(require("./plugin-shared/indexHtmlUtil"), exports);
18
+ tslib_1.__exportStar(require("./spawnServerlessExtensions"), exports);
18
19
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-build-utilities/src/index.ts"],"names":[],"mappings":";;;AAAA,gEAAsC;AACtC,+DAAqC;AACrC,mEAAyC;AACzC,6DAAmC;AACnC,4DAAkC;AAClC,iEAAuC;AACvC,oEAA0C;AAC1C,gEAAsC;AACtC,kEAAwC;AACxC,qEAA2C;AAC3C,uEAA6C;AAC7C,0EAAgD;AAChD,wEAA8C;AAC9C,wEAA8C","sourcesContent":["export * from \"./checkPackageVersion\";\nexport * from \"./enableTsConfigPath\";\nexport * from \"./getAliasesFromTsConfig\";\nexport * from \"./getAvailablePort\";\nexport * from \"./getCopyPatterns\";\nexport * from \"./getExposedExtensions\";\nexport * from \"./getGraphqlCodeGenConfig\";\nexport * from \"./getIrisAppDevServer\";\nexport * from \"./getSharedDependencies\";\nexport * from \"./getTailwindContentForApp\";\nexport * from \"./plugin-shared/consoleUtils\";\nexport * from \"./plugin-shared/customFieldUtil\";\nexport * from \"./plugin-shared/extensionUtil\";\nexport * from \"./plugin-shared/indexHtmlUtil\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-build-utilities/src/index.ts"],"names":[],"mappings":";;;AAAA,gEAAsC;AACtC,+DAAqC;AACrC,mEAAyC;AACzC,6DAAmC;AACnC,4DAAkC;AAClC,iEAAuC;AACvC,oEAA0C;AAC1C,gEAAsC;AACtC,kEAAwC;AACxC,qEAA2C;AAC3C,uEAA6C;AAC7C,0EAAgD;AAChD,wEAA8C;AAC9C,wEAA8C;AAC9C,sEAA4C","sourcesContent":["export * from \"./checkPackageVersion\";\nexport * from \"./enableTsConfigPath\";\nexport * from \"./getAliasesFromTsConfig\";\nexport * from \"./getAvailablePort\";\nexport * from \"./getCopyPatterns\";\nexport * from \"./getExposedExtensions\";\nexport * from \"./getGraphqlCodeGenConfig\";\nexport * from \"./getIrisAppDevServer\";\nexport * from \"./getSharedDependencies\";\nexport * from \"./getTailwindContentForApp\";\nexport * from \"./plugin-shared/consoleUtils\";\nexport * from \"./plugin-shared/customFieldUtil\";\nexport * from \"./plugin-shared/extensionUtil\";\nexport * from \"./plugin-shared/indexHtmlUtil\";\nexport * from \"./spawnServerlessExtensions\";\n"]}
@@ -0,0 +1,16 @@
1
+ import { IrisAppManifest } from "@trackunit/iris-app-api";
2
+ import { ChildProcess } from "child_process";
3
+ export type ServerlessPortMap = Map<string, number>;
4
+ export interface SpawnedServerlessExtensions {
5
+ portMap: ServerlessPortMap;
6
+ processes: Array<ChildProcess>;
7
+ cleanup: () => void;
8
+ }
9
+ /**
10
+ * Spawns serve processes for all serverless function extensions in the manifest.
11
+ *
12
+ * @param manifest The Iris App manifest containing extensions
13
+ * @param workspaceRoot The root directory of the NX workspace
14
+ * @returns {Promise<SpawnedServerlessExtensions>} Object containing port map, spawned processes, and cleanup function
15
+ */
16
+ export declare const spawnServerlessExtensions: (manifest: IrisAppManifest, workspaceRoot: string) => Promise<SpawnedServerlessExtensions>;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spawnServerlessExtensions = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const getAvailablePort_1 = require("./getAvailablePort");
6
+ const SERVERLESS_PORT_MIN = 22230;
7
+ const SERVERLESS_PORT_MAX = 22239;
8
+ /**
9
+ * Spawns serve processes for all serverless function extensions in the manifest.
10
+ *
11
+ * @param manifest The Iris App manifest containing extensions
12
+ * @param workspaceRoot The root directory of the NX workspace
13
+ * @returns {Promise<SpawnedServerlessExtensions>} Object containing port map, spawned processes, and cleanup function
14
+ */
15
+ const spawnServerlessExtensions = async (manifest, workspaceRoot) => {
16
+ const portMap = new Map();
17
+ const processes = [];
18
+ const serverlessExtensions = manifest.extensions.filter(ext => ext.type === "SERVERLESS_FUNCTION_EXTENSION");
19
+ if (serverlessExtensions.length === 0) {
20
+ return {
21
+ portMap,
22
+ processes,
23
+ cleanup: () => { },
24
+ };
25
+ }
26
+ // eslint-disable-next-line no-console
27
+ console.log(`\n[Serverless] Found ${serverlessExtensions.length} serverless extension(s)`);
28
+ let nextPort = SERVERLESS_PORT_MIN;
29
+ for (const extension of serverlessExtensions) {
30
+ const extensionId = extension.id;
31
+ // Find available port
32
+ let port;
33
+ try {
34
+ port = await (0, getAvailablePort_1.getAvailablePort)(nextPort, SERVERLESS_PORT_MAX);
35
+ nextPort = port + 1;
36
+ }
37
+ catch {
38
+ // eslint-disable-next-line no-console
39
+ console.error(`[Serverless] No available ports for ${extensionId}, skipping`);
40
+ continue;
41
+ }
42
+ // eslint-disable-next-line no-console
43
+ console.log(`[Serverless] Starting ${extensionId} on port ${port}`);
44
+ const child = (0, child_process_1.spawn)("npx", ["nx", "run", `${extensionId}:serve`], {
45
+ cwd: workspaceRoot,
46
+ env: {
47
+ ...process.env,
48
+ PORT: String(port),
49
+ },
50
+ stdio: ["ignore", "pipe", "pipe"],
51
+ shell: true,
52
+ });
53
+ // Prefix output with extension name
54
+ child.stdout.on("data", (data) => {
55
+ const lines = data.toString().trim().split("\n");
56
+ for (const line of lines) {
57
+ // eslint-disable-next-line no-console
58
+ console.log(`[${extensionId}] ${line}`);
59
+ }
60
+ });
61
+ child.stderr.on("data", (data) => {
62
+ const lines = data.toString().trim().split("\n");
63
+ for (const line of lines) {
64
+ // eslint-disable-next-line no-console
65
+ console.error(`[${extensionId}] ${line}`);
66
+ }
67
+ });
68
+ child.on("error", err => {
69
+ // eslint-disable-next-line no-console
70
+ console.error(`[Serverless] Failed to start ${extensionId}:`, err.message);
71
+ });
72
+ child.on("exit", (code, signal) => {
73
+ if (code !== null && code !== 0) {
74
+ // eslint-disable-next-line no-console
75
+ console.error(`[Serverless] ${extensionId} exited with code ${code}`);
76
+ }
77
+ else if (signal) {
78
+ // eslint-disable-next-line no-console
79
+ console.log(`[Serverless] ${extensionId} terminated by signal ${signal}`);
80
+ }
81
+ });
82
+ portMap.set(extensionId, port);
83
+ processes.push(child);
84
+ }
85
+ const cleanup = () => {
86
+ // Remove process event handlers to prevent listener accumulation
87
+ process.off("exit", cleanup);
88
+ process.off("SIGINT", cleanup);
89
+ process.off("SIGTERM", cleanup);
90
+ for (const child of processes) {
91
+ if (!child.killed) {
92
+ child.kill("SIGTERM");
93
+ }
94
+ }
95
+ };
96
+ // Handle parent process exit
97
+ process.on("exit", cleanup);
98
+ process.on("SIGINT", cleanup);
99
+ process.on("SIGTERM", cleanup);
100
+ return { portMap, processes, cleanup };
101
+ };
102
+ exports.spawnServerlessExtensions = spawnServerlessExtensions;
103
+ //# sourceMappingURL=spawnServerlessExtensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnServerlessExtensions.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-build-utilities/src/spawnServerlessExtensions.ts"],"names":[],"mappings":"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","sourcesContent":["import { IrisAppManifest } from \"@trackunit/iris-app-api\";\nimport { ChildProcess, spawn } from \"child_process\";\nimport { getAvailablePort } from \"./getAvailablePort\";\n\nconst SERVERLESS_PORT_MIN = 22230;\nconst SERVERLESS_PORT_MAX = 22239;\n\nexport type ServerlessPortMap = Map<string, number>;\n\nexport interface SpawnedServerlessExtensions {\n portMap: ServerlessPortMap;\n processes: Array<ChildProcess>;\n cleanup: () => void;\n}\n\n/**\n * Spawns serve processes for all serverless function extensions in the manifest.\n *\n * @param manifest The Iris App manifest containing extensions\n * @param workspaceRoot The root directory of the NX workspace\n * @returns {Promise<SpawnedServerlessExtensions>} Object containing port map, spawned processes, and cleanup function\n */\nexport const spawnServerlessExtensions = async (\n manifest: IrisAppManifest,\n workspaceRoot: string\n): Promise<SpawnedServerlessExtensions> => {\n const portMap: ServerlessPortMap = new Map();\n const processes: Array<ChildProcess> = [];\n\n const serverlessExtensions = manifest.extensions.filter(ext => ext.type === \"SERVERLESS_FUNCTION_EXTENSION\");\n\n if (serverlessExtensions.length === 0) {\n return {\n portMap,\n processes,\n cleanup: () => {},\n };\n }\n\n // eslint-disable-next-line no-console\n console.log(`\\n[Serverless] Found ${serverlessExtensions.length} serverless extension(s)`);\n\n let nextPort = SERVERLESS_PORT_MIN;\n\n for (const extension of serverlessExtensions) {\n const extensionId = extension.id;\n\n // Find available port\n let port: number;\n try {\n port = await getAvailablePort(nextPort, SERVERLESS_PORT_MAX);\n nextPort = port + 1;\n } catch {\n // eslint-disable-next-line no-console\n console.error(`[Serverless] No available ports for ${extensionId}, skipping`);\n continue;\n }\n\n // eslint-disable-next-line no-console\n console.log(`[Serverless] Starting ${extensionId} on port ${port}`);\n\n const child = spawn(\"npx\", [\"nx\", \"run\", `${extensionId}:serve`], {\n cwd: workspaceRoot,\n env: {\n ...process.env,\n PORT: String(port),\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n shell: true,\n });\n\n // Prefix output with extension name\n child.stdout.on(\"data\", (data: Buffer) => {\n const lines = data.toString().trim().split(\"\\n\");\n for (const line of lines) {\n // eslint-disable-next-line no-console\n console.log(`[${extensionId}] ${line}`);\n }\n });\n\n child.stderr.on(\"data\", (data: Buffer) => {\n const lines = data.toString().trim().split(\"\\n\");\n for (const line of lines) {\n // eslint-disable-next-line no-console\n console.error(`[${extensionId}] ${line}`);\n }\n });\n\n child.on(\"error\", err => {\n // eslint-disable-next-line no-console\n console.error(`[Serverless] Failed to start ${extensionId}:`, err.message);\n });\n\n child.on(\"exit\", (code, signal) => {\n if (code !== null && code !== 0) {\n // eslint-disable-next-line no-console\n console.error(`[Serverless] ${extensionId} exited with code ${code}`);\n } else if (signal) {\n // eslint-disable-next-line no-console\n console.log(`[Serverless] ${extensionId} terminated by signal ${signal}`);\n }\n });\n\n portMap.set(extensionId, port);\n processes.push(child);\n }\n\n const cleanup = () => {\n // Remove process event handlers to prevent listener accumulation\n process.off(\"exit\", cleanup);\n process.off(\"SIGINT\", cleanup);\n process.off(\"SIGTERM\", cleanup);\n\n for (const child of processes) {\n if (!child.killed) {\n child.kill(\"SIGTERM\");\n }\n }\n };\n\n // Handle parent process exit\n process.on(\"exit\", cleanup);\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n return { portMap, processes, cleanup };\n};\n"]}