@leejungkiin/awkit 1.7.1 β 1.7.5
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 +42 -5
- package/bin/awk.js +1148 -92
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +152 -167
- package/core/GEMINI.md.bak +153 -116
- package/core/GreetingEngine/index.js +225 -0
- package/core/GreetingEngine/locales/en.json +6 -0
- package/core/GreetingEngine/locales/ja.json +6 -0
- package/core/GreetingEngine/locales/vi.json +6 -0
- package/core/GreetingEngine/test.js +37 -0
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/PRD.md +57 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_multi_agent_cli.md +211 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/PRD.v1.md +55 -0
- package/docs/history/PRD.v2.md +57 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/docs/history/memoizedfibonacci_spec.v1.md +127 -0
- package/docs/specs/greetingapp_spec.md +97 -0
- package/docs/specs/memoizedfibonacci_spec.md +211 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +35 -2
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +386 -0
- package/scripts/qwen-exec.js +147 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +71 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/codex-conductor/SKILL.md +25 -1
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/qwen-conductor/SKILL.md +100 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/multi-agent-pipeline.md +52 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- package/workflows/roles/safety-router.md +34 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const C = {
|
|
5
|
+
cyan: '\x1b[36m',
|
|
6
|
+
green: '\x1b[32m',
|
|
7
|
+
yellow: '\x1b[33m',
|
|
8
|
+
red: '\x1b[31m',
|
|
9
|
+
gray: '\x1b[90m',
|
|
10
|
+
bold: '\x1b[1m',
|
|
11
|
+
reset: '\x1b[0m'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function log(msg) { console.log(msg); }
|
|
15
|
+
function ok(msg) { log(`${C.green}β ${msg}${C.reset}`); }
|
|
16
|
+
function warn(msg) { log(`${C.yellow}β ${msg}${C.reset}`); }
|
|
17
|
+
function err(msg) { log(`${C.red}β ${msg}${C.reset}`); }
|
|
18
|
+
function dim(msg) { log(`${C.gray}${msg}${C.reset}`); }
|
|
19
|
+
|
|
20
|
+
// Helper to set nested property in object (like lodash.set)
|
|
21
|
+
function setNested(obj, pathStr, value) {
|
|
22
|
+
const parts = pathStr.split('.');
|
|
23
|
+
let current = obj;
|
|
24
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
25
|
+
const part = parts[i];
|
|
26
|
+
if (current[part] === undefined || typeof current[part] !== 'object') {
|
|
27
|
+
current[part] = {};
|
|
28
|
+
}
|
|
29
|
+
current = current[part];
|
|
30
|
+
}
|
|
31
|
+
current[parts[parts.length - 1]] = value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Helper to get nested property in object
|
|
35
|
+
function getNested(obj, pathStr) {
|
|
36
|
+
const parts = pathStr.split('.');
|
|
37
|
+
let current = obj;
|
|
38
|
+
for (const part of parts) {
|
|
39
|
+
if (current === null || current === undefined) return undefined;
|
|
40
|
+
current = current[part];
|
|
41
|
+
}
|
|
42
|
+
return current;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Recursively find all nested keys and values in an object
|
|
46
|
+
function flattenObject(obj, prefix = '') {
|
|
47
|
+
let results = {};
|
|
48
|
+
for (const k in obj) {
|
|
49
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
50
|
+
const keyName = prefix ? `${prefix}.${k}` : k;
|
|
51
|
+
if (obj[k] !== null && typeof obj[k] === 'object' && !Array.isArray(obj[k])) {
|
|
52
|
+
Object.assign(results, flattenObject(obj[k], keyName));
|
|
53
|
+
} else {
|
|
54
|
+
results[keyName] = obj[k];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Format Javascript object as clean JS/TS string with sorted keys
|
|
62
|
+
function formatJsObject(obj, indent = 2) {
|
|
63
|
+
const spaces = ' '.repeat(indent);
|
|
64
|
+
if (Array.isArray(obj)) {
|
|
65
|
+
return '[\n' + obj.map(item => spaces + formatJsObject(item, indent + 2)).join(',\n') + '\n' + ' '.repeat(indent - 2) + ']';
|
|
66
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
67
|
+
const keys = Object.keys(obj).sort();
|
|
68
|
+
const lines = keys.map(k => {
|
|
69
|
+
const keyStr = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k);
|
|
70
|
+
return `${spaces}${keyStr}: ${formatJsObject(obj[k], indent + 2)}`;
|
|
71
|
+
});
|
|
72
|
+
return '{\n' + lines.join(',\n') + '\n' + ' '.repeat(indent - 2) + '}';
|
|
73
|
+
} else {
|
|
74
|
+
return JSON.stringify(obj);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Find translation files in target directories, avoiding heavy system folders
|
|
79
|
+
function findTranslationFiles(dir) {
|
|
80
|
+
const filesList = [];
|
|
81
|
+
const ignoreList = ['node_modules', '.git', '.gitnexus', 'dist', 'build', '.expo', 'ios', 'android', 'web-build'];
|
|
82
|
+
|
|
83
|
+
function traverse(currentDir) {
|
|
84
|
+
let list;
|
|
85
|
+
try {
|
|
86
|
+
list = fs.readdirSync(currentDir);
|
|
87
|
+
} catch (_) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const file of list) {
|
|
92
|
+
const absPath = path.join(currentDir, file);
|
|
93
|
+
let stat;
|
|
94
|
+
try {
|
|
95
|
+
stat = fs.statSync(absPath);
|
|
96
|
+
} catch (_) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (stat.isDirectory()) {
|
|
101
|
+
if (ignoreList.includes(file)) continue;
|
|
102
|
+
traverse(absPath);
|
|
103
|
+
} else if (stat.isFile()) {
|
|
104
|
+
const ext = path.extname(file).toLowerCase();
|
|
105
|
+
const nameLower = file.toLowerCase();
|
|
106
|
+
|
|
107
|
+
// Look for translations.* or locale files like en.json, vi.json
|
|
108
|
+
const isTranslationFile =
|
|
109
|
+
(nameLower.includes('translation') && (ext === '.json' || ext === '.ts' || ext === '.js')) ||
|
|
110
|
+
(ext === '.json' && (nameLower === 'en.json' || nameLower === 'vi.json' || nameLower === 'ja.json'));
|
|
111
|
+
|
|
112
|
+
if (isTranslationFile) {
|
|
113
|
+
filesList.push(absPath);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
traverse(dir);
|
|
120
|
+
return filesList;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Read and parse translation file safely
|
|
124
|
+
function readTranslationFile(filePath) {
|
|
125
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
126
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
127
|
+
|
|
128
|
+
if (ext === '.json') {
|
|
129
|
+
try {
|
|
130
|
+
return { type: 'json', data: JSON.parse(content), raw: content };
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new Error(`Failed to parse JSON file ${filePath}: ${e.message}`);
|
|
133
|
+
}
|
|
134
|
+
} else if (ext === '.ts' || ext === '.js') {
|
|
135
|
+
// Find the outer curly braces block
|
|
136
|
+
const braceStart = content.indexOf('{');
|
|
137
|
+
const braceEnd = content.lastIndexOf('}');
|
|
138
|
+
if (braceStart === -1 || braceEnd === -1 || braceEnd < braceStart) {
|
|
139
|
+
throw new Error(`Could not locate object braces in JS/TS file: ${filePath}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const objectStr = content.substring(braceStart, braceEnd + 1);
|
|
143
|
+
try {
|
|
144
|
+
// Safe evaluation of object content
|
|
145
|
+
const data = new Function(`return (${objectStr})`)();
|
|
146
|
+
return {
|
|
147
|
+
type: 'js',
|
|
148
|
+
data,
|
|
149
|
+
prefix: content.substring(0, braceStart),
|
|
150
|
+
suffix: content.substring(braceEnd + 1),
|
|
151
|
+
raw: content
|
|
152
|
+
};
|
|
153
|
+
} catch (e) {
|
|
154
|
+
throw new Error(`Failed to parse JS/TS object in ${filePath}: ${e.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw new Error(`Unsupported translation file type: ${ext}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Write translation file safely
|
|
162
|
+
function writeTranslationFile(filePath, fileInfo) {
|
|
163
|
+
if (fileInfo.type === 'json') {
|
|
164
|
+
// Sort keys recursively
|
|
165
|
+
const sorted = sortObjectKeys(fileInfo.data);
|
|
166
|
+
fs.writeFileSync(filePath, JSON.stringify(sorted, null, 2) + '\n', 'utf8');
|
|
167
|
+
} else if (fileInfo.type === 'js') {
|
|
168
|
+
const sorted = sortObjectKeys(fileInfo.data);
|
|
169
|
+
const formatted = formatJsObject(sorted);
|
|
170
|
+
fs.writeFileSync(filePath, `${fileInfo.prefix}${formatted}${fileInfo.suffix}`, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Sort object keys recursively
|
|
175
|
+
function sortObjectKeys(obj) {
|
|
176
|
+
if (Array.isArray(obj)) {
|
|
177
|
+
return obj.map(sortObjectKeys);
|
|
178
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
179
|
+
const sorted = {};
|
|
180
|
+
Object.keys(obj).sort().forEach(key => {
|
|
181
|
+
sorted[key] = sortObjectKeys(obj[key]);
|
|
182
|
+
});
|
|
183
|
+
return sorted;
|
|
184
|
+
}
|
|
185
|
+
return obj;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// CLI implementation
|
|
189
|
+
async function cmdI18n(args) {
|
|
190
|
+
const subCmd = args[0] || 'help';
|
|
191
|
+
|
|
192
|
+
if (subCmd === 'help' || subCmd === '--help' || subCmd === '-h') {
|
|
193
|
+
log('');
|
|
194
|
+
log(`${C.cyan}${C.bold}π awkit i18n β Language & Translation Workflow Helper${C.reset}`);
|
|
195
|
+
log(`${C.gray}${'β'.repeat(56)}${C.reset}`);
|
|
196
|
+
log(`Manage your localization and translation files cleanly.`);
|
|
197
|
+
log('');
|
|
198
|
+
log(`${C.bold}Commands:${C.reset}`);
|
|
199
|
+
log(` ${C.green}add${C.reset} --key <key> [--en <val>] [--vi <val>] ...`);
|
|
200
|
+
log(` Add a translation key with values across files.`);
|
|
201
|
+
log(` ${C.green}search${C.reset} <query> (or find)`);
|
|
202
|
+
log(` Search for translation keys or values containing query.`);
|
|
203
|
+
log(` ${C.green}sync${C.reset}`);
|
|
204
|
+
log(` Alphabetically sort and synchronize keys across all translation files.`);
|
|
205
|
+
log(` ${C.green}list${C.reset}`);
|
|
206
|
+
log(` List all translations mapped in the project.`);
|
|
207
|
+
log('');
|
|
208
|
+
log(`${C.bold}Examples:${C.reset}`);
|
|
209
|
+
log(` awkit i18n add --key "auth.login.title" --en "Log In" --vi "ΔΔng nhαΊp"`);
|
|
210
|
+
log(` awkit i18n search "Welcome"`);
|
|
211
|
+
log('');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const cwd = process.cwd();
|
|
216
|
+
|
|
217
|
+
// Read files configured in .project-identity or auto-detect
|
|
218
|
+
let configFiles = [];
|
|
219
|
+
const identityPath = path.join(cwd, '.project-identity');
|
|
220
|
+
if (fs.existsSync(identityPath)) {
|
|
221
|
+
try {
|
|
222
|
+
const identity = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
|
|
223
|
+
if (identity.i18n && Array.isArray(identity.i18n.files)) {
|
|
224
|
+
configFiles = identity.i18n.files.map(f => path.resolve(cwd, f));
|
|
225
|
+
}
|
|
226
|
+
} catch (_) {}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const files = configFiles.length > 0 ? configFiles : findTranslationFiles(cwd);
|
|
230
|
+
|
|
231
|
+
if (files.length === 0) {
|
|
232
|
+
err('No translation files found in this repository.');
|
|
233
|
+
dim('Either configure "i18n.files" in .project-identity or place translation files matching *translation*.{json,ts,js} or en.json/vi.json.');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
switch (subCmd) {
|
|
238
|
+
case 'add': {
|
|
239
|
+
// Parse arguments
|
|
240
|
+
let key = '';
|
|
241
|
+
const translations = {};
|
|
242
|
+
|
|
243
|
+
for (let i = 1; i < args.length; i++) {
|
|
244
|
+
if (args[i] === '--key') {
|
|
245
|
+
key = args[++i];
|
|
246
|
+
} else if (args[i].startsWith('--')) {
|
|
247
|
+
const lang = args[i].substring(2);
|
|
248
|
+
translations[lang] = args[++i];
|
|
249
|
+
} else if (args[i].includes(':')) {
|
|
250
|
+
// Alternative syntax: en:Hello
|
|
251
|
+
const parts = args[i].split(':');
|
|
252
|
+
const lang = parts[0];
|
|
253
|
+
const val = parts.slice(1).join(':');
|
|
254
|
+
translations[lang] = val;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!key) {
|
|
259
|
+
err('Missing translation key. Specify using --key <key_name>');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (Object.keys(translations).length === 0) {
|
|
264
|
+
err('No translations specified. E.g. --en "Hello" --vi "Xin chΓ o"');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
info(`Adding key "${key}" to translation files...`);
|
|
269
|
+
|
|
270
|
+
for (const file of files) {
|
|
271
|
+
const basename = path.basename(file);
|
|
272
|
+
const fileLang = basename.split('.')[0].toLowerCase(); // e.g. en, vi, translations
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const fileInfo = readTranslationFile(file);
|
|
276
|
+
|
|
277
|
+
// Match translation value. If file name is specific like en.json or vi.json, inject specific lang.
|
|
278
|
+
// If it is translations.ts, inject nested object languages.
|
|
279
|
+
if (fileLang === 'en' || fileLang === 'vi') {
|
|
280
|
+
const val = translations[fileLang] || translations['en'] || Object.values(translations)[0];
|
|
281
|
+
setNested(fileInfo.data, key, val);
|
|
282
|
+
} else {
|
|
283
|
+
// Mixed translation file (e.g. translations.ts), set language key paths
|
|
284
|
+
for (const lang in translations) {
|
|
285
|
+
if (fileInfo.data[lang] !== undefined) {
|
|
286
|
+
setNested(fileInfo.data[lang], key, translations[lang]);
|
|
287
|
+
} else {
|
|
288
|
+
// Fallback directly on root
|
|
289
|
+
setNested(fileInfo.data, `${lang}.${key}`, translations[lang]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
writeTranslationFile(file, fileInfo);
|
|
295
|
+
ok(`Updated ${basename}`);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
err(`Failed updating ${basename}: ${e.message}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
case 'search':
|
|
304
|
+
case 'find': {
|
|
305
|
+
const query = args.slice(1).join(' ').toLowerCase();
|
|
306
|
+
if (!query) {
|
|
307
|
+
err('Please specify a query to search.');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
log(`\nπ Searching for "${query}" in translations...`);
|
|
312
|
+
log(`${C.gray}${'β'.repeat(56)}${C.reset}`);
|
|
313
|
+
|
|
314
|
+
let matchesFound = 0;
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
try {
|
|
317
|
+
const fileInfo = readTranslationFile(file);
|
|
318
|
+
const flattened = flattenObject(fileInfo.data);
|
|
319
|
+
|
|
320
|
+
for (const key in flattened) {
|
|
321
|
+
const val = String(flattened[key]);
|
|
322
|
+
if (key.toLowerCase().includes(query) || val.toLowerCase().includes(query)) {
|
|
323
|
+
log(` [${C.cyan}${path.basename(file)}${C.reset}] ${C.green}${key}${C.reset}: "${val}"`);
|
|
324
|
+
matchesFound++;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch (e) {
|
|
328
|
+
err(`Error scanning ${path.basename(file)}: ${e.message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (matchesFound === 0) {
|
|
332
|
+
warn('No matches found.');
|
|
333
|
+
} else {
|
|
334
|
+
log(`\nFound ${matchesFound} matches.`);
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
case 'sync': {
|
|
340
|
+
info('Synchronizing and formatting all translation files...');
|
|
341
|
+
for (const file of files) {
|
|
342
|
+
try {
|
|
343
|
+
const fileInfo = readTranslationFile(file);
|
|
344
|
+
writeTranslationFile(file, fileInfo);
|
|
345
|
+
ok(`Synced & Sorted ${path.basename(file)}`);
|
|
346
|
+
} catch (e) {
|
|
347
|
+
err(`Failed to sync ${path.basename(file)}: ${e.message}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
case 'list': {
|
|
354
|
+
log(`\nπ Listing all translation entries:`);
|
|
355
|
+
log(`${C.gray}${'β'.repeat(56)}${C.reset}`);
|
|
356
|
+
for (const file of files) {
|
|
357
|
+
try {
|
|
358
|
+
const fileInfo = readTranslationFile(file);
|
|
359
|
+
const flattened = flattenObject(fileInfo.data);
|
|
360
|
+
log(`\n${C.cyan}${path.basename(file)}${C.reset}:`);
|
|
361
|
+
for (const key in flattened) {
|
|
362
|
+
log(` ${C.green}${key}${C.reset}: "${flattened[key]}"`);
|
|
363
|
+
}
|
|
364
|
+
} catch (e) {
|
|
365
|
+
err(`Error reading ${path.basename(file)}: ${e.message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
default:
|
|
372
|
+
err(`Unknown command: ${subCmd}`);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function info(msg) { log(`${C.cyan}βΉ ${msg}${C.reset}`); }
|
|
378
|
+
|
|
379
|
+
module.exports = {
|
|
380
|
+
cmdI18n
|
|
381
|
+
};
|