@rimori/client 2.5.47 → 2.5.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,7 +92,7 @@ npx @rimori/client rimori-init --upgrade # refresh config without changing the
92
92
  ### `rimori-release`
93
93
 
94
94
  - Builds (optionally) and uploads the plugin bundle to Rimori.
95
- - Updates release metadata, database migrations, and activates the chosen channel (`alpha`, `beta`, `stable`).
95
+ - Updates release metadata, database migrations, and activates the chosen channel (`alpha`, `stable`).
96
96
 
97
97
  Usage:
98
98
 
@@ -101,7 +101,7 @@ pnpm build
101
101
  npx @rimori/client rimori-release alpha
102
102
  ```
103
103
 
104
- During initialization, convenience scripts (`release:alpha`, `release:beta`, `release:stable`) are added to your project automatically.
104
+ During initialization, convenience scripts (`release:alpha`, `release:stable`) are added to your project automatically.
105
105
 
106
106
  ## Runtime API
107
107
 
@@ -136,7 +136,7 @@ async function main() {
136
136
  console.log('');
137
137
  console.log(`The plugin should now be accessible at: http://localhost:${3000}`);
138
138
  console.log('');
139
- console.log('If you want to release the plugin, simply run: "pnpm release:<alpha|beta|stable>" (details are available in ./rimori/readme.md)');
139
+ console.log('If you want to release the plugin, simply run: "pnpm release:<alpha|stable>" (details are available in ./rimori/readme.md)');
140
140
  }
