@nf-beta/angular 0.0.1 → 0.0.2

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.
Files changed (95) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +458 -0
  3. package/builders.json +10 -0
  4. package/collection.json +27 -0
  5. package/generators.json +12 -0
  6. package/migration-collection.json +13 -0
  7. package/package.json +18 -8
  8. package/src/builders/build/builder.d.ts +6 -0
  9. package/src/builders/build/builder.d.ts.map +1 -0
  10. package/src/builders/build/builder.js +348 -0
  11. package/src/builders/build/federation-build-notifier.d.ts +70 -0
  12. package/src/builders/build/federation-build-notifier.d.ts.map +1 -0
  13. package/src/builders/build/federation-build-notifier.js +186 -0
  14. package/src/builders/build/schema.d.ts +21 -0
  15. package/src/builders/build/schema.json +84 -0
  16. package/src/config.d.ts +3 -0
  17. package/src/config.d.ts.map +1 -0
  18. package/src/config.js +2 -0
  19. package/src/generators/native-federation/files/src/index.ts__template__ +1 -0
  20. package/src/generators/native-federation/generator.d.ts +4 -0
  21. package/src/generators/native-federation/generator.d.ts.map +1 -0
  22. package/src/generators/native-federation/generator.js +43 -0
  23. package/src/generators/native-federation/schema.d.ts +5 -0
  24. package/src/generators/native-federation/schema.json +29 -0
  25. package/src/index.d.ts +2 -0
  26. package/src/index.d.ts.map +1 -0
  27. package/src/index.js +1 -0
  28. package/src/patch-angular-build.d.ts +2 -0
  29. package/src/patch-angular-build.d.ts.map +1 -0
  30. package/src/patch-angular-build.js +5 -0
  31. package/src/plugin/dev-externals-mixin.d.ts +3 -0
  32. package/src/plugin/dev-externals-mixin.d.ts.map +1 -0
  33. package/src/plugin/dev-externals-mixin.js +29 -0
  34. package/src/plugin/externals-skip-list.d.ts +3 -0
  35. package/src/plugin/externals-skip-list.d.ts.map +1 -0
  36. package/src/plugin/externals-skip-list.js +4 -0
  37. package/src/plugin/index.d.ts +4 -0
  38. package/src/plugin/index.d.ts.map +1 -0
  39. package/src/plugin/index.js +75 -0
  40. package/src/schematics/appbuilder/schema.d.ts +3 -0
  41. package/src/schematics/appbuilder/schema.json +17 -0
  42. package/src/schematics/appbuilder/schematic.d.ts +5 -0
  43. package/src/schematics/appbuilder/schematic.d.ts.map +1 -0
  44. package/src/schematics/appbuilder/schematic.js +83 -0
  45. package/src/schematics/init/files/federation.config.js__tmpl__ +33 -0
  46. package/src/schematics/init/schema.d.ts +6 -0
  47. package/src/schematics/init/schema.json +34 -0
  48. package/src/schematics/init/schematic.d.ts +7 -0
  49. package/src/schematics/init/schematic.d.ts.map +1 -0
  50. package/src/schematics/init/schematic.js +422 -0
  51. package/src/schematics/remove/schema.d.ts +3 -0
  52. package/src/schematics/remove/schema.json +17 -0
  53. package/src/schematics/remove/schematic.d.ts +5 -0
  54. package/src/schematics/remove/schematic.d.ts.map +1 -0
  55. package/src/schematics/remove/schematic.js +109 -0
  56. package/src/schematics/update18/schema.json +7 -0
  57. package/src/schematics/update18/schematic.d.ts +3 -0
  58. package/src/schematics/update18/schematic.d.ts.map +1 -0
  59. package/src/schematics/update18/schematic.js +7 -0
  60. package/src/tools/fstart-as-data-url.d.ts +2 -0
  61. package/src/tools/fstart-as-data-url.d.ts.map +1 -0
  62. package/src/tools/fstart-as-data-url.js +1 -0
  63. package/src/utils/angular-esbuild-adapter.d.ts +10 -0
  64. package/src/utils/angular-esbuild-adapter.d.ts.map +1 -0
  65. package/src/utils/angular-esbuild-adapter.js +289 -0
  66. package/src/utils/angular-locales.d.ts +19 -0
  67. package/src/utils/angular-locales.d.ts.map +1 -0
  68. package/src/utils/angular-locales.js +18 -0
  69. package/src/utils/create-awaitable-compiler-plugin.d.ts +6 -0
  70. package/src/utils/create-awaitable-compiler-plugin.d.ts.map +1 -0
  71. package/src/utils/create-awaitable-compiler-plugin.js +29 -0
  72. package/src/utils/create-compiler-options.d.ts +5 -0
  73. package/src/utils/create-compiler-options.d.ts.map +1 -0
  74. package/src/utils/create-compiler-options.js +42 -0
  75. package/src/utils/event-source.d.ts +10 -0
  76. package/src/utils/event-source.d.ts.map +1 -0
  77. package/src/utils/event-source.js +10 -0
  78. package/src/utils/i18n.d.ts +23 -0
  79. package/src/utils/i18n.d.ts.map +1 -0
  80. package/src/utils/i18n.js +61 -0
  81. package/src/utils/mem-resuts.d.ts +29 -0
  82. package/src/utils/mem-resuts.d.ts.map +1 -0
  83. package/src/utils/mem-resuts.js +50 -0
  84. package/src/utils/patch-angular-build.d.ts +4 -0
  85. package/src/utils/patch-angular-build.d.ts.map +1 -0
  86. package/src/utils/patch-angular-build.js +29 -0
  87. package/src/utils/rebuild-events.d.ts +8 -0
  88. package/src/utils/rebuild-events.d.ts.map +1 -0
  89. package/src/utils/rebuild-events.js +4 -0
  90. package/src/utils/shared-mappings-plugin.d.ts +4 -0
  91. package/src/utils/shared-mappings-plugin.d.ts.map +1 -0
  92. package/src/utils/shared-mappings-plugin.js +28 -0
  93. package/src/utils/updateIndexHtml.d.ts +5 -0
  94. package/src/utils/updateIndexHtml.d.ts.map +1 -0
  95. package/src/utils/updateIndexHtml.js +34 -0
