@rimori/client 1.1.10 → 1.2.0

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 (151) hide show
  1. package/README.md +128 -45
  2. package/dist/cli/scripts/init/dev-registration.d.ts +35 -0
  3. package/dist/cli/scripts/init/dev-registration.js +175 -0
  4. package/dist/cli/scripts/init/env-setup.d.ts +9 -0
  5. package/dist/cli/scripts/init/env-setup.js +43 -0
  6. package/dist/cli/scripts/init/file-operations.d.ts +4 -0
  7. package/dist/cli/scripts/init/file-operations.js +51 -0
  8. package/dist/cli/scripts/init/html-cleaner.d.ts +4 -0
  9. package/dist/cli/scripts/init/html-cleaner.js +38 -0
  10. package/dist/cli/scripts/init/main.d.ts +2 -0
  11. package/dist/cli/scripts/init/main.js +159 -0
  12. package/dist/cli/scripts/init/package-setup.d.ts +32 -0
  13. package/dist/cli/scripts/init/package-setup.js +75 -0
  14. package/dist/cli/scripts/init/router-transformer.d.ts +6 -0
  15. package/dist/cli/scripts/init/router-transformer.js +254 -0
  16. package/dist/cli/scripts/init/tailwind-config.d.ts +4 -0
  17. package/dist/cli/scripts/init/tailwind-config.js +56 -0
  18. package/dist/cli/scripts/init/vite-config.d.ts +20 -0
  19. package/dist/cli/scripts/init/vite-config.js +54 -0
  20. package/dist/cli/scripts/release/release-config-upload.d.ts +7 -0
  21. package/dist/cli/scripts/release/release-config-upload.js +116 -0
  22. package/dist/cli/scripts/release/release-db-update.d.ts +6 -0
  23. package/dist/cli/scripts/release/release-db-update.js +100 -0
  24. package/dist/cli/scripts/release/release-file-upload.d.ts +6 -0
  25. package/dist/cli/scripts/release/release-file-upload.js +136 -0
  26. package/dist/cli/scripts/release/release.d.ts +23 -0
  27. package/dist/cli/scripts/release/release.js +70 -0
  28. package/dist/cli/types/DatabaseTypes.d.ts +103 -0
  29. package/dist/cli/types/DatabaseTypes.js +2 -0
  30. package/dist/components/ai/Assistant.js +4 -4
  31. package/dist/components/ai/Avatar.d.ts +3 -2
  32. package/dist/components/ai/Avatar.js +10 -5
  33. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
  34. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +1 -0
  35. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +12 -6
  36. package/dist/components/ai/utils.js +0 -1
  37. package/dist/components/audio/Playbutton.js +3 -3
  38. package/dist/{core → components}/components/ContextMenu.js +2 -2
  39. package/dist/components.d.ts +5 -5
  40. package/dist/components.js +5 -5
  41. package/dist/core/controller/AIController.d.ts +15 -0
  42. package/dist/core/controller/AIController.js +120 -0
  43. package/dist/{controller → core/controller}/ObjectController.d.ts +8 -0
  44. package/dist/{controller → core/controller}/SettingsController.d.ts +12 -4
  45. package/dist/{controller → core/controller}/SettingsController.js +0 -25
  46. package/dist/{controller → core/controller}/SharedContentController.d.ts +1 -1
  47. package/dist/{controller → core/controller}/SharedContentController.js +4 -4
  48. package/dist/core/core.d.ts +13 -0
  49. package/dist/core/core.js +8 -0
  50. package/dist/{plugin/fromRimori → fromRimori}/EventBus.d.ts +3 -3
  51. package/dist/{plugin/fromRimori → fromRimori}/EventBus.js +25 -8
  52. package/dist/fromRimori/PluginTypes.d.ts +171 -0
  53. package/dist/hooks/UseChatHook.d.ts +2 -1
  54. package/dist/hooks/UseChatHook.js +3 -3
  55. package/dist/index.d.ts +5 -3
  56. package/dist/index.js +4 -3
  57. package/dist/plugin/AccomplishmentHandler.d.ts +1 -1
  58. package/dist/plugin/AccomplishmentHandler.js +1 -1
  59. package/dist/plugin/PluginController.d.ts +16 -3
  60. package/dist/plugin/PluginController.js +24 -18
  61. package/dist/plugin/RimoriClient.d.ts +16 -11
  62. package/dist/plugin/RimoriClient.js +35 -25
  63. package/dist/plugin/StandaloneClient.js +11 -8
  64. package/dist/plugin/ThemeSetter.d.ts +1 -0
  65. package/dist/plugin/ThemeSetter.js +9 -6
  66. package/dist/providers/PluginProvider.d.ts +3 -0
  67. package/dist/providers/PluginProvider.js +4 -4
  68. package/dist/utils/Language.d.ts +2 -1
  69. package/dist/utils/Language.js +4 -2
  70. package/dist/utils/difficultyConverter.js +1 -1
  71. package/dist/utils/endpoint.d.ts +2 -0
  72. package/dist/utils/endpoint.js +2 -0
  73. package/dist/worker/WorkerSetup.js +3 -1
  74. package/example/docs/devdocs.md +231 -0
  75. package/example/docs/overview.md +29 -0
  76. package/example/docs/userdocs.md +123 -0
  77. package/example/rimori.config.ts +89 -0
  78. package/example/worker/vite.config.ts +23 -0
  79. package/example/worker/worker.ts +11 -0
  80. package/package.json +15 -9
  81. package/src/cli/scripts/init/dev-registration.ts +193 -0
  82. package/src/cli/scripts/init/env-setup.ts +44 -0
  83. package/src/cli/scripts/init/file-operations.ts +58 -0
  84. package/src/cli/scripts/init/html-cleaner.ts +48 -0
  85. package/src/cli/scripts/init/main.ts +171 -0
  86. package/src/cli/scripts/init/package-setup.ts +117 -0
  87. package/src/cli/scripts/init/router-transformer.ts +329 -0
  88. package/src/cli/scripts/init/tailwind-config.ts +75 -0
  89. package/src/cli/scripts/init/vite-config.ts +73 -0
  90. package/src/cli/scripts/release/release-config-upload.ts +114 -0
  91. package/src/cli/scripts/release/release-db-update.ts +97 -0
  92. package/src/cli/scripts/release/release-file-upload.ts +138 -0
  93. package/src/cli/scripts/release/release.ts +69 -0
  94. package/src/cli/types/DatabaseTypes.ts +117 -0
  95. package/src/components/ai/Assistant.tsx +4 -4
  96. package/src/components/ai/Avatar.tsx +24 -7
  97. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +1 -1
  98. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +16 -8
  99. package/src/components/ai/utils.ts +0 -2
  100. package/src/components/audio/Playbutton.tsx +3 -3
  101. package/src/{core → components}/components/ContextMenu.tsx +3 -3
  102. package/src/components.ts +6 -6
  103. package/src/core/controller/AIController.ts +122 -0
  104. package/src/core/controller/ObjectController.ts +115 -0
  105. package/src/{controller → core/controller}/SettingsController.ts +13 -29
  106. package/src/{controller → core/controller}/SharedContentController.ts +5 -5
  107. package/src/core/core.ts +15 -0
  108. package/src/{plugin/fromRimori → fromRimori}/EventBus.ts +28 -10
  109. package/src/fromRimori/PluginTypes.ts +203 -0
  110. package/src/hooks/UseChatHook.ts +5 -4
  111. package/src/index.ts +5 -3
  112. package/src/plugin/AccomplishmentHandler.ts +1 -1
  113. package/src/plugin/PluginController.ts +35 -23
  114. package/src/plugin/RimoriClient.ts +42 -35
  115. package/src/plugin/StandaloneClient.ts +11 -8
  116. package/src/plugin/ThemeSetter.ts +12 -8
  117. package/src/providers/PluginProvider.tsx +7 -4
  118. package/src/utils/Language.ts +4 -2
  119. package/src/utils/difficultyConverter.ts +3 -3
  120. package/src/utils/endpoint.ts +2 -0
  121. package/src/worker/WorkerSetup.ts +4 -2
  122. package/dist/components/PluginController.d.ts +0 -21
  123. package/dist/components/PluginController.js +0 -116
  124. package/dist/controller/AIController.d.ts +0 -23
  125. package/dist/controller/AIController.js +0 -93
  126. package/dist/controller/SidePluginController.d.ts +0 -3
  127. package/dist/controller/SidePluginController.js +0 -31
  128. package/dist/core.d.ts +0 -7
  129. package/dist/core.js +0 -7
  130. package/dist/plugin/ContextMenu.d.ts +0 -17
  131. package/dist/plugin/ContextMenu.js +0 -45
  132. package/dist/plugin/fromRimori/PluginTypes.d.ts +0 -48
  133. package/dist/plugin/fromRimori/SupabaseHandler.d.ts +0 -13
  134. package/dist/plugin/fromRimori/SupabaseHandler.js +0 -55
  135. package/dist/providers/PluginController.d.ts +0 -21
  136. package/dist/providers/PluginController.js +0 -116
  137. package/dist/types/Actions.d.ts +0 -4
  138. package/dist/types/Actions.js +0 -1
  139. package/src/controller/AIController.ts +0 -112
  140. package/src/controller/ObjectController.ts +0 -107
  141. package/src/controller/SidePluginController.ts +0 -25
  142. package/src/core.ts +0 -8
  143. package/src/plugin/fromRimori/PluginTypes.ts +0 -64
  144. package/src/types/Actions.ts +0 -6
  145. /package/dist/{core → components}/components/ContextMenu.d.ts +0 -0
  146. /package/dist/{controller → core/controller}/ObjectController.js +0 -0
  147. /package/dist/{controller → core/controller}/VoiceController.d.ts +0 -0
  148. /package/dist/{controller → core/controller}/VoiceController.js +0 -0
  149. /package/dist/{plugin/fromRimori → fromRimori}/PluginTypes.js +0 -0
  150. /package/src/{controller → core/controller}/VoiceController.ts +0 -0
  151. /package/src/{plugin/fromRimori → fromRimori}/readme.md +0 -0
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { askForCredentials, askForPort, authenticateWithSupabase, registerDeveloper, } from './dev-registration.js';
14
+ import { setupEnvFile, updateGitignore } from './env-setup.js';
15
+ import { copyPluginFiles } from './file-operations.js';
16
+ import { cleanHtmlMetaTags } from './html-cleaner.js';
17
+ import { updatePackageJson } from './package-setup.js';
18
+ import { transformAppRouter } from './router-transformer.js';
19
+ import { updateTailwindConfig } from './tailwind-config.js';
20
+ import { updateViteConfigBase } from './vite-config.js';
21
+ /**
22
+ * Main function that handles the complete plugin setup flow.
23
+ */
24
+ function main() {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ try {
27
+ // Check for --upgrade flag
28
+ const isUpgrade = process.argv.includes('--upgrade');
29
+ if (isUpgrade) {
30
+ console.log('🔄 Starting Rimori Plugin Upgrade...');
31
+ }
32
+ else {
33
+ console.log('🎯 Starting Rimori Plugin Setup...');
34
+ }
35
+ console.log('');
36
+ // Check if plugin is already initialized (skip for upgrade mode)
37
+ if (!isUpgrade) {
38
+ const packageJsonPath = path.resolve('./package.json');
39
+ if (fs.existsSync(packageJsonPath)) {
40
+ try {
41
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
42
+ if (packageJson.r_id) {
43
+ console.log('❌ Plugin is already initialized!');
44
+ console.log(`Plugin ID: ${packageJson.r_id}`);
45
+ console.log('');
46
+ console.log('If you want to reinitialize the plugin, please remove the "r_id" field from package.json first.');
47
+ console.log('Or use the --upgrade flag to upgrade the plugin configuration without changing the plugin ID.');
48
+ process.exit(0);
49
+ }
50
+ }
51
+ catch (error) {
52
+ console.warn('Warning: Could not read package.json, continuing with setup...');
53
+ }
54
+ }
55
+ }
56
+ let pluginId = '';
57
+ if (isUpgrade) {
58
+ // For upgrade mode, only ask for port and setup plugin
59
+ console.log('🔄 Upgrade mode: Skipping authentication and plugin registration...');
60
+ console.log('');
61
+ // Get plugin ID from existing package.json
62
+ try {
63
+ const packageJsonPath = path.resolve('./package.json');
64
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
65
+ pluginId = packageJson.r_id || '';
66
+ }
67
+ catch (error) {
68
+ console.warn('Warning: Could not read plugin ID from package.json');
69
+ }
70
+ // Ask for development port
71
+ const port = yield askForPort();
72
+ console.log('');
73
+ // Update package.json in upgrade mode
74
+ updatePackageJson({
75
+ port,
76
+ isUpgrade: true,
77
+ });
78
+ // Copy files
79
+ copyPluginFiles();
80
+ // Update gitignore
81
+ updateGitignore();
82
+ }
83
+ else {
84
+ // Step 1: Get user credentials
85
+ const credentials = yield askForCredentials();
86
+ console.log('');
87
+ // Step 2: Authenticate with Supabase
88
+ const jwtToken = yield authenticateWithSupabase(credentials);
89
+ console.log('');
90
+ // Step 3: Ask for development port
91
+ const port = yield askForPort();
92
+ console.log('');
93
+ // Step 4: Register developer and get plugin credentials
94
+ const { plugin_id, access_token } = yield registerDeveloper(jwtToken, port);
95
+ pluginId = plugin_id;
96
+ console.log('');
97
+ // Step 5: Update package.json
98
+ updatePackageJson({
99
+ pluginId: plugin_id,
100
+ port,
101
+ isUpgrade: false,
102
+ });
103
+ // Step 6: Setup environment file
104
+ setupEnvFile(access_token);
105
+ // Step 7: Copy necessary files
106
+ copyPluginFiles();
107
+ // Step 8: Update gitignore
108
+ updateGitignore();
109
+ }
110
+ // Setup vite config base
111
+ try {
112
+ console.log('Updating vite config base...');
113
+ updateViteConfigBase();
114
+ console.log('✅ Vite config base updated');
115
+ }
116
+ catch (error) {
117
+ console.warn(`Warning: Could not update vite.config.ts base property: ${error instanceof Error ? error.message : error}`);
118
+ }
119
+ // Clean meta tags from index.html after vite adaptation
120
+ cleanHtmlMetaTags();
121
+ console.log('✅ Meta tags cleaned from index.html');
122
+ // Update Tailwind CSS configuration
123
+ updateTailwindConfig();
124
+ // Transform App.tsx to use PluginProvider with HashRouter
125
+ if (pluginId) {
126
+ try {
127
+ transformAppRouter(pluginId);
128
+ }
129
+ catch (error) {
130
+ console.warn(`Warning: Could not transform App.tsx router: ${error instanceof Error ? error.message : error}`);
131
+ }
132
+ }
133
+ else {
134
+ console.warn('Warning: Plugin ID not available, skipping router transformation');
135
+ }
136
+ console.log('');
137
+ console.log('✅ Plugin ' + (isUpgrade ? 'upgrade' : 'setup') + ' completed successfully!');
138
+ console.log('');
139
+ console.log('Next steps:');
140
+ console.log('1. Check out ./rimori/readme.md for more information about how to make the most out of the plugin.');
141
+ console.log('2. Adapt the ./rimori/rimori.config.ts file to your needs.');
142
+ console.log('3. Under ./public/docs/ you can find the documentation for an example flashcard plugin to get started easier.');
143
+ console.log('4. Start development with: yarn dev');
144
+ console.log('');
145
+ console.log(`The plugin should now be accessible at: http://localhost:${3000}`);
146
+ console.log('');
147
+ console.log('If you want to release the plugin, simply run: "yarn release:<alpha|beta|stable>" (details are available in ./rimori/readme.md)');
148
+ }
149
+ catch (error) {
150
+ console.error(`❌ Error: ${error instanceof Error ? error.message : error}`);
151
+ console.error('');
152
+ console.error('Make sure that:');
153
+ console.error('1. Your Supabase credentials are correct');
154
+ console.error('2. You have internet connection for authentication');
155
+ process.exit(1);
156
+ }
157
+ });
158
+ }
159
+ main();
@@ -0,0 +1,32 @@
1
+ export interface PackageJson {
2
+ name?: string;
3
+ r_id?: string;
4
+ scripts: {
5
+ dev?: string;
6
+ [key: string]: string | undefined;
7
+ };
8
+ dependencies?: {
9
+ [key: string]: string;
10
+ };
11
+ [key: string]: any;
12
+ }
13
+ export interface PackageSetupParams {
14
+ pluginId?: string;
15
+ port?: number;
16
+ isUpgrade?: boolean;
17
+ }
18
+ /**
19
+ * Gets the current version of @rimori/client from its package.json.
20
+ * @returns The current version string with caret prefix.
21
+ * @throws {Error} if rimori-client package.json cannot be read.
22
+ */
23
+ export declare function getRimoriClientVersion(): string;
24
+ /**
25
+ * Updates the plugin's package.json with necessary configuration.
26
+ * @param param
27
+ * @param param.pluginId - The unique plugin identifier (optional for upgrade mode).
28
+ * @param param.port - The development port for the plugin (optional).
29
+ * @param param.isUpgrade - Whether this is an upgrade operation.
30
+ * @throws {Error} if plugin directory doesn't exist or package.json is missing.
31
+ */
32
+ export declare function updatePackageJson({ pluginId, port, isUpgrade, }: PackageSetupParams): void;
@@ -0,0 +1,75 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ // ES module equivalent of __dirname
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ /**
9
+ * Gets the current version of @rimori/client from its package.json.
10
+ * @returns The current version string with caret prefix.
11
+ * @throws {Error} if rimori-client package.json cannot be read.
12
+ */
13
+ export function getRimoriClientVersion() {
14
+ try {
15
+ // Get the path to rimori-client package.json relative to this script
16
+ // From dist/cli/scripts/init/ we need to go up 4 levels to reach the root
17
+ const rimoriClientPackageJsonPath = path.resolve(__dirname, '../../../../package.json');
18
+ const rimoriClientPackageJson = JSON.parse(fs.readFileSync(rimoriClientPackageJsonPath, 'utf8'));
19
+ return `^${rimoriClientPackageJson.version}`;
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Failed to read rimori-client version: ${error}`);
23
+ }
24
+ }
25
+ /**
26
+ * Updates the plugin's package.json with necessary configuration.
27
+ * @param param
28
+ * @param param.pluginId - The unique plugin identifier (optional for upgrade mode).
29
+ * @param param.port - The development port for the plugin (optional).
30
+ * @param param.isUpgrade - Whether this is an upgrade operation.
31
+ * @throws {Error} if plugin directory doesn't exist or package.json is missing.
32
+ */
33
+ export function updatePackageJson({ pluginId, port, isUpgrade = false, }) {
34
+ // Get the plugin repo name from the current directory
35
+ const pluginRepoName = path.basename(process.cwd());
36
+ console.log(`Setting up plugin: ${pluginRepoName}`);
37
+ if (pluginId) {
38
+ console.log(`Plugin ID: ${pluginId}`);
39
+ }
40
+ if (port) {
41
+ console.log(`Port: ${port}`);
42
+ }
43
+ // Check if package.json exists in plugin directory
44
+ const packageJsonPath = path.resolve('./package.json');
45
+ if (!fs.existsSync(packageJsonPath)) {
46
+ throw new Error(`package.json not found! Are you in the root directory of the plugin?`);
47
+ }
48
+ // Read and parse the existing package.json
49
+ let packageJson;
50
+ try {
51
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
52
+ }
53
+ catch (error) {
54
+ throw new Error(`Failed to read or parse package.json: ${error}`);
55
+ }
56
+ console.log('Updating package.json...');
57
+ console.log('pluginId', pluginId);
58
+ // Get the current rimori-client version
59
+ const rimoriClientVersion = getRimoriClientVersion();
60
+ // Update the package.json object
61
+ packageJson.name = pluginRepoName;
62
+ if (pluginId) {
63
+ packageJson.r_id = pluginId;
64
+ }
65
+ packageJson.scripts = Object.assign(Object.assign({}, packageJson.scripts), { "dev": `vite --port ${port || 3000}`, "build": "yarn run check && vite build", "check": "tsc --project tsconfig.app.json --noEmit --pretty", "release:alpha": "yarn build && yarn rimori-release alpha", "release:beta": "yarn build && yarn rimori-release beta", "release:stable": "yarn build && yarn rimori-release stable", "dev:worker": "VITE_MINIFY=false vite build --watch --config worker/vite.config.ts", "build:worker": "vite build --config worker/vite.config.ts" });
66
+ packageJson.dependencies = Object.assign(Object.assign({}, packageJson.dependencies), { "@rimori/client": rimoriClientVersion });
67
+ // Write the updated package.json back to file
68
+ try {
69
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
70
+ console.log('✅ Successfully updated package.json');
71
+ }
72
+ catch (error) {
73
+ throw new Error(`Failed to update package.json: ${error}`);
74
+ }
75
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Transform App.tsx to use PluginProvider with HashRouter instead of BrowserRouter.
3
+ * Also converts route components to lazy loading with Suspense.
4
+ * @param pluginId - The plugin ID to use in PluginProvider
5
+ */
6
+ export declare function transformAppRouter(pluginId: string): void;
@@ -0,0 +1,254 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ /**
4
+ * Transform App.tsx to use PluginProvider with HashRouter instead of BrowserRouter.
5
+ * Also converts route components to lazy loading with Suspense.
6
+ * @param pluginId - The plugin ID to use in PluginProvider
7
+ */
8
+ export function transformAppRouter(pluginId) {
9
+ const appTsxPath = path.resolve('./src/App.tsx');
10
+ if (!fs.existsSync(appTsxPath)) {
11
+ console.warn('Warning: App.tsx not found, skipping router transformation');
12
+ return;
13
+ }
14
+ let content = fs.readFileSync(appTsxPath, 'utf8');
15
+ // Check if PluginProvider is already applied
16
+ if (content.includes('PluginProvider')) {
17
+ console.log('✅ PluginProvider already applied, skipping router transformation');
18
+ return;
19
+ }
20
+ // Check if BrowserRouter exists
21
+ if (!content.includes('BrowserRouter')) {
22
+ console.log('✅ BrowserRouter not found, skipping router transformation');
23
+ return;
24
+ }
25
+ console.log('🔄 Transforming App.tsx to use PluginProvider with HashRouter...');
26
+ // Step 1: Detect and extract route components
27
+ const routeComponents = detectRouteComponents(content);
28
+ if (routeComponents.length > 0) {
29
+ console.log(`🔄 Found ${routeComponents.length} route components, converting to lazy loading...`);
30
+ // Step 2: Transform imports to lazy loading
31
+ content = transformToLazyImports(content, routeComponents);
32
+ // Step 3: Add Suspense wrapper around Routes
33
+ content = addSuspenseWrapper(content);
34
+ }
35
+ // Step 4: Transform router imports and JSX (existing functionality)
36
+ content = transformImports(content);
37
+ content = transformJSX(content, pluginId);
38
+ // Write the transformed content back
39
+ fs.writeFileSync(appTsxPath, content, 'utf8');
40
+ console.log('✅ App.tsx transformed successfully with lazy loading');
41
+ }
42
+ /**
43
+ * Detects route components in the JSX by parsing Route elements.
44
+ * Looks for patterns like: <Route path="/" element={<ComponentName />} />
45
+ * @param content - The file content to analyze
46
+ * @returns Array of detected route components with their import information
47
+ */
48
+ function detectRouteComponents(content) {
49
+ const routeComponents = [];
50
+ // Regex to match Route elements with component references
51
+ // Matches: <Route ... element={<ComponentName .../>} ... />
52
+ const routeRegex = /<Route[^>]*element=\{\s*<(\w+)[^}]*\}[^>]*\/?>/g;
53
+ let match;
54
+ const componentNames = new Set();
55
+ // Extract all unique component names from routes
56
+ while ((match = routeRegex.exec(content)) !== null) {
57
+ const componentName = match[1];
58
+ componentNames.add(componentName);
59
+ }
60
+ // For each component, find its corresponding import statement
61
+ componentNames.forEach(componentName => {
62
+ const importInfo = findImportForComponent(content, componentName);
63
+ if (importInfo) {
64
+ routeComponents.push({
65
+ componentName,
66
+ importStatement: importInfo.importStatement,
67
+ importPath: importInfo.importPath,
68
+ isDefaultImport: importInfo.isDefaultImport
69
+ });
70
+ }
71
+ });
72
+ return routeComponents;
73
+ }
74
+ /**
75
+ * Finds the import statement for a given component name.
76
+ * Handles both default imports and named imports.
77
+ * @param content - The file content to search
78
+ * @param componentName - The component name to find import for
79
+ * @returns Import information or null if not found
80
+ */
81
+ function findImportForComponent(content, componentName) {
82
+ // Check for default import: import ComponentName from "path"
83
+ const defaultImportRegex = new RegExp(`import\\s+${componentName}\\s+from\\s+["']([^"']+)["'];?`, 'g');
84
+ const defaultMatch = defaultImportRegex.exec(content);
85
+ if (defaultMatch) {
86
+ return {
87
+ importStatement: defaultMatch[0],
88
+ importPath: defaultMatch[1],
89
+ isDefaultImport: true
90
+ };
91
+ }
92
+ // Check for named import: import { ComponentName } from "path"
93
+ const namedImportRegex = /import\s*\{\s*([^}]*)\s*\}\s*from\s*["']([^"']+)["'];?/g;
94
+ let namedMatch;
95
+ while ((namedMatch = namedImportRegex.exec(content)) !== null) {
96
+ const imports = namedMatch[1].split(',').map(imp => imp.trim());
97
+ if (imports.includes(componentName)) {
98
+ return {
99
+ importStatement: namedMatch[0],
100
+ importPath: namedMatch[2],
101
+ isDefaultImport: false
102
+ };
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Transforms regular component imports to lazy imports.
109
+ * Converts: import ComponentName from "./path"
110
+ * To: const ComponentName = lazy(() => import("./path"))
111
+ * @param content - The file content to transform
112
+ * @param routeComponents - Array of route components to transform
113
+ * @returns Transformed content with lazy imports
114
+ */
115
+ function transformToLazyImports(content, routeComponents) {
116
+ let transformedContent = content;
117
+ // Add lazy import if not already present
118
+ if (!content.includes('lazy')) {
119
+ transformedContent = addLazyImport(transformedContent);
120
+ }
121
+ // Transform each route component import
122
+ routeComponents.forEach((component, index) => {
123
+ const { componentName, importStatement, importPath, isDefaultImport } = component;
124
+ // Create lazy import statement
125
+ const lazyImport = isDefaultImport
126
+ ? `const ${componentName} = lazy(() => import("${importPath}"));`
127
+ : `const ${componentName} = lazy(() => import("${importPath}").then(module => ({ default: module.${componentName} })));`;
128
+ // Replace the original import with lazy import
129
+ transformedContent = transformedContent.replace(importStatement, (index === 0 ? '\n' : '') + lazyImport);
130
+ });
131
+ return transformedContent;
132
+ }
133
+ /**
134
+ * Adds lazy import from React if not already present.
135
+ * @param content - The file content to modify
136
+ * @returns Content with lazy import added
137
+ */
138
+ function addLazyImport(content) {
139
+ // Check if React import exists and update it
140
+ const reactImportRegex = /import\s+(?:\*\s+as\s+)?React(?:\s*,\s*\{\s*([^}]*)\s*\})?\s+from\s+["']react["'];?/;
141
+ const reactImportMatch = content.match(reactImportRegex);
142
+ if (reactImportMatch) {
143
+ // React import exists, add lazy to it
144
+ const existingImports = reactImportMatch[1] || '';
145
+ const importList = existingImports.split(',').map(imp => imp.trim()).filter(Boolean);
146
+ if (!importList.includes('lazy')) {
147
+ importList.push('lazy');
148
+ }
149
+ if (!importList.includes('Suspense')) {
150
+ importList.push('Suspense');
151
+ }
152
+ const newImport = importList.length > 0
153
+ ? `import React, { ${importList.join(', ')} } from "react";`
154
+ : `import React from "react";`;
155
+ return content.replace(reactImportMatch[0], newImport);
156
+ }
157
+ else {
158
+ // No React import found, add it
159
+ const firstImportMatch = content.match(/^import.*$/m);
160
+ if (firstImportMatch) {
161
+ return content.replace(firstImportMatch[0], `import React, { lazy, Suspense } from "react";\n${firstImportMatch[0]}`);
162
+ }
163
+ else {
164
+ // No imports found, add at the beginning
165
+ return `import React, { lazy, Suspense } from "react";\n${content}`;
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * Wraps the Routes component with Suspense for lazy loading fallback.
171
+ * Converts: <Routes>...</Routes>
172
+ * To: <Suspense fallback={<div>Loading...</div>}><Routes>...</Routes></Suspense>
173
+ * @param content - The file content to modify
174
+ * @returns Content with Suspense wrapper added
175
+ */
176
+ function addSuspenseWrapper(content) {
177
+ // Check if Suspense is already wrapping Routes
178
+ if (content.includes('<Suspense') && content.match(/<Suspense[^>]*>[\s\S]*<Routes/)) {
179
+ console.log('✅ Suspense wrapper already present');
180
+ return content;
181
+ }
182
+ // Find Routes component and wrap with Suspense
183
+ // Handle both self-closing and regular Routes tags
184
+ const routesRegex = /(<Routes(?:[^>]*)>)([\s\S]*?)(<\/Routes>)/;
185
+ const selfClosingRoutesRegex = /(<Routes[^>]*\/>)/;
186
+ const routesMatch = content.match(routesRegex);
187
+ const selfClosingMatch = content.match(selfClosingRoutesRegex);
188
+ if (routesMatch) {
189
+ // Regular Routes with children
190
+ const [fullMatch, openTag, children, closeTag] = routesMatch;
191
+ const suspenseWrapper = `<Suspense fallback={<div className="flex items-center justify-center min-h-screen"><div className="text-lg">Loading...</div></div>}>\n ${openTag}${children}${closeTag}\n </Suspense>`;
192
+ return content.replace(fullMatch, suspenseWrapper);
193
+ }
194
+ else if (selfClosingMatch) {
195
+ // Self-closing Routes
196
+ const suspenseWrapper = `<Suspense fallback={<div className="flex items-center justify-center min-h-screen"><div className="text-lg">Loading...</div></div>}>\n ${selfClosingMatch[1]}\n </Suspense>`;
197
+ return content.replace(selfClosingMatch[1], suspenseWrapper);
198
+ }
199
+ console.log('✅ Routes component not found, skipping Suspense wrapper');
200
+ return content;
201
+ }
202
+ /**
203
+ * Transform the import statements to include PluginProvider and change BrowserRouter to HashRouter.
204
+ * This is the original functionality for router transformation.
205
+ * @param content - The file content to transform
206
+ * @returns Transformed content with updated imports
207
+ */
208
+ function transformImports(content) {
209
+ // Add PluginProvider import
210
+ if (!content.includes('import') || !content.includes('@rimori/client')) {
211
+ // Add new import line for PluginProvider
212
+ const importMatch = content.match(/^(import.*from\s+["']react["'];?\s*\n)/m);
213
+ if (importMatch) {
214
+ content = content.replace(importMatch[0], `${importMatch[0]}import { PluginProvider } from "@rimori/client";\n`);
215
+ }
216
+ else {
217
+ // If no React import found, add at the beginning
218
+ content = `import { PluginProvider } from "@rimori/client";\n${content}`;
219
+ }
220
+ }
221
+ else {
222
+ // Update existing @rimori/client import to include PluginProvider
223
+ content = content.replace(/import\s*{\s*([^}]*)\s*}\s*from\s*["']@rimori\/client["'];?/, (match, imports) => {
224
+ const importList = imports.split(',').map((imp) => imp.trim()).filter(Boolean);
225
+ if (!importList.includes('PluginProvider')) {
226
+ importList.push('PluginProvider');
227
+ }
228
+ return `import { ${importList.join(', ')} } from "@rimori/client";`;
229
+ });
230
+ }
231
+ // Transform react-router-dom import: replace BrowserRouter with HashRouter
232
+ content = content.replace(/import\s*{\s*([^}]*)\s*}\s*from\s*["']react-router-dom["'];?/, (match, imports) => {
233
+ const importList = imports.split(',').map((imp) => imp.trim()).filter(Boolean);
234
+ const updatedImports = importList.map((imp) => imp === 'BrowserRouter' ? 'HashRouter' : imp);
235
+ return `import { ${updatedImports.join(', ')} } from "react-router-dom";`;
236
+ });
237
+ return content;
238
+ }
239
+ /**
240
+ * Transform the JSX to wrap with PluginProvider and change BrowserRouter to HashRouter.
241
+ * This is the original functionality for router transformation.
242
+ * @param content - The file content to transform
243
+ * @param pluginId - The plugin ID to use in PluginProvider
244
+ * @returns Transformed content with updated JSX
245
+ */
246
+ function transformJSX(content, pluginId) {
247
+ // Replace opening BrowserRouter tag
248
+ content = content.replace(/<BrowserRouter(\s[^>]*)?>/, `<PluginProvider pluginId="${pluginId}">
249
+ <HashRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>`);
250
+ // Replace closing BrowserRouter tag
251
+ content = content.replace(/<\/BrowserRouter>/, `</HashRouter>
252
+ </PluginProvider>`);
253
+ return content;
254
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Updates the tailwind.config.ts file to set darkMode to "class" and add the Rimori client package to content.
3
+ */
4
+ export declare function updateTailwindConfig(): void;
@@ -0,0 +1,56 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ /**
4
+ * Updates the tailwind.config.ts file to set darkMode to "class" and add the Rimori client package to content.
5
+ */
6
+ export function updateTailwindConfig() {
7
+ console.log('Updating Tailwind CSS configuration...');
8
+ const tailwindConfigPath = path.resolve('./tailwind.config.ts');
9
+ if (!fs.existsSync(tailwindConfigPath)) {
10
+ console.log('Warning: tailwind.config.ts not found, skipping Tailwind CSS update');
11
+ return;
12
+ }
13
+ try {
14
+ const configContent = fs.readFileSync(tailwindConfigPath, 'utf8');
15
+ let updatedContent = configContent;
16
+ // Set darkMode to "class" if it exists, otherwise add it
17
+ if (updatedContent.includes('darkMode:')) {
18
+ updatedContent = updatedContent.replace(/darkMode:\s*\[?"[^"]*"?\]?,?/g, 'darkMode: ["class"],');
19
+ }
20
+ else {
21
+ // Add darkMode after the opening brace
22
+ updatedContent = updatedContent.replace(/export default \{/, 'export default {\n darkMode: ["class"],');
23
+ }
24
+ // Add Rimori client package to content array if not already present
25
+ if (!updatedContent.includes('node_modules/@rimori/client')) {
26
+ // Find the content array and add the Rimori client path
27
+ if (updatedContent.includes('content:')) {
28
+ // More precise regex to handle the content array properly
29
+ updatedContent = updatedContent.replace(/(content:\s*\[)([\s\S]*?)(\])/, (match, start, content, end) => {
30
+ // Clean up any existing double commas first
31
+ let cleanContent = content.replace(/,\s*,/g, ',');
32
+ // Remove trailing comma and whitespace
33
+ cleanContent = cleanContent.replace(/,\s*$/, '');
34
+ // Add the new path with proper formatting
35
+ const newPath = '"node_modules/@rimori/client/dist/components/**/*.{js,jsx}"';
36
+ // If content is not empty, add comma before new entry
37
+ if (cleanContent.trim()) {
38
+ return `${start}${cleanContent},\n ${newPath}\n ${end}`;
39
+ }
40
+ else {
41
+ return `${start}\n ${newPath}\n ${end}`;
42
+ }
43
+ });
44
+ }
45
+ else {
46
+ // Add content array if it doesn't exist
47
+ updatedContent = updatedContent.replace(/darkMode: \["class"\],/, 'darkMode: ["class"],\n content: [\n "./src/**/*.{js,jsx,ts,tsx}",\n "node_modules/@rimori/client/dist/components/**/*.{js,jsx}"\n ],');
48
+ }
49
+ }
50
+ fs.writeFileSync(tailwindConfigPath, updatedContent, 'utf8');
51
+ console.log('✅ Tailwind CSS configuration updated');
52
+ }
53
+ catch (error) {
54
+ console.warn(`Warning: Could not update tailwind.config.ts: ${error instanceof Error ? error.message : error}`);
55
+ }
56
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Updates the vite.config.ts file to set the base property.
3
+ * @param param
4
+ * @param param.basePath - The base path to set in vite config (defaults to './')
5
+ * @param param.configPath - Path to the vite.config.ts file (defaults to './vite.config.ts')
6
+ * @throws {Error} if vite.config.ts file is not found or cannot be modified.
7
+ */
8
+ export declare function updateViteConfigBase({ basePath, configPath }?: {
9
+ basePath?: string;
10
+ configPath?: string;
11
+ }): void;
12
+ /**
13
+ * Reads the current base value from vite.config.ts.
14
+ * @param param
15
+ * @param param.configPath - Path to the vite.config.ts file (defaults to './vite.config.ts')
16
+ * @returns The current base value or null if not found.
17
+ */
18
+ export declare function getCurrentViteBase({ configPath }?: {
19
+ configPath?: string;
20
+ }): string | null;
@@ -0,0 +1,54 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ /**
4
+ * Updates the vite.config.ts file to set the base property.
5
+ * @param param
6
+ * @param param.basePath - The base path to set in vite config (defaults to './')
7
+ * @param param.configPath - Path to the vite.config.ts file (defaults to './vite.config.ts')
8
+ * @throws {Error} if vite.config.ts file is not found or cannot be modified.
9
+ */
10
+ export function updateViteConfigBase({ basePath = './', configPath = './vite.config.ts' } = {}) {
11
+ const viteConfigPath = path.resolve(configPath);
12
+ if (!fs.existsSync(viteConfigPath)) {
13
+ throw new Error(`vite.config.ts not found at ${viteConfigPath}`);
14
+ }
15
+ let configContent = fs.readFileSync(viteConfigPath, 'utf8');
16
+ // Check if base property already exists
17
+ const baseRegex = /base:\s*['"][^'"]*['"],?\s*/;
18
+ const hasBase = baseRegex.test(configContent);
19
+ if (hasBase) {
20
+ // Update existing base property
21
+ configContent = configContent.replace(baseRegex, `base: '${basePath}',`);
22
+ console.log(`Updated existing base property in vite.config.ts to '${basePath}'`);
23
+ }
24
+ else {
25
+ // Add base property before server config
26
+ const serverRegex = /(\s*)(server:\s*\{)/;
27
+ const serverMatch = configContent.match(serverRegex);
28
+ if (serverMatch) {
29
+ const indentation = serverMatch[1] || ' '; // Use existing indentation or default to 2 spaces
30
+ const replacement = `${indentation}base: '${basePath}',${indentation}${serverMatch[2]}`;
31
+ configContent = configContent.replace(serverRegex, replacement);
32
+ console.log(`Added base property to vite.config.ts with value '${basePath}'`);
33
+ }
34
+ else {
35
+ throw new Error('Could not find server config in vite.config.ts to add base property before it');
36
+ }
37
+ }
38
+ fs.writeFileSync(viteConfigPath, configContent, 'utf8');
39
+ }
40
+ /**
41
+ * Reads the current base value from vite.config.ts.
42
+ * @param param
43
+ * @param param.configPath - Path to the vite.config.ts file (defaults to './vite.config.ts')
44
+ * @returns The current base value or null if not found.
45
+ */
46
+ export function getCurrentViteBase({ configPath = './vite.config.ts' } = {}) {
47
+ const viteConfigPath = path.resolve(configPath);
48
+ if (!fs.existsSync(viteConfigPath)) {
49
+ return null;
50
+ }
51
+ const configContent = fs.readFileSync(viteConfigPath, 'utf8');
52
+ const baseMatch = configContent.match(/base:\s*['"]([^'"]*)['"]/);
53
+ return baseMatch ? baseMatch[1] : null;
54
+ }