melaka 0.0.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 (49) hide show
  1. package/CONTRIBUTING.md +347 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/docs/AI_PROVIDERS.md +343 -0
  5. package/docs/ARCHITECTURE.md +512 -0
  6. package/docs/CLI.md +438 -0
  7. package/docs/CONFIGURATION.md +453 -0
  8. package/docs/INTEGRATION.md +477 -0
  9. package/docs/ROADMAP.md +248 -0
  10. package/package.json +46 -0
  11. package/packages/ai/README.md +43 -0
  12. package/packages/ai/package.json +42 -0
  13. package/packages/ai/src/facade.ts +120 -0
  14. package/packages/ai/src/index.ts +34 -0
  15. package/packages/ai/src/prompt.ts +117 -0
  16. package/packages/ai/src/providers/gemini.ts +185 -0
  17. package/packages/ai/src/providers/index.ts +9 -0
  18. package/packages/ai/src/types.ts +134 -0
  19. package/packages/ai/tsconfig.json +19 -0
  20. package/packages/cli/README.md +70 -0
  21. package/packages/cli/package.json +44 -0
  22. package/packages/cli/src/cli.ts +30 -0
  23. package/packages/cli/src/commands/deploy.ts +115 -0
  24. package/packages/cli/src/commands/index.ts +9 -0
  25. package/packages/cli/src/commands/init.ts +107 -0
  26. package/packages/cli/src/commands/status.ts +73 -0
  27. package/packages/cli/src/commands/translate.ts +92 -0
  28. package/packages/cli/src/commands/validate.ts +69 -0
  29. package/packages/cli/tsconfig.json +19 -0
  30. package/packages/core/README.md +46 -0
  31. package/packages/core/package.json +50 -0
  32. package/packages/core/src/config.ts +241 -0
  33. package/packages/core/src/index.ts +111 -0
  34. package/packages/core/src/schema-generator.ts +263 -0
  35. package/packages/core/src/schemas.ts +126 -0
  36. package/packages/core/src/types.ts +481 -0
  37. package/packages/core/src/utils.ts +343 -0
  38. package/packages/core/tsconfig.json +19 -0
  39. package/packages/firestore/README.md +60 -0
  40. package/packages/firestore/package.json +48 -0
  41. package/packages/firestore/src/generator.ts +270 -0
  42. package/packages/firestore/src/i18n.ts +262 -0
  43. package/packages/firestore/src/index.ts +54 -0
  44. package/packages/firestore/src/processor.ts +245 -0
  45. package/packages/firestore/src/queue.ts +202 -0
  46. package/packages/firestore/src/task-handler.ts +164 -0
  47. package/packages/firestore/tsconfig.json +19 -0
  48. package/pnpm-workspace.yaml +2 -0
  49. package/turbo.json +31 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Melaka CLI - Validate Command
