i18ntk 1.10.1 → 2.0.2

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 (110) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +141 -1185
  3. package/main/i18ntk-analyze.js +149 -133
  4. package/main/i18ntk-backup-class.js +420 -0
  5. package/main/i18ntk-backup.js +4 -4
  6. package/main/i18ntk-complete.js +90 -65
  7. package/main/i18ntk-doctor.js +123 -103
  8. package/main/i18ntk-fixer.js +61 -725
  9. package/main/i18ntk-go.js +14 -15
  10. package/main/i18ntk-init.js +76 -25
  11. package/main/i18ntk-java.js +27 -32
  12. package/main/i18ntk-js.js +70 -68
  13. package/main/i18ntk-manage.js +128 -29
  14. package/main/i18ntk-php.js +75 -75
  15. package/main/i18ntk-py.js +55 -56
  16. package/main/i18ntk-scanner.js +59 -57
  17. package/main/i18ntk-setup.js +10 -396
  18. package/main/i18ntk-sizing.js +46 -40
  19. package/main/i18ntk-summary.js +21 -18
  20. package/main/i18ntk-ui.js +11 -10
  21. package/main/i18ntk-usage.js +55 -19
  22. package/main/i18ntk-validate.js +13 -13
  23. package/main/manage/commands/AnalyzeCommand.js +1124 -0
  24. package/main/manage/commands/BackupCommand.js +62 -0
  25. package/main/manage/commands/CommandRouter.js +295 -0
  26. package/main/manage/commands/CompleteCommand.js +61 -0
  27. package/main/manage/commands/DoctorCommand.js +60 -0
  28. package/main/manage/commands/FixerCommand.js +624 -0
  29. package/main/manage/commands/InitCommand.js +62 -0
  30. package/main/manage/commands/ScannerCommand.js +654 -0
  31. package/main/manage/commands/SizingCommand.js +60 -0
  32. package/main/manage/commands/SummaryCommand.js +61 -0
  33. package/main/manage/commands/UsageCommand.js +60 -0
  34. package/main/manage/commands/ValidateCommand.js +978 -0
  35. package/main/manage/index-fixed.js +1447 -0
  36. package/main/manage/index.js +1462 -0
  37. package/main/manage/managers/DebugMenu.js +140 -0
  38. package/main/manage/managers/InteractiveMenu.js +177 -0
  39. package/main/manage/managers/LanguageMenu.js +62 -0
  40. package/main/manage/managers/SettingsMenu.js +53 -0
  41. package/main/manage/services/AuthenticationService.js +263 -0
  42. package/main/manage/services/ConfigurationService-fixed.js +449 -0
  43. package/main/manage/services/ConfigurationService.js +449 -0
  44. package/main/manage/services/FileManagementService.js +368 -0
  45. package/main/manage/services/FrameworkDetectionService.js +458 -0
  46. package/main/manage/services/InitService.js +1051 -0
  47. package/main/manage/services/SetupService.js +462 -0
  48. package/main/manage/services/SummaryService.js +450 -0
  49. package/main/manage/services/UsageService.js +1502 -0
  50. package/package.json +32 -30
  51. package/runtime/enhanced.d.ts +221 -221
  52. package/runtime/index.d.ts +29 -29
  53. package/runtime/index.full.d.ts +331 -331
  54. package/runtime/index.js +7 -6
  55. package/scripts/build-lite.js +17 -17
  56. package/scripts/deprecate-versions.js +23 -6
  57. package/scripts/export-translations.js +5 -5
  58. package/scripts/fix-all-i18n.js +3 -3
  59. package/scripts/fix-and-purify-i18n.js +3 -2
  60. package/scripts/fix-locale-control-chars.js +110 -0
  61. package/scripts/lint-locales.js +80 -0
  62. package/scripts/locale-optimizer.js +8 -8
  63. package/scripts/prepublish.js +21 -21
  64. package/scripts/security-check.js +13 -5
  65. package/scripts/sync-translations.js +4 -4
  66. package/scripts/sync-ui-locales.js +9 -8
  67. package/scripts/validate-all-translations.js +8 -7
  68. package/scripts/verify-deprecations.js +23 -15
  69. package/scripts/verify-translations.js +6 -5
  70. package/settings/i18ntk-config.json +282 -282
  71. package/settings/language-config.json +5 -5
  72. package/settings/settings-cli.js +9 -9
  73. package/settings/settings-manager.js +23 -20
  74. package/ui-locales/de.json +2417 -2348
  75. package/ui-locales/en.json +2415 -2352
  76. package/ui-locales/es.json +2425 -2353
  77. package/ui-locales/fr.json +2418 -2348
  78. package/ui-locales/ja.json +2463 -2361
  79. package/ui-locales/ru.json +2463 -2359
  80. package/ui-locales/zh.json +2418 -2351
  81. package/utils/admin-auth.js +2 -2
  82. package/utils/admin-cli.js +297 -297
  83. package/utils/admin-pin.js +9 -9
  84. package/utils/cli-helper.js +9 -9
  85. package/utils/config-helper.js +152 -103
  86. package/utils/config-manager.js +204 -164
  87. package/utils/config.js +5 -4
  88. package/utils/env-manager.js +256 -0
  89. package/utils/framework-detector.js +27 -24
  90. package/utils/i18n-helper.js +85 -41
  91. package/utils/init-helper.js +152 -94
  92. package/utils/json-output.js +98 -98
  93. package/utils/logger.js +6 -2
  94. package/utils/mini-commander.js +179 -0
  95. package/utils/missing-key-validator.js +5 -5
  96. package/utils/plugin-loader.js +29 -11
  97. package/utils/prompt.js +14 -44
  98. package/utils/safe-json.js +40 -0
  99. package/utils/secure-errors.js +3 -3
  100. package/utils/security-check-improved.js +390 -0
  101. package/utils/security-config.js +5 -5
  102. package/utils/security-fixed.js +607 -0
  103. package/utils/security.js +462 -248
  104. package/utils/setup-enforcer.js +136 -44
  105. package/utils/setup-validator.js +33 -32
  106. package/utils/terminal-icons.js +1 -1
  107. package/utils/ultra-performance-optimizer.js +11 -9
  108. package/utils/watch-locales.js +2 -1
  109. package/utils/prompt-fixed.js +0 -55
  110. package/utils/security-check.js +0 -450
