@unisphere/nx 4.14.4 → 4.15.1

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 (23) hide show
  1. package/dist/generators/add-package/templates/new-package/src/languages/README.md +52 -0
  2. package/dist/generators/add-package/templates/new-package/src/languages/en-US.json +1 -0
  3. package/dist/generators/add-package/templates/new-package/src/languages/files/.gitkeep +0 -0
  4. package/dist/generators/add-package/templates/new-package/src/languages/supported-languges.ts +1 -0
  5. package/dist/generators/add-package/templates/new-package/src/lib/use-translation.tsx.template +9 -0
  6. package/dist/generators/add-runtime/templates/new-runtime/src/languages/README.md +32 -0
  7. package/dist/generators/add-runtime/templates/new-runtime/src/languages/en-US.json +1 -0
  8. package/dist/generators/add-runtime/templates/new-runtime/src/languages/files/.gitkeep +0 -0
  9. package/dist/generators/add-runtime/templates/new-runtime/src/languages/supported-languges.ts +1 -0
  10. package/dist/generators/add-runtime/templates/new-runtime/src/lib/runtime.tsx.template +1 -0
  11. package/dist/generators/add-runtime/templates/new-runtime/src/lib/use-translation.tsx.template +9 -0
  12. package/dist/generators/add-visual/templates/new-visual/render-method.template +1 -0
  13. package/dist/migrations/4-15-0/add-languages-support.d.ts +16 -0
  14. package/dist/migrations/4-15-0/add-languages-support.d.ts.map +1 -0
  15. package/dist/migrations/4-15-0/add-languages-support.js +629 -0
  16. package/dist/migrations/4-15-1/templates/_publish-artifacts.template +363 -0
  17. package/dist/migrations/4-15-1/templates/cicd.template +90 -0
  18. package/dist/migrations/4-15-1/templates/documentation-workflow.template +68 -0
  19. package/dist/migrations/4-15-1/upgrade-github-actions-node24.d.ts +17 -0
  20. package/dist/migrations/4-15-1/upgrade-github-actions-node24.d.ts.map +1 -0
  21. package/dist/migrations/4-15-1/upgrade-github-actions-node24.js +80 -0
  22. package/migrations.json +94 -0
  23. package/package.json +1 -1
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ /**
3
+ * Migration: Add Unisphere Languages Support
4
+ *
5
+ * Migrates packages from raw i18next/react-i18next to the Unisphere
6
+ * @unisphere/ui-i18n-react framework with createUseTranslation pattern.
7
+ * Adds UnisphereI18NRuntimeProxy to all runtimes.
8
+ *
9
+ * Steps:
10
+ * 1. For packages with existing en-US.json: move translations, create
11
+ * use-translation.tsx, update imports, remove LanguagesProvider
12
+ * 2. For all runtimes: add UnisphereI18NRuntimeProxy, remove LanguagesProvider usage
13
+ * 3. Update root package.json dependencies
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.default = update;
17
+ const tslib_1 = require("tslib");
18
+ const devkit_1 = require("@nx/devkit");
19
+ const path = tslib_1.__importStar(require("path"));
20
+ async function update(tree) {
21
+ devkit_1.logger.info('🔄 Adding Unisphere languages support...');
22
+ if (!tree.exists('.unisphere')) {
23
+ devkit_1.logger.info('â„šī¸ No .unisphere config found, skipping');
24
+ return;
25
+ }
26
+ const config = (0, devkit_1.readJson)(tree, '.unisphere');
27
+ const packages = config.elements?.packages || {};
28
+ const runtimes = config.elements?.runtimes || {};
29
+ // Step 1: Migrate packages with existing translations
30
+ const migratedPackages = migratePackages(tree, packages);
31
+ // Step 2: Migrate all runtimes
32
+ migrateRuntimes(tree, runtimes, migratedPackages);
33
+ // Step 3: Update root package.json
34
+ updateRootPackageJson(tree);
35
+ await (0, devkit_1.formatFiles)(tree);
36
+ devkit_1.logger.info('✅ Unisphere languages support migration complete');
37
+ }
38
+ // =============================================================================
39
+ // Step 1: Package Migrations
40
+ // =============================================================================
41
+ function migratePackages(tree, packages) {
42
+ const migratedPackages = [];
43
+ for (const [name, config] of Object.entries(packages)) {
44
+ const { sourceRoot } = config;
45
+ if (!tree.exists(sourceRoot)) {
46
+ devkit_1.logger.warn(` âš ī¸ Package "${name}" source root not found: ${sourceRoot}`);
47
+ continue;
48
+ }
49
+ // Find existing en-US.json under this package
50
+ const translationFile = findTranslationFile(tree, sourceRoot);
51
+ // Read package name from package.json
52
+ const packageJsonPath = `${sourceRoot}/package.json`;
53
+ let packageName = `@local/${name}`;
54
+ if (tree.exists(packageJsonPath)) {
55
+ const pkgJson = (0, devkit_1.readJson)(tree, packageJsonPath);
56
+ packageName = pkgJson.name || packageName;
57
+ }
58
+ devkit_1.logger.info(` đŸ“Ļ Migrating package "${name}" (${packageName})...`);
59
+ // 1a. Move translation file to src/languages/en-US.json or create empty one
60
+ const targetTranslationPath = `${sourceRoot}/src/languages/en-US.json`;
61
+ if (translationFile) {
62
+ moveTranslationFile(tree, translationFile, targetTranslationPath);
63
+ }
64
+ else {
65
+ // Create empty en-US.json if none existed
66
+ if (!tree.exists(targetTranslationPath)) {
67
+ tree.write(targetTranslationPath, '{}');
68
+ devkit_1.logger.info(` ✅ Created empty ${targetTranslationPath}`);
69
+ }
70
+ }
71
+ // 1b. Create _db/.gitignore
72
+ const dbGitignorePath = `${sourceRoot}/src/languages/_db/.gitignore`;
73
+ if (!tree.exists(dbGitignorePath)) {
74
+ tree.write(dbGitignorePath, '');
75
+ }
76
+ // 1c. Create files/.gitkeep
77
+ const filesGitkeepPath = `${sourceRoot}/src/languages/files/.gitkeep`;
78
+ if (!tree.exists(filesGitkeepPath)) {
79
+ tree.write(filesGitkeepPath, '');
80
+ }
81
+ // 1d. Create supported-languges.ts
82
+ const supportedLanguagesPath = `${sourceRoot}/src/languages/supported-languges.ts`;
83
+ createSupportedLanguagesFile(tree, supportedLanguagesPath);
84
+ // 1e. Create use-translation.tsx (if not already present)
85
+ const useTranslationPath = `${sourceRoot}/src/lib/use-translation.tsx`;
86
+ createUseTranslationFile(tree, useTranslationPath, packageName);
87
+ // 1f. Create README.md in languages folder
88
+ const readmePath = `${sourceRoot}/src/languages/README.md`;
89
+ createLanguagesReadme(tree, readmePath);
90
+ // 1g. Update useTranslation imports in package files
91
+ updateUseTranslationImports(tree, sourceRoot, useTranslationPath);
92
+ // 1h. Remove LanguagesProvider usage from package files
93
+ removeLanguagesProviderUsage(tree, sourceRoot);
94
+ // 1i. Delete languages-provider.tsx
95
+ deleteLanguagesProvider(tree, sourceRoot);
96
+ // 1j. Update barrel export
97
+ updateBarrelExport(tree, sourceRoot);
98
+ // 1k. Export useTranslation from barrel for runtime consumption
99
+ exportUseTranslationFromBarrel(tree, sourceRoot);
100
+ migratedPackages.push({
101
+ name,
102
+ sourceRoot,
103
+ packageName,
104
+ translationFilePath: targetTranslationPath,
105
+ });
106
+ }
107
+ if (migratedPackages.length === 0) {
108
+ devkit_1.logger.info('â„šī¸ No packages with translations found');
109
+ }
110
+ else {
111
+ devkit_1.logger.info(` ✅ Migrated ${migratedPackages.length} package(s) with translations`);
112
+ }
113
+ return migratedPackages;
114
+ }
115
+ function findTranslationFile(tree, sourceRoot) {
116
+ let found = null;
117
+ (0, devkit_1.visitNotIgnoredFiles)(tree, sourceRoot, (filePath) => {
118
+ if (found)
119
+ return;
120
+ if (filePath.endsWith('/en-US.json') && filePath.includes('/languages/')) {
121
+ found = filePath;
122
+ }
123
+ });
124
+ // Fallback: look for en-US.json anywhere under src
125
+ if (!found) {
126
+ (0, devkit_1.visitNotIgnoredFiles)(tree, sourceRoot, (filePath) => {
127
+ if (found)
128
+ return;
129
+ if (filePath.endsWith('/en-US.json')) {
130
+ found = filePath;
131
+ }
132
+ });
133
+ }
134
+ return found;
135
+ }
136
+ function moveTranslationFile(tree, from, to) {
137
+ if (from === to)
138
+ return;
139
+ if (tree.exists(to)) {
140
+ devkit_1.logger.info(` â„šī¸ Translation file already at target: ${to}`);
141
+ return;
142
+ }
143
+ const content = tree.read(from, 'utf-8');
144
+ if (content) {
145
+ tree.write(to, content);
146
+ tree.delete(from);
147
+ devkit_1.logger.info(` 📁 Moved ${from} → ${to}`);
148
+ }
149
+ }
150
+ function createSupportedLanguagesFile(tree, filePath) {
151
+ if (tree.exists(filePath)) {
152
+ devkit_1.logger.info(` â„šī¸ supported-languges.ts already exists`);
153
+ return;
154
+ }
155
+ const content = `export const supportedLanguages: string[] = ['en-US'];\n`;
156
+ tree.write(filePath, content);
157
+ devkit_1.logger.info(` ✅ Created ${filePath}`);
158
+ }
159
+ function createUseTranslationFile(tree, filePath, packageName) {
160
+ if (tree.exists(filePath)) {
161
+ devkit_1.logger.info(` â„šī¸ use-translation.tsx already exists`);
162
+ return;
163
+ }
164
+ const content = `import en from '../languages/en-US.json';
165
+ import { createUseTranslation } from '@unisphere/ui-i18n-react';
166
+ import { supportedLanguages } from '../languages/supported-languges';
167
+
168
+ export const useTranslation = createUseTranslation({
169
+ packageName: '${packageName}',
170
+ defaultTranslations: en as any,
171
+ supportedLanguages,
172
+ });
173
+ `;
174
+ tree.write(filePath, content);
175
+ devkit_1.logger.info(` ✅ Created ${filePath}`);
176
+ }
177
+ function createLanguagesReadme(tree, filePath) {
178
+ if (tree.exists(filePath)) {
179
+ return;
180
+ }
181
+ const content = `# Languages
182
+
183
+ This folder contains the internationalization (i18n) setup for this package.
184
+
185
+ ## Structure
186
+
187
+ \`\`\`
188
+ languages/
189
+ ├── en-US.json # Default English translations (source of truth)
190
+ ├── supported-languges.ts # List of supported language codes
191
+ ├── files/ # Translated language files (e.g., de-DE.json, fr-FR.json)
192
+ │ └── .gitkeep
193
+ ├── _db/ # Translation database for change tracking (git-ignored)
194
+ │ └── .gitignore
195
+ └── README.md # This file
196
+ \`\`\`
197
+
198
+ ## How It Works
199
+
200
+ - **\`en-US.json\`** — The master translation file. All keys are defined here in English.
201
+ - **\`supported-languges.ts\`** — Exports the list of supported language codes. Add new languages here when translations are available.
202
+ - **\`files/\`** — Contains translated JSON files for each supported language (excluding en-US). These are generated via the Unisphere CLI export/import workflow.
203
+ - **\`_db/\`** — Used internally by the CLI to track translation changes between exports. Do not commit.
204
+
205
+ ## Adding Translations
206
+
207
+ 1. Add your translation keys to \`en-US.json\`
208
+ 2. Run \`npx unisphere languages export\` to generate CSV files for translators
209
+ 3. After receiving translations, run \`npx unisphere languages import\` to update the \`files/\` directory
210
+ 4. Add the new language code to \`supported-languges.ts\`
211
+
212
+ ## Usage
213
+
214
+ Components in this package use translations via the \`useTranslation\` hook:
215
+
216
+ \`\`\`typescript
217
+ import { useTranslation } from './use-translation';
218
+
219
+ const MyComponent = () => {
220
+ const { t } = useTranslation();
221
+ return <span>{t('myKey')}</span>;
222
+ };
223
+ \`\`\`
224
+
225
+ ## CLI Commands
226
+
227
+ \`\`\`bash
228
+ npx unisphere languages export # Export translations to CSV for translation services
229
+ npx unisphere languages import # Import translated CSV files back to JSON
230
+ npx unisphere languages serve # Serve language files during local development
231
+ npx unisphere languages bundle # Bundle language files for distribution
232
+ \`\`\`
233
+ `;
234
+ tree.write(filePath, content);
235
+ devkit_1.logger.info(` ✅ Created ${filePath}`);
236
+ }
237
+ function updateUseTranslationImports(tree, sourceRoot, useTranslationPath) {
238
+ let updatedCount = 0;
239
+ (0, devkit_1.visitNotIgnoredFiles)(tree, sourceRoot, (filePath) => {
240
+ if (!filePath.endsWith('.ts') && !filePath.endsWith('.tsx'))
241
+ return;
242
+ const content = tree.read(filePath, 'utf-8');
243
+ if (!content)
244
+ return;
245
+ // Match: import { useTranslation } from 'react-i18next';
246
+ const reactI18nextImport = /import\s*\{\s*useTranslation\s*\}\s*from\s*['"]react-i18next['"];?\s*\n?/;
247
+ if (!reactI18nextImport.test(content))
248
+ return;
249
+ // Skip the languages-provider.tsx file itself (will be deleted)
250
+ if (filePath.includes('languages-provider'))
251
+ return;
252
+ // Calculate relative path from this file to use-translation.tsx
253
+ const fileDir = path.dirname(filePath);
254
+ let relativePath = path.relative(fileDir, useTranslationPath);
255
+ // Remove .tsx extension for import
256
+ relativePath = relativePath.replace(/\.tsx$/, '');
257
+ // Ensure it starts with ./
258
+ if (!relativePath.startsWith('.')) {
259
+ relativePath = './' + relativePath;
260
+ }
261
+ const updatedContent = content.replace(reactI18nextImport, `import { useTranslation } from '${relativePath}';\n`);
262
+ if (updatedContent !== content) {
263
+ tree.write(filePath, updatedContent);
264
+ updatedCount++;
265
+ }
266
+ });
267
+ if (updatedCount > 0) {
268
+ devkit_1.logger.info(` ✅ Updated useTranslation imports in ${updatedCount} file(s)`);
269
+ }
270
+ }
271
+ function removeLanguagesProviderUsage(tree, sourceRoot) {
272
+ let updatedCount = 0;
273
+ (0, devkit_1.visitNotIgnoredFiles)(tree, sourceRoot, (filePath) => {
274
+ if (!filePath.endsWith('.ts') && !filePath.endsWith('.tsx'))
275
+ return;
276
+ if (filePath.includes('languages-provider'))
277
+ return;
278
+ const content = tree.read(filePath, 'utf-8');
279
+ if (!content)
280
+ return;
281
+ if (!content.includes('LanguagesProvider'))
282
+ return;
283
+ let updatedContent = content;
284
+ // Remove LanguagesProvider import (handles various import patterns)
285
+ // Pattern 1: Standalone import
286
+ updatedContent = updatedContent.replace(/import\s*\{\s*LanguagesProvider\s*\}\s*from\s*['"][^'"]+['"];?\s*\n?/g, '');
287
+ // Pattern 2: Part of a multi-import (e.g., import { X, LanguagesProvider, Y } from ...)
288
+ updatedContent = updatedContent.replace(/,\s*LanguagesProvider\s*/g, '');
289
+ updatedContent = updatedContent.replace(/LanguagesProvider\s*,\s*/g, '');
290
+ // Remove <LanguagesProvider> wrapping (keep children)
291
+ // Handle: <LanguagesProvider>\n...children...\n</LanguagesProvider>
292
+ updatedContent = updatedContent.replace(/\s*<LanguagesProvider>\s*\n?/g, '');
293
+ updatedContent = updatedContent.replace(/\s*<\/LanguagesProvider>\s*\n?/g, '');
294
+ if (updatedContent !== content) {
295
+ tree.write(filePath, updatedContent);
296
+ updatedCount++;
297
+ }
298
+ });
299
+ if (updatedCount > 0) {
300
+ devkit_1.logger.info(` ✅ Removed LanguagesProvider from ${updatedCount} file(s)`);
301
+ }
302
+ }
303
+ function deleteLanguagesProvider(tree, sourceRoot) {
304
+ const possiblePaths = [
305
+ `${sourceRoot}/src/lib/providers/languages-provider.tsx`,
306
+ `${sourceRoot}/src/lib/providers/languages-provider.ts`,
307
+ ];
308
+ for (const filePath of possiblePaths) {
309
+ if (tree.exists(filePath)) {
310
+ tree.delete(filePath);
311
+ devkit_1.logger.info(` đŸ—‘ī¸ Deleted ${filePath}`);
312
+ }
313
+ }
314
+ }
315
+ function updateBarrelExport(tree, sourceRoot) {
316
+ const indexPath = `${sourceRoot}/src/index.ts`;
317
+ if (!tree.exists(indexPath))
318
+ return;
319
+ const content = tree.read(indexPath, 'utf-8');
320
+ if (!content)
321
+ return;
322
+ // Remove export of languages-provider
323
+ const updatedContent = content.replace(/export\s*\*\s*from\s*['"]\.\/lib\/providers\/languages-provider['"];?\s*\n?/g, '');
324
+ if (updatedContent !== content) {
325
+ tree.write(indexPath, updatedContent);
326
+ devkit_1.logger.info(` ✅ Removed LanguagesProvider from barrel export`);
327
+ }
328
+ }
329
+ function exportUseTranslationFromBarrel(tree, sourceRoot) {
330
+ const indexPath = `${sourceRoot}/src/index.ts`;
331
+ if (!tree.exists(indexPath))
332
+ return;
333
+ const content = tree.read(indexPath, 'utf-8');
334
+ if (!content)
335
+ return;
336
+ // Check if already exported
337
+ if (content.includes('use-translation'))
338
+ return;
339
+ const exportLine = `export { useTranslation } from './lib/use-translation';\n`;
340
+ const updatedContent = content + exportLine;
341
+ tree.write(indexPath, updatedContent);
342
+ devkit_1.logger.info(` ✅ Added useTranslation to barrel export`);
343
+ }
344
+ // =============================================================================
345
+ // Step 2: Runtime Migrations
346
+ // =============================================================================
347
+ function migrateRuntimes(tree, runtimes, migratedPackages) {
348
+ let updatedCount = 0;
349
+ for (const [name, config] of Object.entries(runtimes)) {
350
+ const { sourceRoot } = config;
351
+ if (!tree.exists(sourceRoot)) {
352
+ devkit_1.logger.warn(` âš ī¸ Runtime "${name}" source root not found: ${sourceRoot}`);
353
+ continue;
354
+ }
355
+ const runtimeFilePath = `${sourceRoot}/src/lib/runtime.tsx`;
356
+ if (!tree.exists(runtimeFilePath)) {
357
+ // Try alternative patterns
358
+ const altPath = `${sourceRoot}/src/lib/runtime.ts`;
359
+ if (!tree.exists(altPath)) {
360
+ devkit_1.logger.warn(` âš ī¸ Runtime "${name}" has no runtime.tsx at ${runtimeFilePath}`);
361
+ continue;
362
+ }
363
+ }
364
+ devkit_1.logger.info(` ⚡ Migrating runtime "${name}"...`);
365
+ // 2a. Create src/languages folder structure for runtime
366
+ createRuntimeLanguagesFolder(tree, sourceRoot);
367
+ // 2b. Create use-translation.tsx for runtime (uses 'self' as packageName)
368
+ const runtimeUseTranslationPath = `${sourceRoot}/src/lib/use-translation.tsx`;
369
+ createUseTranslationFile(tree, runtimeUseTranslationPath, 'self');
370
+ // 2c. Add UnisphereI18NRuntimeProxy to runtime.tsx
371
+ addRuntimeProxy(tree, runtimeFilePath);
372
+ // 2d. Remove LanguagesProvider from runtime
373
+ removeLanguagesProviderFromRuntime(tree, runtimeFilePath);
374
+ // 2e. Update useTranslation imports in runtime files
375
+ updateRuntimeUseTranslationImports(tree, sourceRoot, migratedPackages);
376
+ updatedCount++;
377
+ }
378
+ if (updatedCount > 0) {
379
+ devkit_1.logger.info(` ✅ Migrated ${updatedCount} runtime(s)`);
380
+ }
381
+ }
382
+ function createRuntimeLanguagesFolder(tree, sourceRoot) {
383
+ const langDir = `${sourceRoot}/src/languages`;
384
+ // en-US.json
385
+ if (!tree.exists(`${langDir}/en-US.json`)) {
386
+ tree.write(`${langDir}/en-US.json`, '{}');
387
+ }
388
+ // supported-languges.ts
389
+ if (!tree.exists(`${langDir}/supported-languges.ts`)) {
390
+ tree.write(`${langDir}/supported-languges.ts`, `export const supportedLanguages: string[] = ['en-US'];\n`);
391
+ }
392
+ // files/.gitkeep
393
+ if (!tree.exists(`${langDir}/files/.gitkeep`)) {
394
+ tree.write(`${langDir}/files/.gitkeep`, '');
395
+ }
396
+ // _db/.gitignore
397
+ if (!tree.exists(`${langDir}/_db/.gitignore`)) {
398
+ tree.write(`${langDir}/_db/.gitignore`, '');
399
+ }
400
+ // README.md
401
+ if (!tree.exists(`${langDir}/README.md`)) {
402
+ createLanguagesReadme(tree, `${langDir}/README.md`);
403
+ }
404
+ }
405
+ function addRuntimeProxy(tree, runtimeFilePath) {
406
+ if (!tree.exists(runtimeFilePath))
407
+ return;
408
+ let content = tree.read(runtimeFilePath, 'utf-8');
409
+ if (!content)
410
+ return;
411
+ // Check if already has the proxy
412
+ if (content.includes('UnisphereI18NRuntimeProxy')) {
413
+ devkit_1.logger.info(` â„šī¸ UnisphereI18NRuntimeProxy already present`);
414
+ return;
415
+ }
416
+ // Add import for UnisphereI18NRuntimeProxy
417
+ content = addI18nProxyImport(content);
418
+ // Add the proxy component inside each render method's ScopedUnisphereWorkspaceProvider
419
+ content = insertProxyIntoRenderMethods(content);
420
+ tree.write(runtimeFilePath, content);
421
+ devkit_1.logger.info(` ✅ Added UnisphereI18NRuntimeProxy to runtime`);
422
+ }
423
+ function addI18nProxyImport(content) {
424
+ // Check if @unisphere/ui-i18n-react is already imported
425
+ if (content.includes('@unisphere/ui-i18n-react')) {
426
+ // Add UnisphereI18NRuntimeProxy to existing import
427
+ return content.replace(/(import\s*\{[^}]*)(}\s*from\s*['"]@unisphere\/ui-i18n-react['"])/, (_, start, end) => {
428
+ if (start.includes('UnisphereI18NRuntimeProxy'))
429
+ return start + end;
430
+ return `${start.trimEnd()}, UnisphereI18NRuntimeProxy ${end}`;
431
+ });
432
+ }
433
+ // Add new import after existing @unisphere imports or at the top
434
+ const unisphereImportPattern = /import\s*\{[^}]*\}\s*from\s*['"]@unisphere\/[^'"]+['"];?\s*\n/g;
435
+ let lastMatch = null;
436
+ let match;
437
+ while ((match = unisphereImportPattern.exec(content)) !== null) {
438
+ lastMatch = match;
439
+ }
440
+ const importStatement = `import { UnisphereI18NRuntimeProxy } from '@unisphere/ui-i18n-react';\n`;
441
+ if (lastMatch) {
442
+ const insertPos = lastMatch.index + lastMatch[0].length;
443
+ return (content.slice(0, insertPos) + importStatement + content.slice(insertPos));
444
+ }
445
+ // Fallback: add after all imports
446
+ const lastImportMatch = content.match(/^import\s.+from\s+['"][^'"]+['"];?\s*$/gm);
447
+ if (lastImportMatch) {
448
+ const lastImport = lastImportMatch[lastImportMatch.length - 1];
449
+ const lastImportEnd = content.lastIndexOf(lastImport) + lastImport.length;
450
+ return (content.slice(0, lastImportEnd) +
451
+ '\n' +
452
+ importStatement +
453
+ content.slice(lastImportEnd));
454
+ }
455
+ return importStatement + content;
456
+ }
457
+ function insertProxyIntoRenderMethods(content) {
458
+ // Strategy: Find each render method's JSX tree and insert the proxy
459
+ // after the first <ScopedUnisphereWorkspaceProvider ...> opening tag.
460
+ // This works whether ScopedProvider wraps ThemeProvider or is inside it.
461
+ //
462
+ // Pattern we're looking for:
463
+ // <ScopedUnisphereWorkspaceProvider
464
+ // runtimeLogger={...}
465
+ // unisphereWorkspace={...}
466
+ // >
467
+ // <-- INSERT PROXY HERE
468
+ //
469
+ // We find each occurrence of this multi-line opening tag and insert after it.
470
+ const lines = content.split('\n');
471
+ const result = [];
472
+ let i = 0;
473
+ while (i < lines.length) {
474
+ result.push(lines[i]);
475
+ // Detect start of ScopedUnisphereWorkspaceProvider opening tag
476
+ if (lines[i].includes('<ScopedUnisphereWorkspaceProvider')) {
477
+ // Find the closing > of this opening tag (may be on same line or subsequent lines)
478
+ let closingLine = i;
479
+ // Check if tag closes on same line (self-closing or inline)
480
+ if (lines[i].includes('>') && !lines[i].includes('</')) {
481
+ closingLine = i;
482
+ }
483
+ else {
484
+ // Look forward for the closing >
485
+ for (let j = i + 1; j < lines.length && j < i + 10; j++) {
486
+ result.push(lines[j]);
487
+ closingLine = j;
488
+ if (lines[j].trim() === '>' || lines[j].trimEnd().endsWith('>')) {
489
+ break;
490
+ }
491
+ }
492
+ i = closingLine;
493
+ }
494
+ // Determine indentation for the proxy (one level deeper than the closing >)
495
+ const closingIndent = lines[closingLine].match(/^(\s*)/)?.[1] || '';
496
+ const proxyIndent = closingIndent + ' ';
497
+ // Insert the proxy component
498
+ result.push(`${proxyIndent}<UnisphereI18NRuntimeProxy`);
499
+ result.push(`${proxyIndent} runtimeUri={this._options.runtimeDeployedPath}`);
500
+ result.push(`${proxyIndent}/>`);
501
+ }
502
+ i++;
503
+ }
504
+ return result.join('\n');
505
+ }
506
+ function removeLanguagesProviderFromRuntime(tree, runtimeFilePath) {
507
+ if (!tree.exists(runtimeFilePath))
508
+ return;
509
+ const content = tree.read(runtimeFilePath, 'utf-8');
510
+ if (!content)
511
+ return;
512
+ if (!content.includes('LanguagesProvider'))
513
+ return;
514
+ let updatedContent = content;
515
+ // Remove LanguagesProvider from import statement
516
+ // Pattern: import { X, LanguagesProvider, Y } from '...'
517
+ // Or standalone: import { LanguagesProvider } from '...'
518
+ // Check if it's the only named import
519
+ const standaloneImport = /import\s*\{\s*LanguagesProvider\s*\}\s*from\s*['"][^'"]+['"];?\s*\n?/g;
520
+ if (standaloneImport.test(updatedContent)) {
521
+ updatedContent = updatedContent.replace(standaloneImport, '');
522
+ }
523
+ else {
524
+ // Part of multi-import: remove with surrounding comma
525
+ updatedContent = updatedContent.replace(/,\s*\n?\s*LanguagesProvider/g, '');
526
+ updatedContent = updatedContent.replace(/LanguagesProvider\s*,\s*\n?\s*/g, '');
527
+ }
528
+ // Remove <LanguagesProvider> and </LanguagesProvider> JSX wrapping
529
+ updatedContent = updatedContent.replace(/(\s*)<LanguagesProvider>\s*\n?/g, '');
530
+ updatedContent = updatedContent.replace(/(\s*)<\/LanguagesProvider>\s*\n?/g, '');
531
+ if (updatedContent !== content) {
532
+ tree.write(runtimeFilePath, updatedContent);
533
+ devkit_1.logger.info(` ✅ Removed LanguagesProvider from runtime`);
534
+ }
535
+ }
536
+ function updateRuntimeUseTranslationImports(tree, runtimeSourceRoot, migratedPackages) {
537
+ if (migratedPackages.length === 0)
538
+ return;
539
+ // Determine which package this runtime uses by checking the main runtime.tsx file
540
+ // for imports from any of our migrated packages
541
+ let targetPackage = null;
542
+ const runtimeFilePath = `${runtimeSourceRoot}/src/lib/runtime.tsx`;
543
+ if (tree.exists(runtimeFilePath)) {
544
+ const runtimeContent = tree.read(runtimeFilePath, 'utf-8') || '';
545
+ for (const pkg of migratedPackages) {
546
+ if (runtimeContent.includes(pkg.packageName)) {
547
+ targetPackage = pkg;
548
+ break;
549
+ }
550
+ }
551
+ }
552
+ // If runtime doesn't import from any migrated package, check all source files
553
+ if (!targetPackage) {
554
+ (0, devkit_1.visitNotIgnoredFiles)(tree, runtimeSourceRoot, (filePath) => {
555
+ if (targetPackage)
556
+ return;
557
+ if (!filePath.endsWith('.ts') && !filePath.endsWith('.tsx'))
558
+ return;
559
+ const content = tree.read(filePath, 'utf-8');
560
+ if (!content)
561
+ return;
562
+ for (const pkg of migratedPackages) {
563
+ if (content.includes(pkg.packageName)) {
564
+ targetPackage = pkg;
565
+ break;
566
+ }
567
+ }
568
+ });
569
+ }
570
+ // Last resort: use first migrated package
571
+ if (!targetPackage) {
572
+ targetPackage = migratedPackages[0];
573
+ }
574
+ let updatedCount = 0;
575
+ (0, devkit_1.visitNotIgnoredFiles)(tree, runtimeSourceRoot, (filePath) => {
576
+ if (!filePath.endsWith('.ts') && !filePath.endsWith('.tsx'))
577
+ return;
578
+ const content = tree.read(filePath, 'utf-8');
579
+ if (!content)
580
+ return;
581
+ const reactI18nextImport = /import\s*\{\s*useTranslation\s*\}\s*from\s*['"]react-i18next['"];?\s*\n?/;
582
+ if (!reactI18nextImport.test(content))
583
+ return;
584
+ const updatedContent = content.replace(reactI18nextImport, `import { useTranslation } from '${targetPackage.packageName}';\n`);
585
+ if (updatedContent !== content) {
586
+ tree.write(filePath, updatedContent);
587
+ updatedCount++;
588
+ }
589
+ });
590
+ if (updatedCount > 0) {
591
+ devkit_1.logger.info(` ✅ Updated useTranslation imports in ${updatedCount} runtime file(s)`);
592
+ }
593
+ }
594
+ // =============================================================================
595
+ // Step 3: Root package.json updates
596
+ // =============================================================================
597
+ function updateRootPackageJson(tree) {
598
+ if (!tree.exists('package.json'))
599
+ return;
600
+ const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
601
+ const deps = packageJson.dependencies || {};
602
+ const devDeps = packageJson.devDependencies || {};
603
+ let changed = false;
604
+ // Remove i18next and react-i18next
605
+ if (deps['i18next']) {
606
+ delete deps['i18next'];
607
+ changed = true;
608
+ devkit_1.logger.info(' đŸ—‘ī¸ Removed i18next from dependencies');
609
+ }
610
+ if (deps['react-i18next']) {
611
+ delete deps['react-i18next'];
612
+ changed = true;
613
+ devkit_1.logger.info(' đŸ—‘ī¸ Removed react-i18next from dependencies');
614
+ }
615
+ if (devDeps['i18next']) {
616
+ delete devDeps['i18next'];
617
+ changed = true;
618
+ }
619
+ if (devDeps['react-i18next']) {
620
+ delete devDeps['react-i18next'];
621
+ changed = true;
622
+ }
623
+ if (changed) {
624
+ packageJson.dependencies = deps;
625
+ packageJson.devDependencies = devDeps;
626
+ (0, devkit_1.writeJson)(tree, 'package.json', packageJson);
627
+ devkit_1.logger.info(' ✅ Updated root package.json dependencies');
628
+ }
629
+ }