@objectql/cli 1.6.1 → 1.7.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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yCAAoC;AACpC,kDAAoD;AACpD,0CAA4C;AAC5C,4CAAyC;AACzC,8CAAgD;AAEhD,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,mBAAmB,CAAC;KAChC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACF,OAAO,CAAC,UAAU,CAAC;KACnB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,2DAA2D,CAAC;KACxE,MAAM,CAAC,qBAAqB,EAAE,0CAA0C,EAAE,GAAG,CAAC;KAC9E,MAAM,CAAC,qBAAqB,EAAE,sCAAsC,EAAE,iBAAiB,CAAC;KACxF,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,IAAI,CAAC;QACD,MAAM,IAAA,wBAAa,EAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,OAAO;KACF,OAAO,CAAC,MAAM,CAAC;KACf,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,qBAAqB,EAAE,+BAA+B,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,MAAM,IAAA,gBAAS,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEP,OAAO;KACF,OAAO,CAAC,OAAO,CAAC;KAChB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,4BAA4B,CAAC;KACzC,MAAM,CAAC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,CAAC;KAC1D,MAAM,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,GAAG,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,MAAM,IAAA,aAAK,EAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEP,OAAO;KACF,OAAO,CAAC,QAAQ,CAAC;KACjB,KAAK,CAAC,IAAI,CAAC;KACX,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,CAAC;KAC1D,MAAM,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,GAAG,CAAC;KAC9D,MAAM,CAAC,WAAW,EAAE,mCAAmC,CAAC;KACxD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,MAAM,IAAA,oBAAW,EAAC;QACd,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;QAC5B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;KACrB,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEP,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yCAAoC;AACpC,kDAAoD;AACpD,0CAA4C;AAC5C,4CAAyC;AACzC,8CAAgD;AAChD,0CAA8C;AAC9C,wCAA6C;AAC7C,0CAAsE;AACtE,gDAA2E;AAE3E,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,mBAAmB,CAAC;KAChC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,oCAAoC;AACpC,OAAO;KACF,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,6CAA6C,CAAC;KAC1D,MAAM,CAAC,2BAA2B,EAAE,kDAAkD,EAAE,OAAO,CAAC;KAChG,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAAC;KAC3C,MAAM,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;KAC9C,MAAM,CAAC,gBAAgB,EAAE,8BAA8B,CAAC;KACxD,MAAM,CAAC,YAAY,EAAE,yBAAyB,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,IAAI,CAAC;QACD,MAAM,IAAA,kBAAW,EAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,wCAAwC;AACxC,OAAO;KACF,OAAO,CAAC,mBAAmB,CAAC;KAC5B,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,GAAG,CAAC;KACnD,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAClC,IAAI,CAAC;QACD,MAAM,IAAA,iBAAW,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,+CAA+C;AAC/C,OAAO;KACF,OAAO,CAAC,UAAU,CAAC;KACnB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,2DAA2D,CAAC;KACxE,MAAM,CAAC,qBAAqB,EAAE,0CAA0C,EAAE,GAAG,CAAC;KAC9E,MAAM,CAAC,qBAAqB,EAAE,sCAAsC,EAAE,iBAAiB,CAAC;KACxF,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,IAAI,CAAC;QACD,MAAM,IAAA,wBAAa,EAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,gBAAgB;AAChB,MAAM,OAAO,GAAG,OAAO;KAClB,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,+BAA+B,CAAC,CAAC;AAElD,OAAO;KACF,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,GAAG,CAAC;KACtD,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,YAAY,CAAC;KAC/D,MAAM,CAAC,mBAAmB,EAAE,eAAe,EAAE,IAAI,CAAC;KAClD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,IAAI,CAAC;QACD,MAAM,IAAA,kBAAW,EAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,OAAO;KACF,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,uBAAuB,EAAE,qBAAqB,EAAE,YAAY,CAAC;KACpE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAC5B,IAAI,CAAC;QACD,MAAM,IAAA,eAAQ,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,OAAO;KACF,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,uBAAuB,EAAE,qBAAqB,EAAE,YAAY,CAAC;KACpE,MAAM,CAAC,oBAAoB,EAAE,kCAAkC,EAAE,IAAI,CAAC;KACtE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAC5B,IAAI,CAAC;QACD,MAAM,IAAA,mBAAY,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACvF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,qBAAqB;AACrB,MAAM,UAAU,GAAG,OAAO;KACrB,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,iCAAiC,CAAC;KAC9C,MAAM,CAAC,qBAAqB,EAAE,+BAA+B,CAAC;KAC9D,MAAM,CAAC,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,CAAC;KAClE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,IAAI,CAAC;QACD,MAAM,IAAA,iBAAO,EAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,UAAU;KACL,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,CAAC;KAClE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAC5B,IAAI,CAAC;QACD,MAAM,IAAA,uBAAa,EAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,UAAU;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,uBAAuB,CAAC;KACpC,MAAM,CAAC,qBAAqB,EAAE,+BAA+B,CAAC;KAC9D,MAAM,CAAC,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,CAAC;KAClE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,IAAI,CAAC;QACD,MAAM,IAAA,uBAAa,EAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC,CAAC;AAEP,eAAe;AACf,OAAO;KACF,OAAO,CAAC,MAAM,CAAC;KACf,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,qBAAqB,EAAE,+BAA+B,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,MAAM,IAAA,gBAAS,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEP,gBAAgB;AAChB,OAAO;KACF,OAAO,CAAC,OAAO,CAAC;KAChB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,4BAA4B,CAAC;KACzC,MAAM,CAAC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,CAAC;KAC1D,MAAM,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,GAAG,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,MAAM,IAAA,aAAK,EAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEP,iBAAiB;AACjB,OAAO;KACF,OAAO,CAAC,QAAQ,CAAC;KACjB,KAAK,CAAC,IAAI,CAAC;KACX,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,CAAC;KAC1D,MAAM,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,GAAG,CAAC;KAC9D,MAAM,CAAC,WAAW,EAAE,mCAAmC,CAAC;KACxD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACtB,MAAM,IAAA,oBAAW,EAAC;QACd,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;QAC5B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;KACrB,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEP,OAAO,CAAC,KAAK,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectql/cli",
3
- "version": "1.6.1",
3
+ "version": "1.7.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "objectql": "./bin/objectql"
@@ -13,11 +13,11 @@
13
13
  "js-yaml": "^4.1.0",