141
141
  catch (error) {
142
142
  console.error(`❌ Error: ${error instanceof Error ? error.message : error}`);
@@ -68,7 +68,6 @@ export function updatePackageJson({ pluginId, port, isUpgrade = false }) {
68
68
  build: 'pnpm check && vite build',
69
69
  check: 'tsc --project tsconfig.app.json --noEmit --pretty',
70
70
  'release:alpha': 'pnpm build && pnpm rimori-release alpha',
71
- 'release:beta': 'pnpm build && pnpm rimori-release beta',
72
71
  'release:stable': 'pnpm build && pnpm rimori-release stable',
73
72
  'dev:worker': 'VITE_MINIFY=false vite build --watch --config worker/vite.config.ts',
74
73
  'build:worker': 'vite build --config worker/vite.config.ts',
@@ -3,4 +3,4 @@ import { Config } from './release';
3
3
  * Read and send the database configuration to the release endpoint
4
4
  * @param config - Configuration object
5
5
  */
6
- export default function dbUpdate(config: Config, release_id: string): Promise<void>;
6
+ export default function dbUpdate(config: Config): Promise<void>;
@@ -5,7 +5,7 @@ import ts from 'typescript';
5
5
  * Read and send the database configuration to the release endpoint
6
6
  * @param config - Configuration object
7
7
  */
8
- export default async function dbUpdate(config, release_id) {
8
+ export default async function dbUpdate(config) {
9
9
  const dbConfigPath = path.resolve('./rimori/db.config.ts');
10
10
  // Check if db config file exists
11
11
  try {
@@ -48,11 +48,10 @@ export default async function dbUpdate(config, release_id) {
48
48
  console.log(`🗄️ Sending database configuration...`);
49
49
  const requestBody = {
50
50
  db_config: dbConfigObject,
51
- version: config.version,
52
51
  release_channel: config.release_channel,
53
52
  plugin_id: config.plugin_id,
54
53
  };
55
- const response = await fetch(`${config.domain}/release/${release_id}/db`, {
54
+ const response = await fetch(`${config.domain}/release/db`, {
56
55
  method: 'POST',
57
56
  headers: {
58
57
  'Content-Type': 'application/json',
@@ -3,6 +3,5 @@ import { Config } from './release';
3
3
  * Read and send the prompts configuration to the release endpoint.
4
4
  * Mirrors the pattern of release-db-update.ts.
5
5
  * @param config - Configuration object
6
- * @param release_id - The release ID
7
6
  */
8
- export default function promptsUpload(config: Config, release_id: string): Promise<void>;
7
+ export default function promptsUpload(config: Config): Promise<void>;
@@ -5,9 +5,8 @@ import ts from 'typescript';
5
5
  * Read and send the prompts configuration to the release endpoint.
6
6
  * Mirrors the pattern of release-db-update.ts.
7
7
  * @param config - Configuration object
8
- * @param release_id - The release ID
9
8
  */
10
- export default async function promptsUpload(config, release_id) {
9
+ export default async function promptsUpload(config) {
11
10
  const promptsConfigPath = path.resolve('./rimori/prompts.config.ts');
12
11
  // Check if prompts config file exists — optional, skip if not present
13
12
  try {
@@ -48,11 +47,9 @@ export default async function promptsUpload(config, release_id) {
48
47
  console.log(`📝 Sending ${prompts.length} prompt definitions...`);
49
48
  const requestBody = {
50
49
  prompts,
51
- version: config.version,
52
- release_channel: config.release_channel,
53
50
  plugin_id: config.plugin_id,
54
51
  };
55
- const response = await fetch(`${config.domain}/release/${release_id}/prompts`, {
52
+ const response = await fetch(`${config.domain}/release/prompts`, {
56
53
  method: 'POST',
57
54
  headers: {
58
55
  'Content-Type': 'application/json',
@@ -15,8 +15,6 @@ import fs from 'fs';
15
15
  import path from 'path';
16
16
  import dbUpdate from './release-db-update.js';
17
17
  import promptsUpload from './release-prompts-upload.js';
18
- import { uploadDirectory } from './release-file-upload.js';
19
- import { releasePlugin, sendConfiguration } from './release-config-upload.js';
20
18
  // Read version from package.json
21
19
  const packageJson = JSON.parse(fs.readFileSync(path.resolve('./package.json'), 'utf8'));
22
20
  const { version, r_id: pluginId } = packageJson;
@@ -61,22 +59,11 @@ async function releaseProcess() {
61
59
  console.log(`🚀 Releasing ${config.plugin_id} to ${config.release_channel}...`);
62
60
  }
63
61
  console.log(`📡 Deploying to: ${config.domain}`);
64
- // First send the configuration
65
- const release_id = await sendConfiguration(config);
66
62
  // Upload prompts (if prompts.config.ts exists)
67
- await promptsUpload(config, release_id);
68
- await dbUpdate(config, release_id);
69
- // Dev-sync only pushes metadata — skip bundle upload and finalize.
70
- if (config.dev_sync) {
71
- console.log('✅ Dev-sync complete');
72
- return;
73
- }
74
- // Then upload the files
75
- await uploadDirectory(config, release_id);
76
- // Then release the plugin
77
- await releasePlugin(config, release_id);
78
- // Inform user about translation processing
79
- console.log('🌐 Hint: The plugin is released but it might take some time until all translations are being processed.');
63
+ await promptsUpload(config);
64
+ // Migrate tables (if db.config.ts exists)
65
+ await dbUpdate(config);
66
+ console.log(config.dev_sync ? '✅ Dev-sync complete' : '✅ Release complete');
80
67
  }
81
68
  catch (error) {
82
69
  console.log('❌ Error:', error.message);
@@ -2,8 +2,17 @@ export type Plugin<T extends object = object> = Omit<RimoriPluginConfig<T>, 'con
2
2
  version: string;
3
3
  endpoint: string;
4
4
  assetEndpoint: string;
5
+ /**
6
+ * Same-origin base URL for this plugin's UI translations, e.g.
7
+ * `https://app.rimori.se/plugins/<pluginId>`. Set by rimori-main for first-party
8
+ * bundled plugins whose locales it serves itself (module-federation removal); the
9
+ * Translator fetches `<localeUrl>/locales/<lang>.json` from here instead of the
10
+ * plugin's `endpoint`. Undefined for third-party / externally-hosted plugins, which
11
+ * still resolve locales from their `endpoint`.
12
+ */
13
+ localeUrl?: string;
5
14
  context_menu_actions: MenuEntry[];
6
- release_channel: 'alpha' | 'beta' | 'stable';
15
+ release_channel: 'alpha' | 'stable';
7
16
  };
8
17
  export type ActivePlugin = Plugin<{
9
18
  active?: boolean;
@@ -88,8 +97,6 @@ export interface RimoriPluginConfig<T extends object = object> {
88
97
  sidebar: (SidebarPage & T)[];
89
98
  /** Optional path to the plugin's settings/configuration page */
90
99
  settings?: string;
91
- /** When true, rimori-main loads this plugin via Module Federation instead of an iframe. */
92
- federated?: boolean;
93
100
  /** Optional array of event topics the plugin pages can listen to for cross-plugin communication */
94
101
  topics?: string[];
95
102
  };
@@ -32,12 +32,12 @@ export interface RimoriInfo {
32
32
  /**
33
33
  * The release channel of the plugin installation.
34
34
  */
35
- releaseChannel: 'alpha' | 'beta' | 'stable';
35
+ releaseChannel: 'alpha' | 'stable';
36
36
  /**
37
37
  * The database schema to use for plugin tables.
38
38
  * Determined by rimori-main based on release channel:
39
39
  * - 'plugins_alpha' for alpha release channel
40
- * - 'plugins' for beta and stable release channels
40
+ * - 'plugins' for stable release channel
41
41
  */
42
42
  dbSchema: 'plugins' | 'plugins_alpha';
43
43
  /**
@@ -38,7 +38,9 @@ export class PluginModule {
38
38
  this.communicationHandler = communicationHandler;
39
39
  this.eventBus = eventBus;
40
40
  const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
41
- this.translator = new Translator(info.interfaceLanguage, currentPlugin?.endpoint || '', ai);
41
+ // Prefer localeUrl (same-origin, set by rimori-main for bundled first-party plugins) and
42
+ // fall back to endpoint (storage origin) for third-party / externally-hosted plugins.
43
+ this.translator = new Translator(info.interfaceLanguage, currentPlugin?.localeUrl || currentPlugin?.endpoint || '', ai);
42
44
  this.ttsEnabled = info.ttsEnabled ?? true;
43
45
  this.communicationHandler.onUpdate((updatedInfo) => {
44
46
  // Settings are keyed by guild id — bust the cache if the guild changed so
@@ -123,6 +123,14 @@ export declare class SharedContentController {
123
123
  * @returns The shared content item
124
124
  */
125
125
  get<T = any>(tableName: string, contentId: string): Promise<SharedContent<T>>;
126
+ /**
127
+ * Get multiple shared content items by ID in a single round-trip.
128
+ * Use instead of `Promise.all(ids.map(id => get(...)))` — that fans out one request per id.
129
+ * @param tableName - Name of the shared content table
130
+ * @param contentIds - IDs of the content to fetch
131
+ * @returns The matching shared content items (fewer than requested if some ids don't exist)
132
+ */
133
+ getMany<T = any>(tableName: string, contentIds: string[]): Promise<SharedContent<T>[]>;
126
134
  /**
127
135
  * Fetch all shared content items.
128
136
  * @param tableName - Name of the shared content table
@@ -209,6 +209,24 @@ export class SharedContentController {
209
209
  }
210
210
  return data;
211
211
  }
212
+ /**
213
+ * Get multiple shared content items by ID in a single round-trip.
214
+ * Use instead of `Promise.all(ids.map(id => get(...)))` — that fans out one request per id.
215
+ * @param tableName - Name of the shared content table
216
+ * @param contentIds - IDs of the content to fetch
217
+ * @returns The matching shared content items (fewer than requested if some ids don't exist)
218
+ */
219
+ async getMany(tableName, contentIds) {
220
+ if (contentIds.length === 0)
221
+ return [];
222
+ const fullTableName = this.getTableName(tableName);
223
+ const { data, error } = await this.supabase.from(fullTableName).select('*').in('id', contentIds);
224
+ if (error) {
225
+ console.error('Error fetching shared content:', error);
226
+ throw new Error('Error fetching shared content');
227
+ }
228
+ return (data || []);
229
+ }
212
230
  /**
213
231
  * Fetch all shared content items.
214
232
  * @param tableName - Name of the shared content table
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/client",
3
- "version": "2.5.47",
3
+ "version": "2.5.48",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -1,5 +0,0 @@
1
- /**
2
- * Detect available translation languages from public/locales directory
3
- * @returns Promise<string[]> Array of language codes found in the locales directory
4
- */
5
- export declare function detectTranslationLanguages(): Promise<string[]>;
@@ -1,32 +0,0 @@
1
- import fs from 'fs';
2
- /**
3
- * Detect available translation languages from public/locales directory
4
- * @returns Promise<string[]> Array of language codes found in the locales directory
5
- */
6
- export async function detectTranslationLanguages() {
7
- const localesPath = './public/locales';
8
- try {
9
- await fs.promises.access(localesPath);
10
- }
11
- catch (e) {
12
- console.log('⚠️ No locales directory found, no translations available');
13
- return [];
14
- }
15
- try {
16
- const files = await fs.promises.readdir(localesPath);
17
- // Filter out local- files and only include .json files
18
- const translationFiles = files.filter((file) => file.endsWith('.json') && !file.startsWith('local-'));
19
- if (translationFiles.length === 0) {
20
- console.log('⚠️ No translation files found (excluding local- files)');
21
- return [];
22
- }
23
- // Extract language codes from filenames (e.g., en.json -> en)
24
- const languages = translationFiles.map((file) => file.replace('.json', ''));
25
- console.log(`🌐 Found ${languages.length} translation languages: ${languages.join(', ')}`);
26
- return languages;
27
- }
28
- catch (error) {
29
- console.error(`❌ Error reading locales directory:`, error.message);
30
- return [];
31
- }
32
- }
@@ -1,7 +0,0 @@
1
- import { Config } from './release.js';
2
- /**
3
- * Read and send the rimori configuration to the release endpoint
4
- * @param config - Configuration object
5
- */
6
- export declare function sendConfiguration(config: Config): Promise<string>;
7
- export declare function releasePlugin(config: Config, release_id: string): Promise<void>;
@@ -1,110 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import ts from 'typescript';
4
- import { detectTranslationLanguages } from './detect-translation-languages.js';
5
- /**
6
- * Read and send the rimori configuration to the release endpoint
7
- * @param config - Configuration object
8
- */
9
- export async function sendConfiguration(config) {
10
- const configPath = path.resolve('./rimori/rimori.config.ts');
11
- // Check if config file exists
12
- try {
13
- await fs.promises.access(configPath);
14
- }
15
- catch (e) {
16
- throw new Error('Could not find rimori.config.ts in ./rimori/ directory');
17
- }
18
- try {
19
- let configObject;
20
- // Use TypeScript compiler to transpile and load
21
- const configContent = await fs.promises.readFile(configPath, 'utf8');
22
- // Transpile TypeScript to JavaScript
23
- const result = ts.transpile(configContent, {
24
- target: ts.ScriptTarget.ES2020,
25
- module: ts.ModuleKind.ES2020,
26
- });
27
- // Create a temporary file to import the transpiled code
28
- const tempFile = path.join(process.cwd(), 'temp_config.js');
29
- await fs.promises.writeFile(tempFile, result);
30
- try {
31
- // Use dynamic import to load the config
32
- const config = await import(`file://${tempFile}`);
33
- configObject = config.default || config;
34
- // Clean up temp file
35
- await fs.promises.unlink(tempFile);
36
- }
37
- catch (error) {
38
- // Clean up temp file even on error
39
- try {
40
- await fs.promises.unlink(tempFile);
41
- }
42
- catch (e) { }
43
- throw error;
44
- }
45
- if (!configObject) {
46
- throw new Error('Configuration object is empty or undefined');
47
- }
48
- // Detect available translation languages
49
- const availableLanguages = await detectTranslationLanguages();
50
- console.log(`🚀 Sending configuration...`);
51
- const requestBody = {
52
- config: configObject,
53
- version: config.version,
54
- plugin_id: config.plugin_id,
55
- release_channel: config.release_channel,
56
- rimori_client_version: config.rimori_client_version,
57
- provided_languages: availableLanguages.join(','),
58
- };
59
- if (config.dev_sync) {
60
- requestBody.update_existing = true;
61
- }
62
- try {
63
- const response = await fetch(`${config.domain}/release`, {
64
- method: 'POST',
65
- headers: {
66
- 'Content-Type': 'application/json',
67
- Authorization: `Bearer ${config.token}`,
68
- },
69
- body: JSON.stringify(requestBody),
70
- });
71
- const responseText = await response.text();
72
- console.log('Configuration response status:', response.status);
73
- console.log('Configuration response text:', responseText);
74
- const responseData = JSON.parse(responseText);
75
- if (response.ok) {
76
- console.log('✅ Configuration deployed successfully!');
77
- return responseData.release_id;
78
- }
79
- else {
80
- console.log('❌ Configuration failed!');
81
- console.log('Error:', responseData.error || 'Unknown error');
82
- console.log('Response data:', JSON.stringify(responseData, null, 2));
83
- throw new Error('Configuration upload failed');
84
- }
85
- }
86
- catch (e) {
87
- console.log('error', e);
88
- throw new Error('Error sending configuration');
89
- }
90
- }
91
- catch (error) {
92
- console.error('❌ Error sending configuration:', error.message);
93
- throw error;
94
- }
95
- }
96
- export async function releasePlugin(config, release_id) {
97
- const response = await fetch(`${config.domain}/release/${release_id}/release`, {
98
- method: 'POST',
99
- headers: {
100
- 'Content-Type': 'application/json',
101
- Authorization: `Bearer ${config.token}`,
102
- },
103
- body: JSON.stringify({ plugin_id: config.plugin_id }),
104
- });
105
- if (!response.ok) {
106
- console.log('Response:', await response.text());
107
- throw new Error('Failed to release plugin');
108
- }
109
- console.log('✅ Plugin released successfully');
110
- }
@@ -1,6 +0,0 @@
1
- import { Config } from './release.js';
2
- /**
3
- * Upload all files from a directory and its subdirectories to the release function
4
- * @param config - Configuration object
5
- */
6
- export declare function uploadDirectory(config: Config, release_id: string): Promise<void>;
@@ -1,120 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- /**
4
- * Upload all files from a directory and its subdirectories to the release function
5
- * @param config - Configuration object
6
- */
7
- export async function uploadDirectory(config, release_id) {
8
- const relativePath = './dist';
9
- console.log(`📁 Preparing to upload files from ${relativePath}...`);
10
- // Check if dist directory exists
11
- try {
12
- await fs.promises.access(relativePath);
13
- }
14
- catch (e) {
15
- throw new Error(`Directory ${relativePath} does not exist. Make sure to build your plugin first.`);
16
- }
17
- // Get all files recursively
18
- const files = await getAllFiles(relativePath);
19
- if (files.length === 0) {
20
- console.log('⚠️ No files found to upload');
21
- return;
22
- }
23
- console.log(`🚀 Uploading ${files.length} files...`);
24
- // Create FormData
25
- const formData = new FormData();
26
- // Add version and release channel data
27
- formData.append('version', config.version);
28
- formData.append('release_channel', config.release_channel);
29
- formData.append('plugin_id', config.plugin_id);
30
- // Create path mapping with IDs as keys
31
- const pathMapping = {};
32
- for (let i = 0; i < files.length; i++) {
33
- const filePath = files[i];
34
- try {
35
- const fileContent = await fs.promises.readFile(filePath);
36
- const relativePath = path.relative('./dist', filePath);
37
- const contentType = getContentType(filePath);
38
- // Generate unique ID for this file
39
- const fileId = `file_${i}`;
40
- // Add to path mapping using ID as key
41
- pathMapping[fileId] = relativePath;
42
- // Create a Blob with the file content and content type
43
- const blob = new Blob([new Uint8Array(fileContent)], { type: contentType });
44
- // Add file to FormData with ID_filename format
45
- const fileName = `${fileId}_${path.basename(filePath)}`;
46
- formData.append('files', blob, fileName);
47
- }
48
- catch (error) {
49
- console.error(`❌ Error reading file ${filePath}:`, error.message);
50
- throw error;
51
- }
52
- }
53
- // Add path mapping to FormData
54
- formData.append('path_mapping', JSON.stringify(pathMapping));
55
- // Upload to the release endpoint
56
- const response = await fetch(`${config.domain}/release/${release_id}/files`, {
57
- method: 'POST',
58
- headers: { Authorization: `Bearer ${config.token}` },
59
- body: formData,
60
- });
61
- if (response.ok) {
62
- console.log('✅ Files uploaded successfully!');
63
- }
64
- else {
65
- const errorText = await response.text();
66
- console.log('❌ File upload failed!');
67
- console.log('Response:', errorText);
68
- throw new Error(`File upload failed with status ${response.status}`);
69
- }
70
- }
71
- /**
72
- * Recursively get all files from a directory
73
- */
74
- async function getAllFiles(dirPath) {
75
- const files = [];
76
- async function traverse(currentPath) {
77
- const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
78
- for (const entry of entries) {
79
- const fullPath = path.join(currentPath, entry.name);
80
- if (entry.isDirectory()) {
81
- await traverse(fullPath);
82
- }
83
- else if (entry.isFile()) {
84
- files.push(fullPath);
85
- }
86
- }
87
- }
88
- await traverse(dirPath);
89
- return files;
90
- }
91
- /**
92
- * Get content type based on file extension
93
- */
94
- function getContentType(filePath) {
95
- const ext = filePath.split('.').pop()?.toLowerCase();
96
- const contentTypes = {
97
- html: 'text/html',
98
- css: 'text/css',
99
- js: 'application/javascript',
100
- json: 'application/json',
101
- md: 'text/markdown',
102
- txt: 'text/plain',
103
- png: 'image/png',
104
- jpg: 'image/jpeg',
105
- jpeg: 'image/jpeg',
106
- gif: 'image/gif',
107
- svg: 'image/svg+xml',
108
- pdf: 'application/pdf',
109
- ico: 'image/x-icon',
110
- mp3: 'audio/mpeg',
111
- wav: 'audio/wav',
112
- ogg: 'audio/ogg',
113
- m4a: 'audio/mp4',
114
- webp: 'image/webp',
115
- };
116
- const contentType = contentTypes[ext || ''];
117
- if (!contentType)
118
- throw new Error(`Unsupported file type: ${ext}`);
119
- return contentType;
120
- }