3
+ *
4
+ * Validate the melaka.config.ts file.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { loadConfig, configExists, getLanguageName } from '@melaka/core';
9
+
10
+ export const validateCommand = new Command('validate')
11
+ .description('Validate melaka.config.ts')
12
+ .action(async () => {
13
+ const chalk = (await import('chalk')).default;
14
+ const ora = (await import('ora')).default;
15
+
16
+ // Check if config exists
17
+ const existsSpinner = ora('Checking for melaka.config.ts...').start();
18
+ const exists = await configExists();
19
+
20
+ if (!exists) {
21
+ existsSpinner.fail('melaka.config.ts not found');
22
+ console.log('');
23
+ console.log(chalk.gray('Run `melaka init` to create one.'));
24
+ process.exit(1);
25
+ }
26
+
27
+ existsSpinner.succeed('Found melaka.config.ts');
28
+
29
+ // Load and validate
30
+ const validateSpinner = ora('Validating configuration...').start();
31
+
32
+ try {
33
+ const config = await loadConfig();
34
+ validateSpinner.succeed('Configuration is valid');
35
+
36
+ console.log('');
37
+ console.log(chalk.green('✓ Config file found: melaka.config.ts'));
38
+
39
+ // Languages
40
+ const langList = config.languages
41
+ .map((l) => `${l}`)
42
+ .join(', ');
43
+ console.log(chalk.green(`✓ Languages valid: ${langList}`));
44
+
45
+ // AI Provider
46
+ console.log(chalk.green(`✓ AI provider configured: ${config.ai.provider} (${config.ai.model})`));
47
+
48
+ // Collections
49
+ console.log(chalk.green(`✓ Collections configured: ${config.collections.length}`));
50
+ for (const collection of config.collections) {
51
+ const fieldCount = collection.fields?.length || 'auto';
52
+ const groupFlag = collection.isCollectionGroup ? ' (collection group)' : '';
53
+ console.log(chalk.gray(` - ${collection.path} (${fieldCount} fields)${groupFlag}`));
54
+ }
55
+
56
+ // Glossary
57
+ const glossaryCount = config.glossary ? Object.keys(config.glossary).length : 0;
58
+ console.log(chalk.green(`✓ Glossary entries: ${glossaryCount}`));
59
+
60
+ console.log('');
61
+ console.log(chalk.green('✨ Config is valid!'));
62
+ console.log('');
63
+ } catch (error) {
64
+ validateSpinner.fail('Configuration is invalid');
65
+ console.log('');
66
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
67
+ process.exit(1);
68
+ }
69
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src"
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
@@ -0,0 +1,46 @@
1
+ # @melaka/core
2
+
3
+ Core types, utilities, and configuration for Melaka.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @melaka/core
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Define Configuration
14
+
15
+ ```typescript
16
+ import { defineConfig } from '@melaka/core';
17
+
18
+ export default defineConfig({
19
+ languages: ['ms-MY', 'zh-CN'],
20
+ ai: {
21
+ provider: 'gemini',
22
+ model: 'gemini-2.5-flash',
23
+ },
24
+ collections: [
25
+ {
26
+ path: 'articles',
27
+ fields: ['title', 'content'],
28
+ },
29
+ ],
30
+ });
31
+ ```
32
+
33
+ ### Types
34
+
35
+ ```typescript
36
+ import type {
37
+ MelakaConfig,
38
+ CollectionConfig,
39
+ FieldMapping,
40
+ TranslationResult,
41
+ } from '@melaka/core';
42
+ ```
43
+
44
+ ## API Reference
45
+
46
+ See [types.ts](./src/types.ts) for full type definitions.
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@melaka/core",
3
+ "version": "0.0.0",
4
+ "description": "Core types and utilities for Melaka",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts",
20
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "lint": "eslint src/",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist"
26
+ },
27
+ "dependencies": {
28
+ "zod": "^3.22.0"
29
+ },
30
+ "devDependencies": {
31
+ "firebase-admin": "^12.0.0",
32
+ "tsup": "^8.0.0",
33
+ "typescript": "^5.3.0",
34
+ "vitest": "^1.2.0"
35
+ },
36
+ "peerDependencies": {
37
+ "firebase-admin": ">=11.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "firebase-admin": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/rizahassan/melaka.git",
47
+ "directory": "packages/core"
48
+ },
49
+ "license": "MIT"
50
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Melaka Core - Config Loader
3
+ *
4
+ * Loads and validates melaka.config.ts configuration files.
5
+ */
6
+
7
+ import { pathToFileURL } from 'url';
8
+ import path from 'path';
9
+ import { MelakaConfigSchema } from './schemas';
10
+ import type { MelakaConfig, CollectionConfig, AIConfig } from './types';
11
+
12
+ // ============================================================================
13
+ // Config Loading
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Load and validate a Melaka configuration file.
18
+ *
19
+ * Supports both TypeScript (.ts) and JavaScript (.js) config files.
20
+ * The config file must export a default configuration object.
21
+ *
22
+ * @param configPath - Path to config file (defaults to 'melaka.config.ts' in cwd)
23
+ * @returns Validated configuration
24
+ * @throws Error if config file not found or invalid
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const config = await loadConfig();
29
+ * // or
30
+ * const config = await loadConfig('/path/to/melaka.config.ts');
31
+ * ```
32
+ */
33
+ export async function loadConfig(configPath?: string): Promise<MelakaConfig> {
34
+ const resolvedPath = await resolveConfigPath(configPath);
35
+
36
+ // Check if file exists
37
+ const fs = await import('fs/promises');
38
+ try {
39
+ await fs.access(resolvedPath);
40
+ } catch {
41
+ throw new Error(`Config file not found: ${resolvedPath}`);
42
+ }
43
+
44
+ // Import the config file
45
+ let configModule: unknown;
46
+ try {
47
+ // Use file URL for ESM compatibility
48
+ const fileUrl = pathToFileURL(resolvedPath).href;
49
+ configModule = await import(fileUrl);
50
+ } catch (error) {
51
+ throw new Error(
52
+ `Failed to load config file: ${resolvedPath}\n${error instanceof Error ? error.message : String(error)}`
53
+ );
54
+ }
55
+
56
+ // Extract default export
57
+ const rawConfig = (configModule as { default?: unknown }).default;
58
+ if (!rawConfig) {
59
+ throw new Error(`Config file must have a default export: ${resolvedPath}`);
60
+ }
61
+
62
+ // Validate with Zod
63
+ const result = MelakaConfigSchema.safeParse(rawConfig);
64
+ if (!result.success) {
65
+ const errors = result.error.errors
66
+ .map((e) => ` - ${e.path.join('.')}: ${e.message}`)
67
+ .join('\n');
68
+ throw new Error(`Invalid configuration:\n${errors}`);
69
+ }
70
+
71
+ return result.data as MelakaConfig;
72
+ }
73
+
74
+ /**
75
+ * Resolve the config file path.
76
+ *
77
+ * Checks for config files in order: .ts, .js, .mjs
78
+ *
79
+ * @param configPath - Optional explicit path
80
+ * @returns Resolved absolute path
81
+ */
82
+ async function resolveConfigPath(configPath?: string): Promise<string> {
83
+ if (configPath) {
84
+ return path.resolve(configPath);
85
+ }
86
+
87
+ const fs = await import('fs/promises');
88
+ const cwd = process.cwd();
89
+
90
+ // Check for config files in order of preference
91
+ const configFiles = [
92
+ 'melaka.config.ts',
93
+ 'melaka.config.js',
94
+ 'melaka.config.mjs',
95
+ ];
96
+
97
+ for (const filename of configFiles) {
98
+ const fullPath = path.resolve(cwd, filename);
99
+ try {
100
+ await fs.access(fullPath);
101
+ return fullPath;
102
+ } catch {
103
+ // File doesn't exist, try next
104
+ }
105
+ }
106
+
107
+ // Default to .ts for error messages
108
+ return path.resolve(cwd, 'melaka.config.ts');
109
+ }
110
+
111
+ // ============================================================================
112
+ // Config Helpers
113
+ // ============================================================================
114
+
115
+ /**
116
+ * Get the effective AI config for a collection.
117
+ *
118
+ * Merges collection-specific AI overrides with root AI config.
119
+ *
120
+ * @param rootConfig - Root Melaka configuration
121
+ * @param collection - Collection configuration
122
+ * @returns Effective AI config
123
+ */
124
+ export function getEffectiveAIConfig(
125
+ rootConfig: MelakaConfig,
126
+ collection: CollectionConfig
127
+ ): AIConfig {
128
+ return {
129
+ ...rootConfig.ai,
130
+ ...(collection.ai || {}),
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Get the effective batch size for a collection.
136
+ *
137
+ * @param rootConfig - Root Melaka configuration
138
+ * @param collection - Collection configuration
139
+ * @returns Effective batch size
140
+ */
141
+ export function getEffectiveBatchSize(
142
+ rootConfig: MelakaConfig,
143
+ collection: CollectionConfig
144
+ ): number {
145
+ return (
146
+ collection.batchSize ||
147
+ rootConfig.defaults?.batchSize ||
148
+ 20
149
+ );
150
+ }
151
+
152
+ /**
153
+ * Get the effective max concurrency for a collection.
154
+ *
155
+ * @param rootConfig - Root Melaka configuration
156
+ * @param collection - Collection configuration
157
+ * @returns Effective max concurrency
158
+ */
159
+ export function getEffectiveMaxConcurrency(
160
+ rootConfig: MelakaConfig,
161
+ collection: CollectionConfig
162
+ ): number {
163
+ return (
164
+ collection.maxConcurrency ||
165
+ rootConfig.defaults?.maxConcurrency ||
166
+ 10
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Get the effective forceUpdate flag for a collection.
172
+ *
173
+ * @param rootConfig - Root Melaka configuration
174
+ * @param collection - Collection configuration
175
+ * @returns Effective forceUpdate value
176
+ */
177
+ export function getEffectiveForceUpdate(
178
+ rootConfig: MelakaConfig,
179
+ collection: CollectionConfig
180
+ ): boolean {
181
+ return (
182
+ collection.forceUpdate ??
183
+ rootConfig.defaults?.forceUpdate ??
184
+ false
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Find a collection config by path.
190
+ *
191
+ * @param config - Root Melaka configuration
192
+ * @param collectionPath - Collection path to find
193
+ * @returns Collection config or undefined
194
+ */
195
+ export function findCollectionConfig(
196
+ config: MelakaConfig,
197
+ collectionPath: string
198
+ ): CollectionConfig | undefined {
199
+ return config.collections.find((c) => c.path === collectionPath);
200
+ }
201
+
202
+ // ============================================================================
203
+ // Config Validation
204
+ // ============================================================================
205
+
206
+ /**
207
+ * Validate a configuration without loading from file.
208
+ *
209
+ * Useful for programmatic config generation or testing.
210
+ *
211
+ * @param config - Configuration object to validate
212
+ * @returns Validated configuration
213
+ * @throws Error if configuration is invalid
214
+ */
215
+ export function validateConfig(config: unknown): MelakaConfig {
216
+ const result = MelakaConfigSchema.safeParse(config);
217
+ if (!result.success) {
218
+ const errors = result.error.errors
219
+ .map((e) => ` - ${e.path.join('.')}: ${e.message}`)
220
+ .join('\n');
221
+ throw new Error(`Invalid configuration:\n${errors}`);
222
+ }
223
+ return result.data as MelakaConfig;
224
+ }
225
+
226
+ /**
227
+ * Check if a config file exists at the given path.
228
+ *
229
+ * @param configPath - Path to check (defaults to 'melaka.config.ts' in cwd)
230
+ * @returns Whether the config file exists
231
+ */
232
+ export async function configExists(configPath?: string): Promise<boolean> {
233
+ const resolvedPath = await resolveConfigPath(configPath);
234
+ const fs = await import('fs/promises');
235
+ try {
236
+ await fs.access(resolvedPath);
237
+ return true;
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @melaka/core
3
+ *
4
+ * Core types, schemas, and utilities for Melaka - AI-powered Firestore localization.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ // ============================================================================
10
+ // Types
11
+ // ============================================================================
12
+
13
+ export type {
14
+ // Configuration
15
+ MelakaConfig,
16
+ AIConfig,
17
+ DefaultsConfig,
18
+ CollectionConfig,
19
+ FieldMapping,
20
+ SchemaType,
21
+ // Translation
22
+ MelakaMetadata,
23
+ TranslationStatus,
24
+ TranslationTaskPayload,
25
+ TranslationResult,
26
+ // Migration
27
+ MigrationResult,
28
+ ProgressResult,
29
+ // Helpers
30
+ SeparatedContent,
31
+ TranslationRequest,
32
+ } from './types';
33
+
34
+ // Re-export defineConfig from types
35
+ export { defineConfig } from './types';
36
+
37
+ // ============================================================================
38
+ // Schemas (Zod)
39
+ // ============================================================================
40
+
41
+ export {
42
+ // Config schemas
43
+ MelakaConfigSchema,
44
+ AIConfigSchema,
45
+ AIProviderSchema,
46
+ CollectionConfigSchema,
47
+ DefaultsConfigSchema,
48
+ FieldMappingSchema,
49
+ SchemaTypeSchema,
50
+ // Runtime schemas
51
+ TranslationTaskPayloadSchema,
52
+ MelakaMetadataSchema,
53
+ TranslationStatusSchema,
54
+ // Type inference
55
+ type AIConfigInput,
56
+ type AIConfigOutput,
57
+ type CollectionConfigInput,
58
+ type CollectionConfigOutput,
59
+ type MelakaConfigInput,
60
+ type MelakaConfigOutput,
61
+ } from './schemas';
62
+
63
+ // ============================================================================
64
+ // Config Loading
65
+ // ============================================================================
66
+
67
+ export {
68
+ loadConfig,
69
+ validateConfig,
70
+ configExists,
71
+ getEffectiveAIConfig,
72
+ getEffectiveBatchSize,
73
+ getEffectiveMaxConcurrency,
74
+ getEffectiveForceUpdate,
75
+ findCollectionConfig,
76
+ } from './config';
77
+
78
+ // ============================================================================
79
+ // Utilities
80
+ // ============================================================================
81
+
82
+ export {
83
+ // Content hashing
84
+ hashContent,
85
+ // Field detection
86
+ detectFieldType,
87
+ isTranslatableType,
88
+ // Content separation
89
+ separateContent,
90
+ // Glossary
91
+ mergeGlossaries,
92
+ formatGlossary,
93
+ // Language
94
+ getLanguageName,
95
+ // ID generation
96
+ generateBatchId,
97
+ } from './utils';
98
+
99
+ // ============================================================================
100
+ // Schema Generation
101
+ // ============================================================================
102
+
103
+ export {
104
+ createFieldSchema,
105
+ createTranslationSchema,
106
+ createSchemaFromMappings,
107
+ createTranslatableSchemaFromMappings,
108
+ validateWithSchema,
109
+ safeValidateWithSchema,
110
+ schemaToJsonSchema,
111
+ } from './schema-generator';