@@ -0,0 +1,348 @@
1
+ import * as fs from 'fs';
2
+ import * as mrmime from 'mrmime';
3
+ import * as path from 'path';
4
+ import { buildApplication } from '@angular/build';
5
+ import { buildApplicationInternal, serveWithVite } from '@angular/build/private';
6
+ import { createBuilder, targetFromTargetString, } from '@angular-devkit/architect';
7
+ import { normalizeOptions } from '@angular-devkit/build-angular/src/builders/dev-server/options.js';
8
+ import { buildForFederation, getExternals, loadFederationConfig, logger, setBuildAdapter, setLogLevel, RebuildQueue, AbortedError, } from '@nf-beta/core/build';
9
+ import { createAngularBuildAdapter, setMemResultHandler, } from '../../utils/angular-esbuild-adapter.js';
10
+ import { existsSync, mkdirSync, rmSync } from 'fs';
11
+ import { fstart } from '../../tools/fstart-as-data-url.js';
12
+ import { EsBuildResult, MemResults, NgCliAssetResult } from '../../utils/mem-resuts.js';
13
+ import { getI18nConfig, translateFederationArtefacts } from '../../utils/i18n.js';
14
+ import { RebuildHubs } from '../../utils/rebuild-events.js';
15
+ import { createSharedMappingsPlugin } from '../../utils/shared-mappings-plugin.js';
16
+ import { updateScriptTags } from '../../utils/updateIndexHtml.js';
17
+ import { federationBuildNotifier } from './federation-build-notifier.js';
18
+ const originalWrite = process.stderr.write.bind(process.stderr);
19
+ process.stderr.write = function (chunk, encodingOrCallback, callback) {
20
+ const str = typeof chunk === 'string' ? chunk : chunk.toString();
21
+ if (str.includes('vite:import-analysis') && str.includes('es-module-shims.js')) {
22
+ return true;
23
+ }
24
+ if (typeof encodingOrCallback !== 'string') {
25
+ return originalWrite(chunk, encodingOrCallback);
26
+ }
27
+ return originalWrite(chunk, encodingOrCallback, callback);
28
+ };
29
+ function _buildApplication(options, context, pluginsOrExtensions) {
30
+ let extensions;
31
+ if (pluginsOrExtensions && Array.isArray(pluginsOrExtensions)) {
32
+ extensions = {
33
+ codePlugins: pluginsOrExtensions,
34
+ };
35
+ }
36
+ else {
37
+ extensions = pluginsOrExtensions;
38
+ }
39
+ return buildApplicationInternal(options, context, extensions);
40
+ }
41
+ export async function* runBuilder(nfOptions, context) {
42
+ let target = targetFromTargetString(nfOptions.target);
43
+ let targetOptions = (await context.getTargetOptions(target));
44
+ let builder = await context.getBuilderNameForTarget(target);
45
+ if (builder === '@angular-devkit/build-angular:browser-esbuild') {
46
+ logger.info('.: NATIVE FEDERATION - UPDATE NEEDED :.');
47
+ logger.info('');
48
+ logger.info("Since version 17.1, Native Federation uses Angular's");
49
+ logger.info('Application-Builder and its Dev-Server.');
50
+ logger.info('');
51
+ logger.info('If you are sill on Angular 17.0.x, please update to');
52
+ logger.info('Angular 17.1.x or downgrade to Native Federation 17.0.x.');
53
+ logger.info('');
54
+ logger.info('For working with Native Federation 17.1.x (recommented), ');
55
+ logger.info('please update your project config, e.g. in angular.json');
56
+ logger.info('');
57
+ logger.info('This command performs the needed update for default configs:');
58
+ logger.info('');
59
+ logger.info('\tng g @angular-architects/native-federation:appbuilder');
60
+ logger.info('');
61
+ logger.info('You need to run it once per application to migrate');
62
+ logger.info('Please find more information here: https://shorturl.at/gADJW');
63
+ return;
64
+ }
65
+ /**
66
+ * Explicitly defined as devServer or if the target contains "serve"
67
+ */
68
+ const runServer = typeof nfOptions.devServer !== 'undefined'
69
+ ? !!nfOptions.devServer
70
+ : target.target.includes('serve');
71
+ let options = (await context.validateOptions(runServer
72
+ ? {
73
+ ...targetOptions,
74
+ port: nfOptions.port || targetOptions['port'],
75
+ }
76
+ : targetOptions, builder));
77
+ let serverOptions = null;
78
+ const write = true;
79
+ const watch = nfOptions.watch;
80
+ if (options['buildTarget']) {
81
+ serverOptions = await normalizeOptions(context, context.target.project, options);
82
+ target = targetFromTargetString(options['buildTarget']);
83
+ targetOptions = (await context.getTargetOptions(target));
84
+ builder = await context.getBuilderNameForTarget(target);
85
+ options = (await context.validateOptions(targetOptions, builder));
86
+ }
87
+ options.watch = watch;
88
+ if (nfOptions.baseHref) {
89
+ options.baseHref = nfOptions.baseHref;
90
+ }
91
+ if (nfOptions.outputPath) {
92
+ options.outputPath = nfOptions.outputPath;
93
+ }
94
+ const rebuildEvents = new RebuildHubs();
95
+ const adapter = createAngularBuildAdapter(options, context, rebuildEvents);
96
+ setBuildAdapter(adapter);
97
+ setLogLevel(options.verbose ? 'verbose' : 'info');
98
+ if (!options.outputPath) {
99
+ options.outputPath = `dist/${context.target.project}`;
100
+ }
101
+ const outputPath = options.outputPath;
102
+ const outputOptions = {
103
+ browser: 'browser',
104
+ server: 'server',
105
+ media: 'media',
106
+ ...(typeof outputPath === 'string' ? undefined : outputPath),
107
+ base: typeof outputPath === 'string' ? outputPath : outputPath.base,
108
+ };
109
+ const i18n = await getI18nConfig(context);
110
+ const localeFilter = getLocaleFilter(options, runServer);
111
+ const sourceLocaleSegment = typeof i18n?.sourceLocale === 'string'
112
+ ? i18n.sourceLocale
113
+ : i18n?.sourceLocale?.subPath || i18n?.sourceLocale?.code || '';
114
+ const browserOutputPath = path.join(outputOptions.base, outputOptions.browser, options.localize ? sourceLocaleSegment : '');
115
+ const differentDevServerOutputPath = Array.isArray(localeFilter) && localeFilter.length === 1;
116
+ const devServerOutputPath = !differentDevServerOutputPath
117
+ ? browserOutputPath
118
+ : path.join(outputOptions.base, outputOptions.browser, localeFilter[0]);
119
+ const entryPoint = path.join(path.dirname(options.tsConfig), 'src/main.ts');
120
+ const fedOptions = {
121
+ workspaceRoot: context.workspaceRoot,
122
+ outputPath: browserOutputPath,
123
+ federationConfig: inferConfigPath(options.tsConfig),
124
+ tsConfig: options.tsConfig,
125
+ verbose: options.verbose,
126
+ watch: false, // options.watch,
127
+ dev: !!nfOptions.dev,
128
+ entryPoint,
129
+ buildNotifications: nfOptions.buildNotifications,
130
+ cacheExternalArtifacts: nfOptions.cacheExternalArtifacts,
131
+ };
132
+ const activateSsr = nfOptions.ssr && !nfOptions.dev;
133
+ const start = process.hrtime();
134
+ const config = await loadFederationConfig(fedOptions);
135
+ logger.measure(start, 'To load the federation config.');
136
+ const externals = getExternals(config);
137
+ const plugins = [
138
+ createSharedMappingsPlugin(config.sharedMappings),
139
+ {
140
+ name: 'externals',
141
+ setup(build) {
142
+ if (!activateSsr && build.initialOptions.platform !== 'node') {
143
+ build.initialOptions.external = externals.filter(e => e !== 'tslib');
144
+ }
145
+ },
146
+ },
147
+ ];
148
+ // SSR build fails when externals are provided via the plugin
149
+ if (activateSsr) {
150
+ options.externalDependencies = externals;
151
+ }
152
+ const isLocalDevelopment = runServer && nfOptions.dev;
153
+ // Initialize SSE reloader only for local development
154
+ if (isLocalDevelopment && nfOptions.buildNotifications?.enable) {
155
+ federationBuildNotifier.initialize(nfOptions.buildNotifications.endpoint);
156
+ }
157
+ const middleware = [
158
+ ...(isLocalDevelopment
159
+ ? [
160
+ federationBuildNotifier.createEventMiddleware(req => removeBaseHref(req, options.baseHref)),
161
+ ]
162
+ : []),
163
+ (req, res, next) => {
164
+ const url = removeBaseHref(req, options.baseHref);
165
+ const fileName = path.join(fedOptions.workspaceRoot, devServerOutputPath, url);
166
+ const exists = fs.existsSync(fileName);
167
+ if (url !== '/' && url !== '' && exists) {
168
+ const lookup = mrmime.lookup;
169
+ const mimeType = lookup(path.extname(fileName)) || 'text/javascript';
170
+ const rawBody = fs.readFileSync(fileName, 'utf-8');
171
+ // TODO: Evaluate need for debug infos
172
+ // const body = addDebugInformation(url, rawBody);
173
+ const body = rawBody;
174
+ res.writeHead(200, {
175
+ 'Content-Type': mimeType,
176
+ 'Access-Control-Allow-Origin': '*',
177
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
178
+ 'Access-Control-Allow-Headers': 'Content-Type',
179
+ });
180
+ res.end(body);
181
+ }
182
+ else {
183
+ next();
184
+ }
185
+ },
186
+ ];
187
+ const memResults = new MemResults();
188
+ let first = true;
189
+ let lastResult;
190
+ if (existsSync(fedOptions.outputPath)) {
191
+ rmSync(fedOptions.outputPath, { recursive: true });
192
+ }
193
+ if (!existsSync(fedOptions.outputPath)) {
194
+ mkdirSync(fedOptions.outputPath, { recursive: true });
195
+ }
196
+ if (!write) {
197
+ // todo: Hardcoded disabled?
198
+ setMemResultHandler((outFiles, outDir) => {
199
+ const fullOutDir = outDir ? path.join(fedOptions.workspaceRoot, outDir) : undefined;
200
+ memResults.add(outFiles.map(f => new EsBuildResult(f, fullOutDir)));
201
+ });
202
+ }
203
+ let federationResult;
204
+ try {
205
+ const start = process.hrtime();
206
+ federationResult = await buildForFederation(config, fedOptions, externals);
207
+ logger.measure(start, 'To build the artifacts.');
208
+ }
209
+ catch (e) {
210
+ logger.error(e?.message ?? 'Building the artifacts failed');
211
+ process.exit(1);
212
+ }
213
+ if (activateSsr) {
214
+ writeFstartScript(fedOptions);
215
+ }
216
+ const hasLocales = i18n?.locales && Object.keys(i18n.locales).length > 0;
217
+ if (hasLocales && localeFilter) {
218
+ const start = process.hrtime();
219
+ translateFederationArtefacts(i18n, localeFilter, outputOptions.base, federationResult);
220
+ logger.measure(start, 'To translate the artifacts.');
221
+ }
222
+ options.deleteOutputPath = false;
223
+ const appBuilderName = '@angular/build:application';
224
+ const builderRun = runServer
225
+ ? serveWithVite(serverOptions, appBuilderName, _buildApplication, context, nfOptions.skipHtmlTransform ? {} : { indexHtml: transformIndexHtml(nfOptions) }, {
226
+ buildPlugins: plugins,
227
+ middleware,
228
+ })
229
+ : buildApplication(options, context, {
230
+ codePlugins: plugins,
231
+ indexHtmlTransformer: transformIndexHtml(nfOptions),
232
+ });
233
+ const rebuildQueue = new RebuildQueue();
234
+ try {
235
+ for await (const output of builderRun) {
236
+ lastResult = output;
237
+ if (!write && output['outputFiles']) {
238
+ memResults.add(output['outputFiles'].map((file) => new EsBuildResult(file)));
239
+ }
240
+ if (!write && output['assetFiles']) {
241
+ memResults.add(output['assetFiles'].map((file) => new NgCliAssetResult(file)));
242
+ }
243
+ // if (write && !runServer && !nfOptions.skipHtmlTransform) {
244
+ // updateIndexHtml(fedOptions, nfOptions);
245
+ // }
246
+ // if (!runServer) {
247
+ // yield output;
248
+ // }
249
+ if (!first && (nfOptions.dev || watch)) {
250
+ rebuildQueue
251
+ .enqueue(async (signal) => {
252
+ if (signal?.aborted) {
253
+ throw new AbortedError('Build canceled before starting');
254
+ }
255
+ await new Promise((resolve, reject) => {
256
+ const timeout = setTimeout(resolve, Math.max(10, nfOptions.rebuildDelay));
257
+ if (signal) {
258
+ const abortHandler = () => {
259
+ clearTimeout(timeout);
260
+ reject(new AbortedError('[builder] During delay.'));
261
+ };
262
+ signal.addEventListener('abort', abortHandler, { once: true });
263
+ }
264
+ });
265
+ if (signal?.aborted) {
266
+ throw new AbortedError('[builder] Before federation build.');
267
+ }
268
+ const start = process.hrtime();
269
+ federationResult = await buildForFederation(config, fedOptions, externals, {
270
+ skipMappingsAndExposed: false,
271
+ skipShared: true,
272
+ signal,
273
+ });
274
+ if (signal?.aborted) {
275
+ throw new AbortedError('[builder] After federation build.');
276
+ }
277
+ if (hasLocales && localeFilter) {
278
+ translateFederationArtefacts(i18n, localeFilter, outputOptions.base, federationResult);
279
+ }
280
+ if (signal?.aborted) {
281
+ throw new AbortedError('[builder] After federation translations.');
282
+ }
283
+ logger.info('Done!');
284
+ if (isLocalDevelopment) {
285
+ federationBuildNotifier.broadcastBuildCompletion();
286
+ }
287
+ logger.measure(start, 'To rebuild the federation artifacts.');
288
+ })
289
+ .catch(error => {
290
+ if (error instanceof AbortedError) {
291
+ logger.verbose('Rebuild was canceled. Cancellation point: ' + error?.message);
292
+ federationBuildNotifier.broadcastBuildCancellation();
293
+ }
294
+ else {
295
+ logger.error('Federation rebuild failed!');
296
+ if (options.verbose)
297
+ console.error(error);
298
+ if (isLocalDevelopment) {
299
+ federationBuildNotifier.broadcastBuildError(error);
300
+ }
301
+ }
302
+ });
303
+ }
304
+ first = false;
305
+ }
306
+ }
307
+ finally {
308
+ rebuildQueue.dispose();
309
+ if (isLocalDevelopment) {
310
+ federationBuildNotifier.stopEventServer();
311
+ }
312
+ }
313
+ yield lastResult || { success: false };
314
+ }
315
+ function removeBaseHref(req, baseHref) {
316
+ let url = req.url ?? '';
317
+ if (baseHref && url.startsWith(baseHref)) {
318
+ url = url.substr(baseHref.length);
319
+ }
320
+ return url;
321
+ }
322
+ function writeFstartScript(fedOptions) {
323
+ const serverOutpath = path.join(fedOptions.outputPath, '../server');
324
+ const fstartPath = path.join(serverOutpath, 'fstart.mjs');
325
+ const buffer = Buffer.from(fstart, 'base64');
326
+ fs.mkdirSync(serverOutpath, { recursive: true });
327
+ fs.writeFileSync(fstartPath, buffer, 'utf-8');
328
+ }
329
+ function getLocaleFilter(options, runServer) {
330
+ let localize = options.localize || false;
331
+ if (runServer && Array.isArray(localize) && localize.length > 1) {
332
+ localize = false;
333
+ }
334
+ if (runServer && localize === true) {
335
+ localize = false;
336
+ }
337
+ return localize;
338
+ }
339
+ function inferConfigPath(tsConfig) {
340
+ const relProjectPath = path.dirname(tsConfig);
341
+ const relConfigPath = path.join(relProjectPath, 'federation.config.js');
342
+ return relConfigPath;
343
+ }
344
+ function transformIndexHtml(nfOptions) {
345
+ return (content) => Promise.resolve(updateScriptTags(content, nfOptions));
346
+ }
347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
+ export default createBuilder(runBuilder);
@@ -0,0 +1,70 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ type NextFunction = (error?: Error) => void;
3
+ type MiddlewareFunction = (req: IncomingMessage, res: ServerResponse, next: NextFunction) => void;
4
+ /**
5
+ * Manages Server-Sent Events for federation hot reload in local development
6
+ * Only active when running in development mode with dev server
7
+ */
8
+ export declare class FederationBuildNotifier {
9
+ private connections;
10
+ private cleanupInterval;
11
+ private isActive;
12
+ private endpoint;
13
+ /**
14
+ * Initializes the SSE reloader for local development
15
+ */
16
+ initialize(endpoint: string): void;
17
+ /**
18
+ * Creates SSE middleware for federation events
19
+ */
20
+ createEventMiddleware(removeBaseHref: (req: IncomingMessage) => string): MiddlewareFunction;
21
+ /**
22
+ * Sets up a new SSE connection
23
+ */
24
+ private _setupSSEConnection;
25
+ /**
26
+ * Removes a connection from the pool
27
+ */
28
+ private _removeConnection;
29
+ /**
30
+ * Broadcasts an event to all connected clients
31
+ * Only works in local development mode
32
+ */
33
+ private _broadcastEvent;
34
+ /**
35
+ * Sends an event to a specific response stream
36
+ */
37
+ private _sendEvent;
38
+ /**
39
+ * Starts periodic cleanup of dead connections
40
+ */
41
+ private startCleanup;
42
+ /**
43
+ * Notifies about successful federation rebuild
44
+ */
45
+ broadcastBuildCompletion(): void;
46
+ /**
47
+ * Notifies about cancellation of a federation rebuild
48
+ */
49
+ broadcastBuildCancellation(): void;
50
+ /**
51
+ * Notifies about failed federation rebuild
52
+ */
53
+ broadcastBuildError(error: unknown): void;
54
+ /**
55
+ * Stops cleanup and closes all connections
56
+ * Should be called when development server stops
57
+ */
58
+ stopEventServer(): void;
59
+ /**
60
+ * Returns the number of active connections
61
+ */
62
+ get activeConnections(): number;
63
+ /**
64
+ * Returns whether the reloader is active
65
+ */
66
+ get isRunning(): boolean;
67
+ }
68
+ export declare const federationBuildNotifier: FederationBuildNotifier;
69
+ export {};
70
+ //# sourceMappingURL=federation-build-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-build-notifier.d.ts","sourceRoot":"","sources":["../../../../src/builders/build/federation-build-notifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAkB5D,KAAK,YAAY,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;AAC5C,KAAK,kBAAkB,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;AAElG;;;GAGG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAqB;IAErC;;OAEG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYzC;;OAEG;IACI,qBAAqB,CAC1B,cAAc,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAC/C,kBAAkB;IAgBrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAyBvB;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAsBpB;;OAEG;IACI,wBAAwB,IAAI,IAAI;IAOvC;;OAEG;IACI,0BAA0B,IAAI,IAAI;IAOzC;;OAEG;IACI,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQhD;;;OAGG;IACI,eAAe,IAAI,IAAI;IAuB9B;;OAEG;IACH,IAAW,iBAAiB,IAAI,MAAM,CAErC;IAED;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;CACF;AAGD,eAAO,MAAM,uBAAuB,yBAAgC,CAAC"}
@@ -0,0 +1,186 @@
1
+ import { BuildNotificationType } from '@nf-beta/runtime';
2
+ import { logger } from '@nf-beta/core/build';
3
+ /**
4
+ * Manages Server-Sent Events for federation hot reload in local development
5
+ * Only active when running in development mode with dev server
6
+ */
7
+ export class FederationBuildNotifier {
8
+ connections = [];
9
+ cleanupInterval = null;
10
+ isActive = false;
11
+ endpoint;
12
+ /**
13
+ * Initializes the SSE reloader for local development
14
+ */
15
+ initialize(endpoint) {
16
+ if (this.isActive) {
17
+ return;
18
+ }
19
+ this.endpoint = endpoint;
20
+ this.isActive = true;
21
+ this.startCleanup();
22
+ logger.info(`[Federation SSE] Local reloader initialized with endpoint ${this.endpoint}`);
23
+ }
24
+ /**
25
+ * Creates SSE middleware for federation events
26
+ */
27
+ createEventMiddleware(removeBaseHref) {
28
+ if (!this.isActive) {
29
+ return (_req, _res, next) => next();
30
+ }
31
+ return (req, res, next) => {
32
+ const url = removeBaseHref(req);
33
+ if (url !== this.endpoint) {
34
+ return next();
35
+ }
36
+ this._setupSSEConnection(req, res);
37
+ };
38
+ }
39
+ /**
40
+ * Sets up a new SSE connection
41
+ */
42
+ _setupSSEConnection(req, res) {
43
+ res.writeHead(200, {
44
+ 'Content-Type': 'text/event-stream',
45
+ 'Cache-Control': 'no-cache',
46
+ Connection: 'keep-alive',
47
+ 'Access-Control-Allow-Origin': '*',
48
+ 'Access-Control-Allow-Headers': 'Cache-Control',
49
+ });
50
+ // Send initial connection event
51
+ this._sendEvent(res, {
52
+ type: 'connected',
53
+ timestamp: Date.now(),
54
+ });
55
+ // Store connection
56
+ const connection = { response: res, request: req };
57
+ this.connections.push(connection);
58
+ // Handle disconnection
59
+ req.on('close', () => this._removeConnection(connection));
60
+ req.on('error', () => this._removeConnection(connection));
61
+ logger.info(`[Federation SSE] Client connected. Active connections: ${this.connections.length}`);
62
+ }
63
+ /**
64
+ * Removes a connection from the pool
65
+ */
66
+ _removeConnection(connection) {
67
+ const index = this.connections.indexOf(connection);
68
+ if (index !== -1) {
69
+ this.connections.splice(index, 1);
70
+ logger.info(`[Federation SSE] Client disconnected. Active connections: ${this.connections.length}`);
71
+ }
72
+ }
73
+ /**
74
+ * Broadcasts an event to all connected clients
75
+ * Only works in local development mode
76
+ */
77
+ _broadcastEvent(event) {
78
+ if (!this.isActive || this.connections.length === 0) {
79
+ return;
80
+ }
81
+ const deadConnections = [];
82
+ this.connections.forEach(connection => {
83
+ try {
84
+ this._sendEvent(connection.response, event);
85
+ }
86
+ catch {
87
+ deadConnections.push(connection);
88
+ }
89
+ });
90
+ // Remove dead connections
91
+ deadConnections.forEach(connection => this._removeConnection(connection));
92
+ if (this.connections.length > 0) {
93
+ logger.info(`[Federation SSE] Event '${event.type}' broadcast to ${this.connections.length} clients`);
94
+ }
95
+ }
96
+ /**
97
+ * Sends an event to a specific response stream
98
+ */
99
+ _sendEvent(res, event) {
100
+ const data = JSON.stringify(event);
101
+ res.write(`data: ${data}\n\n`);
102
+ }
103
+ /**
104
+ * Starts periodic cleanup of dead connections
105
+ */
106
+ startCleanup() {
107
+ if (this.cleanupInterval) {
108
+ return;
109
+ }
110
+ this.cleanupInterval = setInterval(() => {
111
+ const aliveBefore = this.connections.length;
112
+ this.connections = this.connections.filter(connection => !connection.response.destroyed &&
113
+ !connection.request.destroyed &&
114
+ connection.response.writable);
115
+ if (this.connections.length !== aliveBefore) {
116
+ logger.info(`[Federation SSE] Cleaned up ${aliveBefore - this.connections.length} dead connections`);
117
+ }
118
+ }, 30000); // Clean every 30 seconds
119
+ }
120
+ /**
121
+ * Notifies about successful federation rebuild
122
+ */
123
+ broadcastBuildCompletion() {
124
+ this._broadcastEvent({
125
+ type: BuildNotificationType.COMPLETED,
126
+ timestamp: Date.now(),
127
+ });
128
+ }
129
+ /**
130
+ * Notifies about cancellation of a federation rebuild
131
+ */
132
+ broadcastBuildCancellation() {
133
+ this._broadcastEvent({
134
+ type: BuildNotificationType.CANCELLED,
135
+ timestamp: Date.now(),
136
+ });
137
+ }
138
+ /**
139
+ * Notifies about failed federation rebuild
140
+ */
141
+ broadcastBuildError(error) {
142
+ this._broadcastEvent({
143
+ type: BuildNotificationType.ERROR,
144
+ timestamp: Date.now(),
145
+ error: error instanceof Error ? error.message : 'Unknown error',
146
+ });
147
+ }
148
+ /**
149
+ * Stops cleanup and closes all connections
150
+ * Should be called when development server stops
151
+ */
152
+ stopEventServer() {
153
+ if (!this.isActive) {
154
+ return;
155
+ }
156
+ if (this.cleanupInterval) {
157
+ clearInterval(this.cleanupInterval);
158
+ this.cleanupInterval = null;
159
+ }
160
+ this.connections.forEach(connection => {
161
+ try {
162
+ connection.response.end();
163
+ }
164
+ catch {
165
+ // Connection might already be closed
166
+ }
167
+ });
168
+ this.connections = [];
169
+ this.isActive = false;
170
+ logger.info('[Federation SSE] Local reloader disposed');
171
+ }
172
+ /**
173
+ * Returns the number of active connections
174
+ */
175
+ get activeConnections() {
176
+ return this.connections.length;
177
+ }
178
+ /**
179
+ * Returns whether the reloader is active
180
+ */
181
+ get isRunning() {
182
+ return this.isActive;
183
+ }
184
+ }
185
+ // Singleton instance for local development
186
+ export const federationBuildNotifier = new FederationBuildNotifier();
@@ -0,0 +1,21 @@
1
+ import type { JsonObject } from '@angular-devkit/core';
2
+ import type { BuildNotificationOptions } from '@nf-beta/runtime';
3
+ import type { ESMSInitOptions } from 'es-module-shims';
4
+
5
+ export interface NfBuilderSchema extends JsonObject {
6
+ target: string;
7
+ dev: boolean;
8
+ port: number;
9
+ open: boolean;
10
+ rebuildDelay: number;
11
+ buildNotifications?: BuildNotificationOptions;
12
+ shell: string;
13
+ watch: boolean;
14
+ skipHtmlTransform: boolean;
15
+ esmsInitOptions: ESMSInitOptions;
16
+ baseHref?: string;
17
+ outputPath?: string;
18
+ ssr: boolean;
19
+ devServer?: boolean;
20
+ cacheExternalArtifacts?: boolean;
21
+ } // eslint-disable-line