@@ -1,81 +1,121 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const configManager = require('./config-helper');
4
- const { ensureDirectory } = require('./config-helper');
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const configManager = require('./config-manager');
4
+ const SecurityUtils = require('./security');
5
+
6
+ function ensureDirectory(dirPath) {
7
+ if (!dirPath || typeof dirPath !== 'string') return;
8
+ if (!SecurityUtils.safeExistsSync(dirPath)) {
9
+ fs.mkdirSync(dirPath, { recursive: true });
10
+ }
11
+ }
12
+
13
+ function readJsonSafe(filePath) {
14
+ try {
15
+ if (!SecurityUtils.safeExistsSync(filePath)) return null;
16
+ const raw = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
17
+ if (!raw) return null;
18
+ return JSON.parse(raw);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function hasSourceLanguageFiles(sourceDir, sourceLanguage) {
25
+ const baseSourceDir = path.resolve(sourceDir);
26
+ const modularLanguageDir = path.join(baseSourceDir, sourceLanguage);
27
+ const singleLanguageFile = path.join(baseSourceDir, `${sourceLanguage}.json`);
28
+
29
+ if (SecurityUtils.safeExistsSync(modularLanguageDir)) {
30
+ try {
31
+ if (fs.statSync(modularLanguageDir).isDirectory()) {
32
+ return fs.readdirSync(modularLanguageDir).some(file => file.endsWith('.json'));
33
+ }
34
+ } catch {
35
+ // Continue with single-file fallback.
36
+ }
37
+ }
38
+
39
+ return SecurityUtils.safeExistsSync(singleLanguageFile);
40
+ }
5
41
 
6
42
  /**
7
43
  * Check if the project is properly initialized
8
44
  * @param {Object} options - Options for initialization check
9
45
  * @returns {Promise<Object>} Object containing initialization status and config
10
46
  */
11
- async function checkInitialized(options = {}) {
12
- const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
13
- const currentVersion = require('../package.json').version;
14
- const configDir = settings.configDir || './settings';
15
-
16
- // Ensure config directory exists
17
- ensureDirectory(configDir);
18
-
47
+ async function checkInitialized(options = {}) {
48
+ const settings = configManager.getConfig ? configManager.getConfig() : {};
49
+ const currentVersion = require('../package.json').version;
50
+ const projectConfigPath = configManager.CONFIG_PATH || path.join(process.cwd(), '.i18ntk-config');
51
+ const configDir = path.dirname(projectConfigPath);
52
+
53
+ // Ensure config directory exists
54
+ ensureDirectory(configDir);
55
+
19
56
  const defaultConfig = {
20
57
  sourceDir: settings.sourceDir || './locales',
21
58
  sourceLanguage: settings.sourceLanguage || 'en',
22
59
  projectRoot: path.resolve('.'),
23
60
  framework: settings.framework || { detected: false, prompt: 'always' },
24
- configDir: configDir
25
- };
26
-
27
- // Check initialization status file with consistent path resolution
28
- const initFilePath = path.resolve(configDir, 'initialization.json');
29
-
30
- // If initialization file exists and is valid, return early
31
- if (fs.existsSync(initFilePath)) {
32
- try {
33
- const initStatus = JSON.parse(fs.readFileSync(initFilePath, 'utf8'));
34
- const isInitialized = initStatus.initialized &&
35
- initStatus.version &&
36
- initStatus.version.split('.')[0] === currentVersion.split('.')[0];
37
-
38
- if (isInitialized) {
39
- // Merge with default config but don't override existing settings
40
- return {
41
- initialized: true,
42
- config: {
43
- ...defaultConfig,
44
- ...initStatus,
45
- // Don't override framework settings if they exist
46
- framework: initStatus.framework || defaultConfig.framework
47
- }
48
- };
49
- }
50
- } catch (e) {
51
- console.warn('Warning: Invalid initialization file, will reinitialize...', e.message);
52
- }
53
- }
54
-
55
- // Check if source language files exist
56
- const langDir = path.resolve(defaultConfig.sourceDir, defaultConfig.sourceLanguage);
57
- const hasLanguageFiles = fs.existsSync(langDir) &&
58
- fs.readdirSync(langDir).some(f => f.endsWith('.json'));
59
-
60
- // If language files exist but no init file, create one
61
- if (hasLanguageFiles) {
62
- const initData = {
63
- initialized: true,
64
- version: currentVersion,
65
- timestamp: new Date().toISOString(),
66
- sourceDir: defaultConfig.sourceDir,
67
- sourceLanguage: defaultConfig.sourceLanguage,
68
- detectedLanguage: defaultConfig.detectedLanguage,
69
- detectedFramework: defaultConfig.detectedFramework,
70
- lastUpdated: new Date().toISOString()
71
- };
72
-
73
- ensureDirectory(path.dirname(initFilePath));
74
- fs.writeFileSync(initFilePath, JSON.stringify(initData, null, 2));
75
-
76
- return {
77
- initialized: true,
78
- config: { ...defaultConfig, ...initData }
61
+ configDir: configDir
62
+ };
63
+
64
+ // Primary source of truth in v2: project-level .i18ntk-config
65
+ const projectConfig = readJsonSafe(projectConfigPath);
66
+ if (projectConfig?.setup?.completed === true) {
67
+ return {
68
+ initialized: true,
69
+ config: {
70
+ ...defaultConfig,
71
+ ...projectConfig,
72
+ framework: projectConfig.framework || defaultConfig.framework
73
+ }
74
+ };
75
+ }
76
+
77
+ // Backward compatibility: legacy initialization marker file.
78
+ const initFilePath = path.join(configDir, 'initialization.json');
79
+ const initStatus = readJsonSafe(initFilePath);
80
+ const isLegacyInitialized = Boolean(initStatus?.initialized) && (
81
+ !initStatus.version ||
82
+ initStatus.version.split('.')[0] === currentVersion.split('.')[0]
83
+ );
84
+ if (isLegacyInitialized) {
85
+ return {
86
+ initialized: true,
87
+ config: {
88
+ ...defaultConfig,
89
+ ...initStatus,
90
+ framework: initStatus.framework || defaultConfig.framework
91
+ }
92
+ };
93
+ }
94
+
95
+ // Final fallback: detect existing source language files and mark initialized.
96
+ const sourceDir = options.sourceDir || defaultConfig.sourceDir;
97
+ const sourceLanguage = options.sourceLanguage || defaultConfig.sourceLanguage;
98
+ const hasLanguageFiles = hasSourceLanguageFiles(sourceDir, sourceLanguage);
99
+
100
+ // If language files exist but no init file, create one
101
+ if (hasLanguageFiles) {
102
+ const initData = {
103
+ initialized: true,
104
+ version: currentVersion,
105
+ timestamp: new Date().toISOString(),
106
+ sourceDir,
107
+ sourceLanguage,
108
+ detectedLanguage: defaultConfig.detectedLanguage,
109
+ detectedFramework: defaultConfig.detectedFramework,
110
+ lastUpdated: new Date().toISOString()
111
+ };
112
+
113
+ ensureDirectory(path.dirname(initFilePath));
114
+ SecurityUtils.safeWriteFileSync(initFilePath, JSON.stringify(initData, null, 2), path.dirname(initFilePath), 'utf8');
115
+
116
+ return {
117
+ initialized: true,
118
+ config: { ...defaultConfig, ...initData }
79
119
  };
80
120
  }
81
121
 
@@ -90,33 +130,51 @@ async function checkInitialized(options = {}) {
90
130
  * @param {Object} config - Configuration to save
91
131
  * @returns {Promise<void>}
92
132
  */
93
- async function markAsInitialized(config) {
94
- const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
95
- const configDir = settings.configDir || './settings';
96
- const initFilePath = path.resolve(configDir, 'initialization.json');
97
- const currentVersion = require('../package.json').version;
98
-
99
- const initData = {
100
- initialized: true,
101
- version: currentVersion,
102
- timestamp: new Date().toISOString(),
103
- sourceDir: config.sourceDir,
104
- sourceLanguage: config.sourceLanguage,
105
- detectedLanguage: config.detectedLanguage,
106
- detectedFramework: config.detectedFramework,
107
- lastUpdated: new Date().toISOString()
108
- };
109
-
110
- ensureDirectory(path.dirname(initFilePath));
111
- fs.writeFileSync(initFilePath, JSON.stringify(initData, null, 2));
112
-
113
- // Update the settings object if it has a save method
114
- if (configManager.saveSettings) {
115
- await configManager.saveSettings({ ...settings, ...initData });
116
- } else if (configManager.saveConfig) {
117
- await configManager.saveConfig({ ...settings, ...initData });
118
- }
119
- }
133
+ async function markAsInitialized(config) {
134
+ const settings = configManager.getConfig ? configManager.getConfig() : {};
135
+ const projectConfigPath = configManager.CONFIG_PATH || path.join(process.cwd(), '.i18ntk-config');
136
+ const configDir = path.dirname(projectConfigPath);
137
+ const initFilePath = path.join(configDir, 'initialization.json');
138
+ const currentVersion = require('../package.json').version;
139
+ const now = new Date().toISOString();
140
+ const sourceDir = config?.sourceDir || settings.sourceDir || './locales';
141
+ const sourceLanguage = config?.sourceLanguage || settings.sourceLanguage || 'en';
142
+
143
+ const initData = {
144
+ initialized: true,
145
+ version: currentVersion,
146
+ timestamp: now,
147
+ sourceDir,
148
+ sourceLanguage,
149
+ detectedLanguage: config?.detectedLanguage || settings.detectedLanguage,
150
+ detectedFramework: config?.detectedFramework || settings.detectedFramework,
151
+ lastUpdated: now
152
+ };
153
+
154
+ ensureDirectory(path.dirname(initFilePath));
155
+ SecurityUtils.safeWriteFileSync(initFilePath, JSON.stringify(initData, null, 2), path.dirname(initFilePath), 'utf8');
156
+
157
+ const mergedConfig = {
158
+ ...settings,
159
+ ...(config || {}),
160
+ sourceDir,
161
+ sourceLanguage,
162
+ version: currentVersion,
163
+ setup: {
164
+ ...(settings.setup || {}),
165
+ completed: true,
166
+ completedAt: now,
167
+ version: currentVersion,
168
+ setupId: settings.setup?.setupId || `setup_${Date.now()}`
169
+ }
170
+ };
171
+
172
+ if (configManager.saveConfig) {
173
+ await configManager.saveConfig(mergedConfig);
174
+ } else {
175
+ SecurityUtils.safeWriteFileSync(projectConfigPath, JSON.stringify(mergedConfig, null, 2), path.dirname(projectConfigPath), 'utf8');
176
+ }
177
+ }
120
178
 
121
179
  module.exports = {
122
180
  checkInitialized,
@@ -1,99 +1,99 @@
1
- /**
2
- * JSON Output Utility for i18ntk commands
3
- * Provides consistent machine-readable output format for CI/CD integration
4
- */
5
-
6
- class JsonOutput {
7
- constructor(command) {
8
- this.command = command;
9
- this.version = this.getPackageVersion();
10
- this.startTime = Date.now();
11
- this.data = {
12
- command: this.command,
13
- version: this.version,
14
- status: 'ok',
15
- stats: {},
16
- issues: [],
17
- metadata: {
18
- timestamp: new Date().toISOString(),
19
- duration: 0
20
- }
21
- };
22
- }
23
-
24
- getPackageVersion() {
25
- try {
26
- const packageJson = require('../package.json');
27
- return packageJson.version;
28
- } catch (error) {
29
- return '1.8.3';
30
- }
31
- }
32
-
33
- /**
34
- * Set the overall status
35
- * @param {'ok'|'warn'|'error'} status
36
- */
37
- setStatus(status) {
38
- this.data.status = status;
39
- }
40
-
41
- /**
42
- * Add statistics to the output
43
- * @param {Object} stats
44
- */
45
- setStats(stats) {
46
- this.data.stats = { ...this.data.stats, ...stats };
47
- }
48
-
49
- /**
50
- * Add an issue to the output
51
- * @param {Object} issue
52
- */
53
- addIssue(issue) {
54
- this.data.issues.push({
55
- file: issue.file || '',
56
- key: issue.key || '',
57
- type: issue.type || 'unknown',
58
- message: issue.message || '',
59
- severity: issue.severity || 'info'
60
- });
61
- }
62
-
63
- /**
64
- * Add metadata information
65
- * @param {Object} metadata
66
- */
67
- addMetadata(metadata) {
68
- this.data.metadata = { ...this.data.metadata, ...metadata };
69
- }
70
-
71
- /**
72
- * Finalize and output the JSON
73
- */
74
- output() {
75
- this.data.metadata.duration = Date.now() - this.startTime;
76
-
77
- if (process.env.NODE_ENV !== 'test') {
78
- console.log(JSON.stringify(this.data, null, 2));
79
- }
80
-
81
- return this.data;
82
- }
83
-
84
- /**
85
- * Output error in JSON format
86
- * @param {Error} error
87
- */
88
- outputError(error) {
89
- this.setStatus('error');
90
- this.addIssue({
91
- type: 'error',
92
- message: error.message,
93
- severity: 'error'
94
- });
95
- this.output();
96
- }
97
- }
98
-
1
+ /**
2
+ * JSON Output Utility for i18ntk commands
3
+ * Provides consistent machine-readable output format for CI/CD integration
4
+ */
5
+
6
+ class JsonOutput {
7
+ constructor(command) {
8
+ this.command = command;
9
+ this.version = this.getPackageVersion();
10
+ this.startTime = Date.now();
11
+ this.data = {
12
+ command: this.command,
13
+ version: this.version,
14
+ status: 'ok',
15
+ stats: {},
16
+ issues: [],
17
+ metadata: {
18
+ timestamp: new Date().toISOString(),
19
+ duration: 0
20
+ }
21
+ };
22
+ }
23
+
24
+ getPackageVersion() {
25
+ try {
26
+ const packageJson = require('../package.json');
27
+ return packageJson.version;
28
+ } catch (error) {
29
+ return '1.8.3';
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Set the overall status
35
+ * @param {'ok'|'warn'|'error'} status
36
+ */
37
+ setStatus(status) {
38
+ this.data.status = status;
39
+ }
40
+
41
+ /**
42
+ * Add statistics to the output
43
+ * @param {Object} stats
44
+ */
45
+ setStats(stats) {
46
+ this.data.stats = { ...this.data.stats, ...stats };
47
+ }
48
+
49
+ /**
50
+ * Add an issue to the output
51
+ * @param {Object} issue
52
+ */
53
+ addIssue(issue) {
54
+ this.data.issues.push({
55
+ file: issue.file || '',
56
+ key: issue.key || '',
57
+ type: issue.type || 'unknown',
58
+ message: issue.message || '',
59
+ severity: issue.severity || 'info'
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Add metadata information
65
+ * @param {Object} metadata
66
+ */
67
+ addMetadata(metadata) {
68
+ this.data.metadata = { ...this.data.metadata, ...metadata };
69
+ }
70
+
71
+ /**
72
+ * Finalize and output the JSON
73
+ */
74
+ output() {
75
+ this.data.metadata.duration = Date.now() - this.startTime;
76
+
77
+ if (process.env.NODE_ENV !== 'test') {
78
+ console.log(JSON.stringify(this.data, null, 2));
79
+ }
80
+
81
+ return this.data;
82
+ }
83
+
84
+ /**
85
+ * Output error in JSON format
86
+ * @param {Error} error
87
+ */
88
+ outputError(error) {
89
+ this.setStatus('error');
90
+ this.addIssue({
91
+ type: 'error',
92
+ message: error.message,
93
+ severity: 'error'
94
+ });
95
+ this.output();
96
+ }
97
+ }
98
+
99
99
  module.exports = JsonOutput;
package/utils/logger.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const colors = require('./colors-new');
2
+ const { envManager } = require('./env-manager');
2
3
 
3
4
  // Enhanced logger with TTY detection and proper stream handling
4
5
  const logger = {
@@ -44,9 +45,12 @@ const logger = {
44
45
  logger.log(output, colors.blue);
45
46
  },
46
47
 
47
- // Debug logging (only when DEBUG env var is set)
48
+ // Debug logging (only when DEBUG env var is set or log level is debug)
48
49
  debug: (message) => {
49
- if (process.env.DEBUG) {
50
+ const logLevel = envManager.get('I18NTK_LOG_LEVEL');
51
+ const debugEnabled = process.env.DEBUG || logLevel === 'debug';
52
+
53
+ if (debugEnabled) {
50
54
  const output = `[DEBUG] ${message}`;
51
55
  if (process.stderr.isTTY) {
52
56
  process.stderr.write(colors.gray(output) + '\n');
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Minimal zero-dependency subset of commander used by i18ntk language CLIs.
3
+ */
4
+
5
+ function toCamelCase(input) {
6
+ return String(input || '').replace(/-([a-z])/g, (_, char) => char.toUpperCase());
7
+ }
8
+
9
+ function parseOptionDefinition(flags, defaultValue) {
10
+ const longMatch = flags.match(/--([a-zA-Z0-9-]+)/);
11
+ const shortMatch = flags.match(/(^|\s)-([a-zA-Z])(\s|,|$)/);
12
+ const hasValue = /<[^>]+>/.test(flags);
13
+ const longName = longMatch ? longMatch[1] : null;
14
+ const shortName = shortMatch ? shortMatch[2] : null;
15
+ const name = toCamelCase(longName || shortName || '');
16
+
17
+ return {
18
+ flags,
19
+ hasValue,
20
+ defaultValue,
21
+ longFlag: longName ? `--${longName}` : null,
22
+ shortFlag: shortName ? `-${shortName}` : null,
23
+ name
24
+ };
25
+ }
26
+
27
+ function parseOptions(args, optionDefs) {
28
+ const options = {};
29
+
30
+ for (const def of optionDefs) {
31
+ if (def.defaultValue !== undefined) {
32
+ options[def.name] = def.defaultValue;
33
+ } else if (!def.hasValue) {
34
+ options[def.name] = false;
35
+ }
36
+ }
37
+
38
+ for (let i = 0; i < args.length; i++) {
39
+ const arg = args[i];
40
+ if (!arg || !arg.startsWith('-')) {
41
+ continue;
42
+ }
43
+
44
+ let matchedDef = null;
45
+ let inlineValue;
46
+
47
+ if (arg.startsWith('--')) {
48
+ const [flag, value] = arg.split('=', 2);
49
+ matchedDef = optionDefs.find(def => def.longFlag === flag);
50
+ inlineValue = value;
51
+ } else {
52
+ matchedDef = optionDefs.find(def => def.shortFlag === arg);
53
+ }
54
+
55
+ if (!matchedDef) {
56
+ continue;
57
+ }
58
+
59
+ if (!matchedDef.hasValue) {
60
+ options[matchedDef.name] = true;
61
+ continue;
62
+ }
63
+
64
+ let value = inlineValue;
65
+ if (value === undefined) {
66
+ const next = args[i + 1];
67
+ if (next !== undefined && !String(next).startsWith('-')) {
68
+ value = next;
69
+ i++;
70
+ }
71
+ }
72
+
73
+ options[matchedDef.name] = value !== undefined ? value : '';
74
+ }
75
+
76
+ return options;
77
+ }
78
+
79
+ class MiniCommand {
80
+ constructor(name) {
81
+ this._name = name;
82
+ this._description = '';
83
+ this._options = [];
84
+ this._action = null;
85
+ }
86
+
87
+ description(text) {
88
+ this._description = text;
89
+ return this;
90
+ }
91
+
92
+ option(flags, _description, defaultValue) {
93
+ this._options.push(parseOptionDefinition(flags, defaultValue));
94
+ return this;
95
+ }
96
+
97
+ action(handler) {
98
+ this._action = handler;
99
+ return this;
100
+ }
101
+
102
+ execute(args) {
103
+ const parsed = parseOptions(args, this._options);
104
+ if (typeof this._action === 'function') {
105
+ const result = this._action(parsed);
106
+ if (result && typeof result.then === 'function') {
107
+ result.catch(error => {
108
+ console.error(error && error.message ? error.message : String(error));
109
+ process.exit(1);
110
+ });
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ class MiniProgram {
117
+ constructor() {
118
+ this._name = '';
119
+ this._description = '';
120
+ this._version = '';
121
+ this._options = [];
122
+ this._commands = [];
123
+ this._opts = {};
124
+ }
125
+
126
+ name(value) {
127
+ this._name = value;
128
+ return this;
129
+ }
130
+
131
+ description(value) {
132
+ this._description = value;
133
+ return this;
134
+ }
135
+
136
+ version(value) {
137
+ this._version = value;
138
+ return this;
139
+ }
140
+
141
+ option(flags, _description, defaultValue) {
142
+ this._options.push(parseOptionDefinition(flags, defaultValue));
143
+ return this;
144
+ }
145
+
146
+ command(name) {
147
+ const command = new MiniCommand(name);
148
+ this._commands.push(command);
149
+ return command;
150
+ }
151
+
152
+ opts() {
153
+ return { ...this._opts };
154
+ }
155
+
156
+ parse(argv = process.argv) {
157
+ const args = argv.slice(2);
158
+
159
+ if (this._commands.length > 0 && args.length > 0 && !args[0].startsWith('-')) {
160
+ const command = this._commands.find(item => item._name === args[0]);
161
+ if (command) {
162
+ command.execute(args.slice(1));
163
+ return this;
164
+ }
165
+ }
166
+
167
+ this._opts = parseOptions(args, this._options);
168
+ return this;
169
+ }
170
+ }
171
+
172
+ function createProgram() {
173
+ return new MiniProgram();
174
+ }
175
+
176
+ module.exports = {
177
+ createProgram,
178
+ program: createProgram()
179
+ };