14
14
  "prettier": "^3.0.0",
15
15
  "ts-node": "^10.9.1",
16
- "@objectql/types": "1.6.1",
17
- "@objectql/server": "1.6.1",
18
- "@objectql/driver-sql": "1.6.1",
19
- "@objectql/platform-node": "1.6.1",
20
- "@objectql/core": "1.6.1"
16
+ "@objectql/types": "1.7.1",
17
+ "@objectql/core": "1.7.1",
18
+ "@objectql/server": "1.7.1",
19
+ "@objectql/driver-sql": "1.7.1",
20
+ "@objectql/platform-node": "1.7.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "typescript": "^5.0.0",
@@ -0,0 +1,303 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import * as yaml from 'js-yaml';
5
+ import glob from 'fast-glob';
6
+
7
+ interface I18nExtractOptions {
8
+ source?: string;
9
+ output?: string;
10
+ lang?: string;
11
+ }
12
+
13
+ interface I18nInitOptions {
14
+ lang: string;
15
+ baseDir?: string;
16
+ }
17
+
18
+ interface I18nValidateOptions {
19
+ lang: string;
20
+ baseDir?: string;
21
+ baseLang?: string;
22
+ }
23
+
24
+ /**
25
+ * Extract translatable strings from metadata files and create i18n files
26
+ */
27
+ export async function i18nExtract(options: I18nExtractOptions) {
28
+ const sourceDir = path.resolve(process.cwd(), options.source || '.');
29
+ const outputDir = path.resolve(process.cwd(), options.output || './src/i18n');
30
+ const lang = options.lang || 'en';
31
+
32
+ console.log(chalk.blue('🌐 Extracting translatable strings...'));
33
+ console.log(chalk.gray(`Source: ${sourceDir}`));
34
+ console.log(chalk.gray(`Output: ${outputDir}/${lang}\n`));
35
+
36
+ try {
37
+ // Find all metadata files
38
+ const files = await glob('**/*.{object,view,form,page,action,permission,validation,workflow,report,menu}.yml', {
39
+ cwd: sourceDir,
40
+ ignore: ['node_modules/**', 'dist/**', 'i18n/**']
41
+ });
42
+
43
+ console.log(chalk.gray(`Found ${files.length} metadata files`));
44
+
45
+ const translations: Record<string, any> = {};
46
+
47
+ for (const file of files) {
48
+ const filePath = path.join(sourceDir, file);
49
+ const content = fs.readFileSync(filePath, 'utf-8');
50
+ const data = yaml.load(content) as any;
51
+
52
+ if (!data) continue;
53
+
54
+ // Extract object name from filename
55
+ const objectName = path.basename(file).split('.')[0];
56
+
57
+ // Extract translatable fields
58
+ const objectTranslations: any = {};
59
+
60
+ if (data.label) {
61
+ objectTranslations.label = data.label;
62
+ }
63
+
64
+ if (data.description) {
65
+ objectTranslations.description = data.description;
66
+ }
67
+
68
+ // Extract field labels
69
+ if (data.fields) {
70
+ objectTranslations.fields = {};
71
+ for (const [fieldName, fieldConfig] of Object.entries(data.fields) as any) {
72
+ const fieldTrans: any = {};
73
+
74
+ if (fieldConfig.label) {
75
+ fieldTrans.label = fieldConfig.label;
76
+ }
77
+
78
+ if (fieldConfig.description) {
79
+ fieldTrans.description = fieldConfig.description;
80
+ }
81
+
82
+ if (fieldConfig.help_text) {
83
+ fieldTrans.help_text = fieldConfig.help_text;
84
+ }
85
+
86
+ // Extract select options
87
+ if (fieldConfig.options && Array.isArray(fieldConfig.options)) {
88
+ fieldTrans.options = {};
89
+ for (const option of fieldConfig.options) {
90
+ if (option.value && option.label) {
91
+ fieldTrans.options[option.value] = option.label;
92
+ }
93
+ }
94
+ }
95
+
96
+ if (Object.keys(fieldTrans).length > 0) {
97
+ objectTranslations.fields[fieldName] = fieldTrans;
98
+ }
99
+ }
100
+ }
101
+
102
+ // Extract action labels
103
+ if (data.actions) {
104
+ objectTranslations.actions = {};
105
+ for (const [actionName, actionConfig] of Object.entries(data.actions) as any) {
106
+ const actionTrans: any = {};
107
+ if (actionConfig.label) {
108
+ actionTrans.label = actionConfig.label;
109
+ }
110
+ if (actionConfig.confirm_text) {
111
+ actionTrans.confirm_text = actionConfig.confirm_text;
112
+ }
113
+ if (Object.keys(actionTrans).length > 0) {
114
+ objectTranslations.actions[actionName] = actionTrans;
115
+ }
116
+ }
117
+ }
118
+
119
+ // Extract validation messages
120
+ if (data.validation?.rules) {
121
+ objectTranslations.validation = {};
122
+ for (const rule of data.validation.rules) {
123
+ if (rule.name && rule.message) {
124
+ objectTranslations.validation[rule.name] = rule.message;
125
+ }
126
+ }
127
+ }
128
+
129
+ if (Object.keys(objectTranslations).length > 0) {
130
+ translations[objectName] = objectTranslations;
131
+ }
132
+ }
133
+
134
+ // Write translation files
135
+ const langDir = path.join(outputDir, lang);
136
+ if (!fs.existsSync(langDir)) {
137
+ fs.mkdirSync(langDir, { recursive: true });
138
+ }
139
+
140
+ // Write one file per object
141
+ for (const [objectName, objectTranslations] of Object.entries(translations)) {
142
+ const outputFile = path.join(langDir, `${objectName}.json`);
143
+ fs.writeFileSync(outputFile, JSON.stringify(objectTranslations, null, 4), 'utf-8');
144
+ console.log(chalk.green(`āœ“ ${objectName}.json`));
145
+ }
146
+
147
+ console.log(chalk.green(`\nāœ… Extracted translations to ${langDir}`));
148
+ console.log(chalk.gray(`Total: ${Object.keys(translations).length} files`));
149
+
150
+ } catch (error: any) {
151
+ console.error(chalk.red(`āŒ Failed to extract translations: ${error.message}`));
152
+ process.exit(1);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Initialize i18n structure for a new language
158
+ */
159
+ export async function i18nInit(options: I18nInitOptions) {
160
+ const baseDir = path.resolve(process.cwd(), options.baseDir || './src/i18n');
161
+ const { lang } = options;
162
+
163
+ console.log(chalk.blue(`🌐 Initializing i18n for language: ${lang}`));
164
+
165
+ // Validate language code
166
+ if (!/^[a-z]{2}(-[A-Z]{2})?$/.test(lang)) {
167
+ console.error(chalk.red('āŒ Invalid language code. Use format: en, zh-CN, etc.'));
168
+ process.exit(1);
169
+ }
170
+
171
+ const langDir = path.join(baseDir, lang);
172
+
173
+ if (fs.existsSync(langDir)) {
174
+ console.error(chalk.red(`āŒ Language directory already exists: ${langDir}`));
175
+ process.exit(1);
176
+ }
177
+
178
+ try {
179
+ fs.mkdirSync(langDir, { recursive: true });
180
+
181
+ // Create a sample translation file
182
+ const sampleTranslation = {
183
+ _meta: {
184
+ language: lang,
185
+ created: new Date().toISOString()
186
+ }
187
+ };
188
+
189
+ const sampleFile = path.join(langDir, 'common.json');
190
+ fs.writeFileSync(sampleFile, JSON.stringify(sampleTranslation, null, 4), 'utf-8');
191
+
192
+ console.log(chalk.green(`āœ… Initialized i18n for ${lang}`));
193
+ console.log(chalk.gray(`Directory: ${langDir}`));
194
+ console.log(chalk.gray(`\nNext steps:`));
195
+ console.log(chalk.gray(` 1. Run: objectql i18n extract --lang ${lang}`));
196
+ console.log(chalk.gray(` 2. Translate the JSON files in ${langDir}`));
197
+
198
+ } catch (error: any) {
199
+ console.error(chalk.red(`āŒ Failed to initialize i18n: ${error.message}`));
200
+ process.exit(1);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Validate translation completeness
206
+ */
207
+ export async function i18nValidate(options: I18nValidateOptions) {
208
+ const baseDir = path.resolve(process.cwd(), options.baseDir || './src/i18n');
209
+ const { lang, baseLang = 'en' } = options;
210
+
211
+ console.log(chalk.blue(`🌐 Validating translations for ${lang} against ${baseLang}...\n`));
212
+
213
+ const baseLangDir = path.join(baseDir, baseLang);
214
+ const targetLangDir = path.join(baseDir, lang);
215
+
216
+ if (!fs.existsSync(baseLangDir)) {
217
+ console.error(chalk.red(`āŒ Base language directory not found: ${baseLangDir}`));
218
+ process.exit(1);
219
+ }
220
+
221
+ if (!fs.existsSync(targetLangDir)) {
222
+ console.error(chalk.red(`āŒ Target language directory not found: ${targetLangDir}`));
223
+ process.exit(1);
224
+ }
225
+
226
+ try {
227
+ const baseFiles = fs.readdirSync(baseLangDir).filter(f => f.endsWith('.json'));
228
+ const targetFiles = fs.readdirSync(targetLangDir).filter(f => f.endsWith('.json'));
229
+
230
+ let totalMissing = 0;
231
+ let totalFiles = 0;
232
+
233
+ for (const file of baseFiles) {
234
+ totalFiles++;
235
+ const basePath = path.join(baseLangDir, file);
236
+ const targetPath = path.join(targetLangDir, file);
237
+
238
+ if (!fs.existsSync(targetPath)) {
239
+ console.log(chalk.red(`āœ— ${file} - Missing file`));
240
+ totalMissing++;
241
+ continue;
242
+ }
243
+
244
+ const baseData = JSON.parse(fs.readFileSync(basePath, 'utf-8'));
245
+ const targetData = JSON.parse(fs.readFileSync(targetPath, 'utf-8'));
246
+
247
+ const missing = findMissingKeys(baseData, targetData);
248
+
249
+ if (missing.length > 0) {
250
+ console.log(chalk.yellow(`⚠ ${file} - ${missing.length} missing keys:`));
251
+ for (const key of missing) {
252
+ console.log(chalk.gray(` - ${key}`));
253
+ }
254
+ totalMissing += missing.length;
255
+ } else {
256
+ console.log(chalk.green(`āœ“ ${file} - Complete`));
257
+ }
258
+ }
259
+
260
+ // Check for extra files in target
261
+ const extraFiles = targetFiles.filter(f => !baseFiles.includes(f));
262
+ if (extraFiles.length > 0) {
263
+ console.log(chalk.yellow(`\n⚠ Extra files in ${lang}:`));
264
+ for (const file of extraFiles) {
265
+ console.log(chalk.gray(` - ${file}`));
266
+ }
267
+ }
268
+
269
+ console.log(chalk.blue(`\nšŸ“Š Summary:`));
270
+ console.log(chalk.gray(`Total files: ${totalFiles}`));
271
+ console.log(totalMissing > 0
272
+ ? chalk.yellow(`Missing translations: ${totalMissing}`)
273
+ : chalk.green('All translations complete āœ“')
274
+ );
275
+
276
+ } catch (error: any) {
277
+ console.error(chalk.red(`āŒ Failed to validate translations: ${error.message}`));
278
+ process.exit(1);
279
+ }
280
+ }
281
+
282
+ function findMissingKeys(base: any, target: any, prefix = ''): string[] {
283
+ const missing: string[] = [];
284
+
285
+ for (const key in base) {
286
+ const fullKey = prefix ? `${prefix}.${key}` : key;
287
+
288
+ if (!(key in target)) {
289
+ missing.push(fullKey);
290
+ continue;
291
+ }
292
+
293
+ if (typeof base[key] === 'object' && base[key] !== null && !Array.isArray(base[key])) {
294
+ if (typeof target[key] === 'object' && target[key] !== null) {
295
+ missing.push(...findMissingKeys(base[key], target[key], fullKey));
296
+ } else {
297
+ missing.push(fullKey);
298
+ }
299
+ }
300
+ }
301
+
302
+ return missing;
303
+ }
@@ -0,0 +1,191 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import chalk from 'chalk';
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ interface InitOptions {
10
+ template?: string;
11
+ name?: string;
12
+ dir?: string;
13
+ skipInstall?: boolean;
14
+ skipGit?: boolean;
15
+ }
16
+
17
+ const TEMPLATES = {
18
+ basic: '@objectql/starter-basic',
19
+ 'express-api': '@objectql/starter-express-api',
20
+ enterprise: '@objectql/starter-enterprise'
21
+ };
22
+
23
+ export async function initProject(options: InitOptions) {
24
+ const projectName = options.name || 'my-objectql-project';
25
+ const targetDir = options.dir || path.join(process.cwd(), projectName);
26
+ const template = (options.template || 'basic') as keyof typeof TEMPLATES;
27
+
28
+ if (!TEMPLATES[template]) {
29
+ console.error(chalk.red(`āŒ Unknown template: ${template}`));
30
+ console.log(chalk.gray(`Available templates: ${Object.keys(TEMPLATES).join(', ')}`));
31
+ process.exit(1);
32
+ }
33
+
34
+ console.log(chalk.blue(`\nšŸš€ Initializing ObjectQL project: ${chalk.bold(projectName)}`));
35
+ console.log(chalk.gray(`Template: ${template}`));
36
+ console.log(chalk.gray(`Directory: ${targetDir}\n`));
37
+
38
+ // Check if directory exists
39
+ if (fs.existsSync(targetDir)) {
40
+ console.error(chalk.red(`āŒ Directory already exists: ${targetDir}`));
41
+ process.exit(1);
42
+ }
43
+
44
+ // Create project directory
45
+ fs.mkdirSync(targetDir, { recursive: true });
46
+
47
+ try {
48
+ // Copy template files from starters
49
+ const templatePath = path.join(__dirname, '../../../../starters', template);
50
+
51
+ // Check if we're in the monorepo (for development)
52
+ if (fs.existsSync(templatePath)) {
53
+ console.log(chalk.gray('Copying template files...'));
54
+ await copyDirectory(templatePath, targetDir, ['node_modules', 'dist', '.git']);
55
+ } else {
56
+ // Try to use the published package
57
+ console.log(chalk.gray(`Installing template from npm: ${TEMPLATES[template]}...`));
58
+ await execAsync(`npm create ${TEMPLATES[template]} ${targetDir}`, {
59
+ cwd: process.cwd()
60
+ });
61
+ }
62
+
63
+ // Update package.json with project name
64
+ const pkgPath = path.join(targetDir, 'package.json');
65
+ if (fs.existsSync(pkgPath)) {
66
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
67
+ pkg.name = projectName;
68
+ pkg.version = '0.1.0';
69
+ // Convert workspace dependencies to actual versions for standalone project
70
+ if (pkg.dependencies) {
71
+ for (const dep of Object.keys(pkg.dependencies)) {
72
+ if (pkg.dependencies[dep] === 'workspace:*') {
73
+ pkg.dependencies[dep] = '^1.0.0'; // Use latest published version
74
+ }
75
+ }
76
+ }
77
+ if (pkg.devDependencies) {
78
+ for (const dep of Object.keys(pkg.devDependencies)) {
79
+ if (pkg.devDependencies[dep] === 'workspace:*') {
80
+ pkg.devDependencies[dep] = '^1.0.0';
81
+ }
82
+ }
83
+ }
84
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
85
+ }
86
+
87
+ // Initialize git repository
88
+ if (!options.skipGit) {
89
+ try {
90
+ console.log(chalk.gray('\nInitializing git repository...'));
91
+ await execAsync('git init', { cwd: targetDir });
92
+
93
+ // Create .gitignore if it doesn't exist
94
+ const gitignorePath = path.join(targetDir, '.gitignore');
95
+ if (!fs.existsSync(gitignorePath)) {
96
+ const gitignore = `node_modules/
97
+ dist/
98
+ *.log
99
+ .DS_Store
100
+ *.sqlite3
101
+ .env
102
+ .env.local
103
+ `;
104
+ fs.writeFileSync(gitignorePath, gitignore);
105
+ }
106
+
107
+ console.log(chalk.green('āœ“ Git repository initialized'));
108
+ } catch (err) {
109
+ console.log(chalk.yellow('⚠ Git initialization skipped (git not available)'));
110
+ }
111
+ }
112
+
113
+ // Install dependencies
114
+ if (!options.skipInstall) {
115
+ console.log(chalk.gray('\nInstalling dependencies...'));
116
+ console.log(chalk.gray('This might take a few minutes...\n'));
117
+
118
+ try {
119
+ // Try pnpm first, fall back to npm
120
+ const hasNpm = await checkCommand('pnpm');
121
+ const packageManager = hasNpm ? 'pnpm' : 'npm';
122
+
123
+ await execAsync(`${packageManager} install`, {
124
+ cwd: targetDir,
125
+ // Show output in real-time would be better, but this is simpler
126
+ });
127
+ console.log(chalk.green(`āœ“ Dependencies installed with ${packageManager}`));
128
+ } catch (err: any) {
129
+ console.log(chalk.yellow(`⚠ Failed to install dependencies: ${err.message}`));
130
+ console.log(chalk.gray(`You can install them manually by running:`));
131
+ console.log(chalk.gray(` cd ${projectName} && npm install`));
132
+ }
133
+ }
134
+
135
+ // Success message
136
+ console.log(chalk.green('\nāœ… Project created successfully!\n'));
137
+ console.log(chalk.bold('Next steps:'));
138
+ console.log(chalk.gray(` cd ${projectName}`));
139
+
140
+ if (options.skipInstall) {
141
+ console.log(chalk.gray(' pnpm install # or npm install'));
142
+ }
143
+
144
+ console.log(chalk.gray(' pnpm run build'));
145
+ console.log(chalk.gray(' pnpm run repl\n'));
146
+
147
+ console.log(chalk.blue('šŸ“š Documentation: https://github.com/objectql/objectql'));
148
+
149
+ } catch (error: any) {
150
+ console.error(chalk.red(`\nāŒ Failed to create project: ${error.message}`));
151
+ // Clean up
152
+ if (fs.existsSync(targetDir)) {
153
+ fs.rmSync(targetDir, { recursive: true, force: true });
154
+ }
155
+ process.exit(1);
156
+ }
157
+ }
158
+
159
+ async function copyDirectory(src: string, dest: string, ignore: string[] = []) {
160
+ // Create destination directory
161
+ if (!fs.existsSync(dest)) {
162
+ fs.mkdirSync(dest, { recursive: true });
163
+ }
164
+
165
+ const entries = fs.readdirSync(src, { withFileTypes: true });
166
+
167
+ for (const entry of entries) {
168
+ const srcPath = path.join(src, entry.name);
169
+ const destPath = path.join(dest, entry.name);
170
+
171
+ // Skip ignored directories
172
+ if (ignore.includes(entry.name)) {
173
+ continue;
174
+ }
175
+
176
+ if (entry.isDirectory()) {
177
+ await copyDirectory(srcPath, destPath, ignore);
178
+ } else {
179
+ fs.copyFileSync(srcPath, destPath);
180
+ }
181
+ }
182
+ }
183
+
184
+ async function checkCommand(cmd: string): Promise<boolean> {
185
+ try {
186
+ await execAsync(`${cmd} --version`);
187
+ return true;
188
+ } catch {
189
+ return false;
190
+ }
191
+ }