pabal-resource-mcp 1.5.8 → 1.5.10
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/bin/mcp-server.js +24 -10
- package/dist/chunk-AIFJ4O2O.js +396 -0
- package/dist/chunk-X723BZS5.js +396 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
getPushDataDir,
|
|
12
12
|
loadAsoFromConfig,
|
|
13
13
|
saveAsoToAsoDir
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-AIFJ4O2O.js";
|
|
15
15
|
import {
|
|
16
16
|
DEFAULT_LOCALE,
|
|
17
17
|
appStoreToUnified,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
ListToolsRequestSchema
|
|
33
33
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
34
34
|
|
|
35
|
-
// src/tools/aso/
|
|
35
|
+
// src/tools/aso/aso-to-public.ts
|
|
36
36
|
import { z } from "zod";
|
|
37
37
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
38
38
|
|
|
@@ -199,7 +199,7 @@ Copy screenshots from these directories to public/products/[slug]/screenshots/ a
|
|
|
199
199
|
**Important:** Return only valid JSON, without any additional explanation.`;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
// src/tools/aso/
|
|
202
|
+
// src/tools/aso/aso-to-public.ts
|
|
203
203
|
var toJsonSchema = zodToJsonSchema;
|
|
204
204
|
var asoToPublicInputSchema = z.object({
|
|
205
205
|
slug: z.string().describe("Product slug")
|
|
@@ -348,7 +348,7 @@ Next steps (manual):
|
|
|
348
348
|
};
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
// src/tools/aso/
|
|
351
|
+
// src/tools/aso/public-to-aso.ts
|
|
352
352
|
import { z as z2 } from "zod";
|
|
353
353
|
import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
|
|
354
354
|
import path4 from "path";
|
|
@@ -718,7 +718,7 @@ function convertToMultilingual(data, locale) {
|
|
|
718
718
|
};
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
-
// src/tools/aso/
|
|
721
|
+
// src/tools/aso/public-to-aso.ts
|
|
722
722
|
import fs4 from "fs";
|
|
723
723
|
var toJsonSchema2 = zodToJsonSchema2;
|
|
724
724
|
var publicToAsoInputSchema = z2.object({
|
|
@@ -749,6 +749,10 @@ async function downloadScreenshotsToAsoDir(slug, asoData) {
|
|
|
749
749
|
continue;
|
|
750
750
|
}
|
|
751
751
|
const localeData = googlePlayData.locales[unifiedLocale];
|
|
752
|
+
const hasAnyScreenshots = localeData.screenshots?.phone && localeData.screenshots.phone.length > 0 || localeData.screenshots?.tablet7 && localeData.screenshots.tablet7.length > 0 || localeData.screenshots?.tablet10 && localeData.screenshots.tablet10.length > 0 || localeData.screenshots?.tablet && localeData.screenshots.tablet.length > 0 || localeData.featureGraphic;
|
|
753
|
+
if (!hasAnyScreenshots) {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
752
756
|
const asoDir = path4.join(
|
|
753
757
|
productStoreRoot,
|
|
754
758
|
"google-play",
|
|
@@ -829,6 +833,10 @@ async function downloadScreenshotsToAsoDir(slug, asoData) {
|
|
|
829
833
|
continue;
|
|
830
834
|
}
|
|
831
835
|
const localeData = appStoreData.locales[unifiedLocale];
|
|
836
|
+
const hasAnyScreenshots = localeData.screenshots?.iphone65 && localeData.screenshots.iphone65.length > 0 || localeData.screenshots?.ipadPro129 && localeData.screenshots.ipadPro129.length > 0;
|
|
837
|
+
if (!hasAnyScreenshots) {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
832
840
|
const asoDir = path4.join(
|
|
833
841
|
productStoreRoot,
|
|
834
842
|
"app-store",
|
|
@@ -1044,7 +1052,7 @@ Sanitized invalid characters:
|
|
|
1044
1052
|
};
|
|
1045
1053
|
}
|
|
1046
1054
|
|
|
1047
|
-
// src/tools/aso/improve.ts
|
|
1055
|
+
// src/tools/aso/improve-public.ts
|
|
1048
1056
|
import { z as z3 } from "zod";
|
|
1049
1057
|
import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
|
|
1050
1058
|
|
|
@@ -2331,7 +2339,7 @@ function loadKeywordResearchForLocale(slug, locale) {
|
|
|
2331
2339
|
return { entries: [], sections: [], researchDir, isFallback: false };
|
|
2332
2340
|
}
|
|
2333
2341
|
|
|
2334
|
-
// src/tools/aso/improve.ts
|
|
2342
|
+
// src/tools/aso/improve-public.ts
|
|
2335
2343
|
var toJsonSchema3 = zodToJsonSchema3;
|
|
2336
2344
|
var improvePublicInputSchema = z3.object({
|
|
2337
2345
|
slug: z3.string().describe("Product slug"),
|
|
@@ -2527,7 +2535,7 @@ async function handleImprovePublic(input) {
|
|
|
2527
2535
|
throw new Error(`Invalid stage: ${stage}. Must be "1", "2", or "both".`);
|
|
2528
2536
|
}
|
|
2529
2537
|
|
|
2530
|
-
// src/tools/aso/validate.ts
|
|
2538
|
+
// src/tools/aso/validate-aso.ts
|
|
2531
2539
|
import { z as z4 } from "zod";
|
|
2532
2540
|
import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
|
|
2533
2541
|
var toJsonSchema4 = zodToJsonSchema4;
|
|
@@ -3675,8 +3683,9 @@ function prepareLocalesForTranslation(locales, primaryLocale) {
|
|
|
3675
3683
|
}
|
|
3676
3684
|
geminiLocalesNeeded.add(geminiLocale);
|
|
3677
3685
|
const existing = localeMapping.get(geminiLocale) || [];
|
|
3678
|
-
|
|
3679
|
-
|
|
3686
|
+
const unifiedLocale = locale;
|
|
3687
|
+
if (!existing.includes(unifiedLocale)) {
|
|
3688
|
+
existing.push(unifiedLocale);
|
|
3680
3689
|
}
|
|
3681
3690
|
localeMapping.set(geminiLocale, existing);
|
|
3682
3691
|
}
|
|
@@ -4315,6 +4324,11 @@ async function handleInitProject(input) {
|
|
|
4315
4324
|
"Use pabal-resource-mcp 'public-to-aso' with dryRun=true to validate structure and build pushData before uploading via store tooling."
|
|
4316
4325
|
);
|
|
4317
4326
|
lines.push("");
|
|
4327
|
+
lines.push("Step 4: Create Privacy Policy and Terms of Use (if needed)");
|
|
4328
|
+
lines.push(
|
|
4329
|
+
"If Privacy Policy and Terms of Use need to be created, generate them at [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/) and save as markdown files."
|
|
4330
|
+
);
|
|
4331
|
+
lines.push("");
|
|
4318
4332
|
lines.push("Notes:");
|
|
4319
4333
|
lines.push(
|
|
4320
4334
|
"- This tool is read-only; it does not write files or call pabal-store-api-mcp."
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_LOCALE,
|
|
3
|
+
isAppStoreLocale,
|
|
4
|
+
isGooglePlayLocale,
|
|
5
|
+
isSupportedLocale
|
|
6
|
+
} from "./chunk-BOWRBVVV.js";
|
|
7
|
+
|
|
8
|
+
// src/utils/config.util.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
12
|
+
function getAsoDataDir() {
|
|
13
|
+
const configPath = path.join(
|
|
14
|
+
os.homedir(),
|
|
15
|
+
".config",
|
|
16
|
+
"pabal-mcp",
|
|
17
|
+
"config.json"
|
|
18
|
+
);
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Config file not found at ${configPath}. Please create the config file and set the 'dataDir' property to specify the ASO data directory.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
26
|
+
const config = JSON.parse(configContent);
|
|
27
|
+
if (!config.dataDir) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`'dataDir' property is not set in ${configPath}. Please set 'dataDir' to specify the ASO data directory.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (path.isAbsolute(config.dataDir)) {
|
|
33
|
+
return config.dataDir;
|
|
34
|
+
}
|
|
35
|
+
return path.resolve(os.homedir(), config.dataDir);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error instanceof Error && error.message.includes("dataDir")) {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to read config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getPullDataDir() {
|
|
46
|
+
return path.join(getAsoDataDir(), ".aso", "pullData");
|
|
47
|
+
}
|
|
48
|
+
function getPushDataDir() {
|
|
49
|
+
return path.join(getAsoDataDir(), ".aso", "pushData");
|
|
50
|
+
}
|
|
51
|
+
function getPublicDir() {
|
|
52
|
+
return path.join(getAsoDataDir(), "public");
|
|
53
|
+
}
|
|
54
|
+
function getKeywordResearchDir() {
|
|
55
|
+
return path.join(getAsoDataDir(), ".aso", "keywordResearch");
|
|
56
|
+
}
|
|
57
|
+
function getProductsDir() {
|
|
58
|
+
return path.join(getPublicDir(), "products");
|
|
59
|
+
}
|
|
60
|
+
function loadConfig() {
|
|
61
|
+
const configPath = path.join(
|
|
62
|
+
os.homedir(),
|
|
63
|
+
".config",
|
|
64
|
+
"pabal-mcp",
|
|
65
|
+
"config.json"
|
|
66
|
+
);
|
|
67
|
+
if (!fs.existsSync(configPath)) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
72
|
+
return JSON.parse(configContent);
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function getGeminiApiKey() {
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
if (config.gemini?.apiKey) {
|
|
80
|
+
return config.gemini.apiKey;
|
|
81
|
+
}
|
|
82
|
+
const envKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
83
|
+
if (envKey) {
|
|
84
|
+
return envKey;
|
|
85
|
+
}
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Gemini API key not found. Set it in ~/.config/pabal-mcp/config.json under "gemini.apiKey" or use GEMINI_API_KEY environment variable.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/utils/aso-converter.ts
|
|
92
|
+
import fs2 from "fs";
|
|
93
|
+
import path2 from "path";
|
|
94
|
+
function generateFullDescription(localeData, metadata = {}) {
|
|
95
|
+
const { aso, landing } = localeData;
|
|
96
|
+
const template = aso?.template;
|
|
97
|
+
if (!template) {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
const landingFeatures = landing?.features?.items || [];
|
|
101
|
+
const landingScreenshots = landing?.screenshots?.images || [];
|
|
102
|
+
const keyHeading = template.keyFeaturesHeading || "Key Features";
|
|
103
|
+
const featuresHeading = template.featuresHeading || "Additional Features";
|
|
104
|
+
const parts = [template.intro];
|
|
105
|
+
if (landingFeatures.length > 0) {
|
|
106
|
+
parts.push(
|
|
107
|
+
"",
|
|
108
|
+
keyHeading,
|
|
109
|
+
"",
|
|
110
|
+
...landingFeatures.map(
|
|
111
|
+
(feature) => [`\u25B6\uFE0E ${feature.title}`, feature.body || ""].filter(Boolean).join("\n")
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (landingScreenshots.length > 0) {
|
|
116
|
+
parts.push("", featuresHeading, "");
|
|
117
|
+
parts.push(
|
|
118
|
+
...landingScreenshots.map(
|
|
119
|
+
(screenshot) => [`\u25B6\uFE0E ${screenshot.title}`, screenshot.description || ""].filter(Boolean).join("\n")
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
parts.push("", template.outro);
|
|
124
|
+
const includeSupport = template.includeSupportLinks ?? true;
|
|
125
|
+
if (includeSupport) {
|
|
126
|
+
const contactLines = [
|
|
127
|
+
metadata.instagram ? `Instagram: ${metadata.instagram}` : null,
|
|
128
|
+
metadata.contactEmail ? `Email: ${metadata.contactEmail}` : null,
|
|
129
|
+
metadata.termsUrl ? `- Terms of Use: ${metadata.termsUrl}` : null,
|
|
130
|
+
metadata.privacyUrl ? `- Privacy Policy: ${metadata.privacyUrl}` : null
|
|
131
|
+
].filter((line) => line !== null);
|
|
132
|
+
if (contactLines.length > 0) {
|
|
133
|
+
parts.push("", "[Contact & Support]", "", ...contactLines);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return parts.join("\n");
|
|
137
|
+
}
|
|
138
|
+
function loadAsoFromConfig(slug) {
|
|
139
|
+
const productsDir = getProductsDir();
|
|
140
|
+
const configPath = path2.join(productsDir, slug, "config.json");
|
|
141
|
+
console.debug(`[loadAsoFromConfig] Looking for ${slug}:`);
|
|
142
|
+
console.debug(` - productsDir: ${productsDir}`);
|
|
143
|
+
console.debug(` - configPath: ${configPath}`);
|
|
144
|
+
console.debug(` - configPath exists: ${fs2.existsSync(configPath)}`);
|
|
145
|
+
if (!fs2.existsSync(configPath)) {
|
|
146
|
+
console.warn(`[loadAsoFromConfig] Config file not found at ${configPath}`);
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const configContent = fs2.readFileSync(configPath, "utf-8");
|
|
151
|
+
const config = JSON.parse(configContent);
|
|
152
|
+
const localesDir = path2.join(productsDir, slug, "locales");
|
|
153
|
+
console.debug(` - localesDir: ${localesDir}`);
|
|
154
|
+
console.debug(` - localesDir exists: ${fs2.existsSync(localesDir)}`);
|
|
155
|
+
if (!fs2.existsSync(localesDir)) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`[loadAsoFromConfig] Locales directory not found at ${localesDir}`
|
|
158
|
+
);
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
const localeFiles = fs2.readdirSync(localesDir).filter((f) => f.endsWith(".json"));
|
|
162
|
+
const locales = {};
|
|
163
|
+
for (const file of localeFiles) {
|
|
164
|
+
const localeCode = file.replace(".json", "");
|
|
165
|
+
const localePath = path2.join(localesDir, file);
|
|
166
|
+
const localeContent = fs2.readFileSync(localePath, "utf-8");
|
|
167
|
+
locales[localeCode] = JSON.parse(localeContent);
|
|
168
|
+
}
|
|
169
|
+
console.debug(
|
|
170
|
+
` - Found ${Object.keys(locales).length} locale file(s): ${Object.keys(
|
|
171
|
+
locales
|
|
172
|
+
).join(", ")}`
|
|
173
|
+
);
|
|
174
|
+
if (Object.keys(locales).length === 0) {
|
|
175
|
+
console.warn(
|
|
176
|
+
`[loadAsoFromConfig] No locale files found in ${localesDir}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const defaultLocale = config.content?.defaultLocale || DEFAULT_LOCALE;
|
|
180
|
+
const asoData = {};
|
|
181
|
+
if (config.packageName) {
|
|
182
|
+
const googlePlayLocales = {};
|
|
183
|
+
const metadata = config.metadata || {};
|
|
184
|
+
const screenshots = metadata.screenshots || {};
|
|
185
|
+
for (const [locale, localeData] of Object.entries(locales)) {
|
|
186
|
+
if (!isSupportedLocale(locale)) {
|
|
187
|
+
console.debug(
|
|
188
|
+
`Skipping locale ${locale} - not a valid unified locale`
|
|
189
|
+
);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (!isGooglePlayLocale(locale)) {
|
|
193
|
+
console.debug(
|
|
194
|
+
`Skipping locale ${locale} - not supported by Google Play`
|
|
195
|
+
);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const aso = localeData.aso || {};
|
|
199
|
+
if (!aso || !aso.title && !aso.shortDescription) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`Locale ${locale} has no ASO data (title or shortDescription)`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const screenshotsDir = path2.join(productsDir, slug, "screenshots", locale);
|
|
205
|
+
const hasScreenshots = fs2.existsSync(screenshotsDir);
|
|
206
|
+
const localeScreenshots = hasScreenshots ? {
|
|
207
|
+
phone: screenshots.phone?.map(
|
|
208
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
209
|
+
),
|
|
210
|
+
tablet: screenshots.tablet?.map(
|
|
211
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
212
|
+
)
|
|
213
|
+
} : {
|
|
214
|
+
phone: void 0,
|
|
215
|
+
tablet: void 0
|
|
216
|
+
};
|
|
217
|
+
googlePlayLocales[locale] = {
|
|
218
|
+
title: aso.title || "",
|
|
219
|
+
shortDescription: aso.shortDescription || "",
|
|
220
|
+
fullDescription: generateFullDescription(localeData, metadata),
|
|
221
|
+
packageName: config.packageName,
|
|
222
|
+
defaultLanguage: locale,
|
|
223
|
+
screenshots: {
|
|
224
|
+
phone: localeScreenshots.phone || [],
|
|
225
|
+
tablet: localeScreenshots.tablet
|
|
226
|
+
},
|
|
227
|
+
contactEmail: metadata.contactEmail
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const googleLocaleKeys = Object.keys(googlePlayLocales);
|
|
231
|
+
if (googleLocaleKeys.length > 0) {
|
|
232
|
+
const hasConfigDefault = isGooglePlayLocale(defaultLocale) && Boolean(googlePlayLocales[defaultLocale]);
|
|
233
|
+
const resolvedDefault = hasConfigDefault ? defaultLocale : googlePlayLocales[DEFAULT_LOCALE] ? DEFAULT_LOCALE : googleLocaleKeys[0];
|
|
234
|
+
asoData.googlePlay = {
|
|
235
|
+
locales: googlePlayLocales,
|
|
236
|
+
defaultLocale: resolvedDefault
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (config.bundleId) {
|
|
241
|
+
const appStoreLocales = {};
|
|
242
|
+
const metadata = config.metadata || {};
|
|
243
|
+
const screenshots = metadata.screenshots || {};
|
|
244
|
+
for (const [locale, localeData] of Object.entries(locales)) {
|
|
245
|
+
if (!isSupportedLocale(locale)) {
|
|
246
|
+
console.debug(
|
|
247
|
+
`Skipping locale ${locale} - not a valid unified locale`
|
|
248
|
+
);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (!isAppStoreLocale(locale)) {
|
|
252
|
+
console.debug(
|
|
253
|
+
`Skipping locale ${locale} - not supported by App Store`
|
|
254
|
+
);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const aso = localeData.aso || {};
|
|
258
|
+
if (!aso || !aso.title && !aso.shortDescription) {
|
|
259
|
+
console.warn(
|
|
260
|
+
`Locale ${locale} has no ASO data (title or shortDescription)`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const screenshotsDir = path2.join(productsDir, slug, "screenshots", locale);
|
|
264
|
+
const hasScreenshots = fs2.existsSync(screenshotsDir);
|
|
265
|
+
const localeScreenshots = hasScreenshots ? {
|
|
266
|
+
phone: screenshots.phone?.map(
|
|
267
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
268
|
+
),
|
|
269
|
+
tablet: screenshots.tablet?.map(
|
|
270
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
271
|
+
)
|
|
272
|
+
} : {
|
|
273
|
+
phone: void 0,
|
|
274
|
+
tablet: void 0
|
|
275
|
+
};
|
|
276
|
+
appStoreLocales[locale] = {
|
|
277
|
+
name: aso.title || "",
|
|
278
|
+
subtitle: aso.subtitle,
|
|
279
|
+
description: generateFullDescription(localeData, metadata),
|
|
280
|
+
keywords: Array.isArray(aso.keywords) ? aso.keywords.join(", ") : aso.keywords,
|
|
281
|
+
promotionalText: void 0,
|
|
282
|
+
bundleId: config.bundleId,
|
|
283
|
+
locale,
|
|
284
|
+
supportUrl: metadata.supportUrl,
|
|
285
|
+
marketingUrl: metadata.marketingUrl,
|
|
286
|
+
privacyPolicyUrl: metadata.privacyUrl,
|
|
287
|
+
screenshots: {
|
|
288
|
+
// 폰 스크린샷을 iphone65로 매핑
|
|
289
|
+
iphone65: localeScreenshots.phone || [],
|
|
290
|
+
// 태블릿 스크린샷을 ipadPro129로 매핑
|
|
291
|
+
ipadPro129: localeScreenshots.tablet
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const appStoreLocaleKeys = Object.keys(appStoreLocales);
|
|
296
|
+
if (appStoreLocaleKeys.length > 0) {
|
|
297
|
+
const hasConfigDefault = isAppStoreLocale(defaultLocale) && Boolean(appStoreLocales[defaultLocale]);
|
|
298
|
+
const resolvedDefault = hasConfigDefault ? defaultLocale : appStoreLocales[DEFAULT_LOCALE] ? DEFAULT_LOCALE : appStoreLocaleKeys[0];
|
|
299
|
+
asoData.appStore = {
|
|
300
|
+
locales: appStoreLocales,
|
|
301
|
+
defaultLocale: resolvedDefault
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const hasGooglePlay = !!asoData.googlePlay;
|
|
306
|
+
const hasAppStore = !!asoData.appStore;
|
|
307
|
+
console.debug(`[loadAsoFromConfig] Result for ${slug}:`);
|
|
308
|
+
console.debug(
|
|
309
|
+
` - Google Play data: ${hasGooglePlay ? "found" : "not found"}`
|
|
310
|
+
);
|
|
311
|
+
console.debug(` - App Store data: ${hasAppStore ? "found" : "not found"}`);
|
|
312
|
+
if (!hasGooglePlay && !hasAppStore) {
|
|
313
|
+
console.warn(`[loadAsoFromConfig] No ASO data generated for ${slug}`);
|
|
314
|
+
}
|
|
315
|
+
return asoData;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(
|
|
318
|
+
`[loadAsoFromConfig] Failed to load ASO data from config for ${slug}:`,
|
|
319
|
+
error
|
|
320
|
+
);
|
|
321
|
+
return {};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function saveAsoToConfig(slug, config) {
|
|
325
|
+
const productsDir = getProductsDir();
|
|
326
|
+
const configPath = path2.join(productsDir, slug, "config.json");
|
|
327
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
328
|
+
}
|
|
329
|
+
function saveAsoToAsoDir(slug, asoData) {
|
|
330
|
+
const rootDir = getPushDataDir();
|
|
331
|
+
if (asoData.googlePlay) {
|
|
332
|
+
const asoPath = path2.join(
|
|
333
|
+
rootDir,
|
|
334
|
+
"products",
|
|
335
|
+
slug,
|
|
336
|
+
"store",
|
|
337
|
+
"google-play",
|
|
338
|
+
"aso-data.json"
|
|
339
|
+
);
|
|
340
|
+
const dir = path2.dirname(asoPath);
|
|
341
|
+
if (!fs2.existsSync(dir)) {
|
|
342
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
const googlePlayData = asoData.googlePlay;
|
|
345
|
+
const multilingualData = "locales" in googlePlayData ? googlePlayData : {
|
|
346
|
+
locales: {
|
|
347
|
+
[googlePlayData.defaultLanguage || DEFAULT_LOCALE]: googlePlayData
|
|
348
|
+
},
|
|
349
|
+
defaultLocale: googlePlayData.defaultLanguage || DEFAULT_LOCALE
|
|
350
|
+
};
|
|
351
|
+
fs2.writeFileSync(
|
|
352
|
+
asoPath,
|
|
353
|
+
JSON.stringify({ googlePlay: multilingualData }, null, 2) + "\n",
|
|
354
|
+
"utf-8"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (asoData.appStore) {
|
|
358
|
+
const asoPath = path2.join(
|
|
359
|
+
rootDir,
|
|
360
|
+
"products",
|
|
361
|
+
slug,
|
|
362
|
+
"store",
|
|
363
|
+
"app-store",
|
|
364
|
+
"aso-data.json"
|
|
365
|
+
);
|
|
366
|
+
const dir = path2.dirname(asoPath);
|
|
367
|
+
if (!fs2.existsSync(dir)) {
|
|
368
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
const appStoreData = asoData.appStore;
|
|
371
|
+
const multilingualData = "locales" in appStoreData ? appStoreData : {
|
|
372
|
+
locales: {
|
|
373
|
+
[appStoreData.locale || DEFAULT_LOCALE]: appStoreData
|
|
374
|
+
},
|
|
375
|
+
defaultLocale: appStoreData.locale || DEFAULT_LOCALE
|
|
376
|
+
};
|
|
377
|
+
fs2.writeFileSync(
|
|
378
|
+
asoPath,
|
|
379
|
+
JSON.stringify({ appStore: multilingualData }, null, 2) + "\n",
|
|
380
|
+
"utf-8"
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export {
|
|
386
|
+
getAsoDataDir,
|
|
387
|
+
getPullDataDir,
|
|
388
|
+
getPushDataDir,
|
|
389
|
+
getPublicDir,
|
|
390
|
+
getKeywordResearchDir,
|
|
391
|
+
getProductsDir,
|
|
392
|
+
getGeminiApiKey,
|
|
393
|
+
loadAsoFromConfig,
|
|
394
|
+
saveAsoToConfig,
|
|
395
|
+
saveAsoToAsoDir
|
|
396
|
+
};
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_LOCALE,
|
|
3
|
+
isAppStoreLocale,
|
|
4
|
+
isGooglePlayLocale,
|
|
5
|
+
isSupportedLocale
|
|
6
|
+
} from "./chunk-BOWRBVVV.js";
|
|
7
|
+
|
|
8
|
+
// src/utils/config.util.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
12
|
+
function getAsoDataDir() {
|
|
13
|
+
const configPath = path.join(
|
|
14
|
+
os.homedir(),
|
|
15
|
+
".config",
|
|
16
|
+
"pabal-mcp",
|
|
17
|
+
"config.json"
|
|
18
|
+
);
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Config file not found at ${configPath}. Please create the config file and set the 'dataDir' property to specify the ASO data directory.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
26
|
+
const config = JSON.parse(configContent);
|
|
27
|
+
if (!config.dataDir) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`'dataDir' property is not set in ${configPath}. Please set 'dataDir' to specify the ASO data directory.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (path.isAbsolute(config.dataDir)) {
|
|
33
|
+
return config.dataDir;
|
|
34
|
+
}
|
|
35
|
+
return path.resolve(os.homedir(), config.dataDir);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error instanceof Error && error.message.includes("dataDir")) {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to read config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getPullDataDir() {
|
|
46
|
+
return path.join(getAsoDataDir(), ".aso", "pullData");
|
|
47
|
+
}
|
|
48
|
+
function getPushDataDir() {
|
|
49
|
+
return path.join(getAsoDataDir(), ".aso", "pushData");
|
|
50
|
+
}
|
|
51
|
+
function getPublicDir() {
|
|
52
|
+
return path.join(getAsoDataDir(), "public");
|
|
53
|
+
}
|
|
54
|
+
function getKeywordResearchDir() {
|
|
55
|
+
return path.join(getAsoDataDir(), ".aso", "keywordResearch");
|
|
56
|
+
}
|
|
57
|
+
function getProductsDir() {
|
|
58
|
+
return path.join(getPublicDir(), "products");
|
|
59
|
+
}
|
|
60
|
+
function loadConfig() {
|
|
61
|
+
const configPath = path.join(
|
|
62
|
+
os.homedir(),
|
|
63
|
+
".config",
|
|
64
|
+
"pabal-mcp",
|
|
65
|
+
"config.json"
|
|
66
|
+
);
|
|
67
|
+
if (!fs.existsSync(configPath)) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
72
|
+
return JSON.parse(configContent);
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function getGeminiApiKey() {
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
if (config.gemini?.apiKey) {
|
|
80
|
+
return config.gemini.apiKey;
|
|
81
|
+
}
|
|
82
|
+
const envKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
83
|
+
if (envKey) {
|
|
84
|
+
return envKey;
|
|
85
|
+
}
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Gemini API key not found. Set it in ~/.config/pabal-mcp/config.json under "gemini.apiKey" or use GEMINI_API_KEY environment variable.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/utils/aso-converter.ts
|
|
92
|
+
import fs2 from "fs";
|
|
93
|
+
import path2 from "path";
|
|
94
|
+
function generateFullDescription(localeData, metadata = {}) {
|
|
95
|
+
const { aso, landing } = localeData;
|
|
96
|
+
const template = aso?.template;
|
|
97
|
+
if (!template) {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
const landingFeatures = landing?.features?.items || [];
|
|
101
|
+
const landingScreenshots = landing?.screenshots?.images || [];
|
|
102
|
+
const keyHeading = template.keyFeaturesHeading || "Key Features";
|
|
103
|
+
const featuresHeading = template.featuresHeading || "Additional Features";
|
|
104
|
+
const parts = [template.intro];
|
|
105
|
+
if (landingFeatures.length > 0) {
|
|
106
|
+
parts.push(
|
|
107
|
+
"",
|
|
108
|
+
keyHeading,
|
|
109
|
+
"",
|
|
110
|
+
...landingFeatures.map(
|
|
111
|
+
(feature) => [`\u25B6\uFE0E ${feature.title}`, feature.body || ""].filter(Boolean).join("\n")
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (landingScreenshots.length > 0) {
|
|
116
|
+
parts.push("", featuresHeading, "");
|
|
117
|
+
parts.push(
|
|
118
|
+
...landingScreenshots.map(
|
|
119
|
+
(screenshot) => [`\u25B6\uFE0E ${screenshot.title}`, screenshot.description || ""].filter(Boolean).join("\n")
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
parts.push("", template.outro);
|
|
124
|
+
const includeSupport = template.includeSupportLinks ?? true;
|
|
125
|
+
if (includeSupport) {
|
|
126
|
+
const contactLines = [
|
|
127
|
+
metadata.instagram ? `Instagram: ${metadata.instagram}` : null,
|
|
128
|
+
metadata.contactEmail ? `Email: ${metadata.contactEmail}` : null,
|
|
129
|
+
metadata.termsUrl ? `- Terms of Use: ${metadata.termsUrl}` : null,
|
|
130
|
+
metadata.privacyUrl ? `- Privacy Policy: ${metadata.privacyUrl}` : null
|
|
131
|
+
].filter((line) => line !== null);
|
|
132
|
+
if (contactLines.length > 0) {
|
|
133
|
+
parts.push("", "[Contact & Support]", "", ...contactLines);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return parts.join("\n");
|
|
137
|
+
}
|
|
138
|
+
function loadAsoFromConfig(slug) {
|
|
139
|
+
const productsDir = getProductsDir();
|
|
140
|
+
const configPath = path2.join(productsDir, slug, "config.json");
|
|
141
|
+
console.debug(`[loadAsoFromConfig] Looking for ${slug}:`);
|
|
142
|
+
console.debug(` - productsDir: ${productsDir}`);
|
|
143
|
+
console.debug(` - configPath: ${configPath}`);
|
|
144
|
+
console.debug(` - configPath exists: ${fs2.existsSync(configPath)}`);
|
|
145
|
+
if (!fs2.existsSync(configPath)) {
|
|
146
|
+
console.warn(`[loadAsoFromConfig] Config file not found at ${configPath}`);
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const configContent = fs2.readFileSync(configPath, "utf-8");
|
|
151
|
+
const config = JSON.parse(configContent);
|
|
152
|
+
const localesDir = path2.join(productsDir, slug, "locales");
|
|
153
|
+
console.debug(` - localesDir: ${localesDir}`);
|
|
154
|
+
console.debug(` - localesDir exists: ${fs2.existsSync(localesDir)}`);
|
|
155
|
+
if (!fs2.existsSync(localesDir)) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`[loadAsoFromConfig] Locales directory not found at ${localesDir}`
|
|
158
|
+
);
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
const localeFiles = fs2.readdirSync(localesDir).filter((f) => f.endsWith(".json"));
|
|
162
|
+
const locales = {};
|
|
163
|
+
for (const file of localeFiles) {
|
|
164
|
+
const localeCode = file.replace(".json", "");
|
|
165
|
+
const localePath = path2.join(localesDir, file);
|
|
166
|
+
const localeContent = fs2.readFileSync(localePath, "utf-8");
|
|
167
|
+
locales[localeCode] = JSON.parse(localeContent);
|
|
168
|
+
}
|
|
169
|
+
console.debug(
|
|
170
|
+
` - Found ${Object.keys(locales).length} locale file(s): ${Object.keys(
|
|
171
|
+
locales
|
|
172
|
+
).join(", ")}`
|
|
173
|
+
);
|
|
174
|
+
if (Object.keys(locales).length === 0) {
|
|
175
|
+
console.warn(
|
|
176
|
+
`[loadAsoFromConfig] No locale files found in ${localesDir}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const defaultLocale = config.content?.defaultLocale || DEFAULT_LOCALE;
|
|
180
|
+
const asoData = {};
|
|
181
|
+
if (config.packageName) {
|
|
182
|
+
const googlePlayLocales = {};
|
|
183
|
+
const metadata = config.metadata || {};
|
|
184
|
+
const screenshots = metadata.screenshots || {};
|
|
185
|
+
for (const [locale, localeData] of Object.entries(locales)) {
|
|
186
|
+
if (!isSupportedLocale(locale)) {
|
|
187
|
+
console.debug(
|
|
188
|
+
`Skipping locale ${locale} - not a valid unified locale`
|
|
189
|
+
);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (!isGooglePlayLocale(locale)) {
|
|
193
|
+
console.debug(
|
|
194
|
+
`Skipping locale ${locale} - not supported by Google Play`
|
|
195
|
+
);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const aso = localeData.aso || {};
|
|
199
|
+
if (!aso || !aso.title && !aso.shortDescription) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`Locale ${locale} has no ASO data (title or shortDescription)`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const screenshotsDir = path2.join(productsDir, slug, "screenshots", locale);
|
|
205
|
+
const hasScreenshots = fs2.existsSync(screenshotsDir);
|
|
206
|
+
const localeScreenshots = hasScreenshots ? {
|
|
207
|
+
phone: screenshots.phone?.map(
|
|
208
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
209
|
+
),
|
|
210
|
+
tablet: screenshots.tablet?.map(
|
|
211
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
212
|
+
)
|
|
213
|
+
} : {
|
|
214
|
+
phone: [],
|
|
215
|
+
tablet: void 0
|
|
216
|
+
};
|
|
217
|
+
googlePlayLocales[locale] = {
|
|
218
|
+
title: aso.title || "",
|
|
219
|
+
shortDescription: aso.shortDescription || "",
|
|
220
|
+
fullDescription: generateFullDescription(localeData, metadata),
|
|
221
|
+
packageName: config.packageName,
|
|
222
|
+
defaultLanguage: locale,
|
|
223
|
+
screenshots: {
|
|
224
|
+
phone: localeScreenshots.phone || [],
|
|
225
|
+
tablet: localeScreenshots.tablet
|
|
226
|
+
},
|
|
227
|
+
contactEmail: metadata.contactEmail
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const googleLocaleKeys = Object.keys(googlePlayLocales);
|
|
231
|
+
if (googleLocaleKeys.length > 0) {
|
|
232
|
+
const hasConfigDefault = isGooglePlayLocale(defaultLocale) && Boolean(googlePlayLocales[defaultLocale]);
|
|
233
|
+
const resolvedDefault = hasConfigDefault ? defaultLocale : googlePlayLocales[DEFAULT_LOCALE] ? DEFAULT_LOCALE : googleLocaleKeys[0];
|
|
234
|
+
asoData.googlePlay = {
|
|
235
|
+
locales: googlePlayLocales,
|
|
236
|
+
defaultLocale: resolvedDefault
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (config.bundleId) {
|
|
241
|
+
const appStoreLocales = {};
|
|
242
|
+
const metadata = config.metadata || {};
|
|
243
|
+
const screenshots = metadata.screenshots || {};
|
|
244
|
+
for (const [locale, localeData] of Object.entries(locales)) {
|
|
245
|
+
if (!isSupportedLocale(locale)) {
|
|
246
|
+
console.debug(
|
|
247
|
+
`Skipping locale ${locale} - not a valid unified locale`
|
|
248
|
+
);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (!isAppStoreLocale(locale)) {
|
|
252
|
+
console.debug(
|
|
253
|
+
`Skipping locale ${locale} - not supported by App Store`
|
|
254
|
+
);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const aso = localeData.aso || {};
|
|
258
|
+
if (!aso || !aso.title && !aso.shortDescription) {
|
|
259
|
+
console.warn(
|
|
260
|
+
`Locale ${locale} has no ASO data (title or shortDescription)`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const screenshotsDir = path2.join(productsDir, slug, "screenshots", locale);
|
|
264
|
+
const hasScreenshots = fs2.existsSync(screenshotsDir);
|
|
265
|
+
const localeScreenshots = hasScreenshots ? {
|
|
266
|
+
phone: screenshots.phone?.map(
|
|
267
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
268
|
+
),
|
|
269
|
+
tablet: screenshots.tablet?.map(
|
|
270
|
+
(p) => p.replace(/\/screenshots\/[^/]+\//, `/screenshots/${locale}/`)
|
|
271
|
+
)
|
|
272
|
+
} : {
|
|
273
|
+
phone: [],
|
|
274
|
+
tablet: void 0
|
|
275
|
+
};
|
|
276
|
+
appStoreLocales[locale] = {
|
|
277
|
+
name: aso.title || "",
|
|
278
|
+
subtitle: aso.subtitle,
|
|
279
|
+
description: generateFullDescription(localeData, metadata),
|
|
280
|
+
keywords: Array.isArray(aso.keywords) ? aso.keywords.join(", ") : aso.keywords,
|
|
281
|
+
promotionalText: void 0,
|
|
282
|
+
bundleId: config.bundleId,
|
|
283
|
+
locale,
|
|
284
|
+
supportUrl: metadata.supportUrl,
|
|
285
|
+
marketingUrl: metadata.marketingUrl,
|
|
286
|
+
privacyPolicyUrl: metadata.privacyUrl,
|
|
287
|
+
screenshots: {
|
|
288
|
+
// 폰 스크린샷을 iphone65로 매핑
|
|
289
|
+
iphone65: localeScreenshots.phone || [],
|
|
290
|
+
// 태블릿 스크린샷을 ipadPro129로 매핑
|
|
291
|
+
ipadPro129: localeScreenshots.tablet
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const appStoreLocaleKeys = Object.keys(appStoreLocales);
|
|
296
|
+
if (appStoreLocaleKeys.length > 0) {
|
|
297
|
+
const hasConfigDefault = isAppStoreLocale(defaultLocale) && Boolean(appStoreLocales[defaultLocale]);
|
|
298
|
+
const resolvedDefault = hasConfigDefault ? defaultLocale : appStoreLocales[DEFAULT_LOCALE] ? DEFAULT_LOCALE : appStoreLocaleKeys[0];
|
|
299
|
+
asoData.appStore = {
|
|
300
|
+
locales: appStoreLocales,
|
|
301
|
+
defaultLocale: resolvedDefault
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const hasGooglePlay = !!asoData.googlePlay;
|
|
306
|
+
const hasAppStore = !!asoData.appStore;
|
|
307
|
+
console.debug(`[loadAsoFromConfig] Result for ${slug}:`);
|
|
308
|
+
console.debug(
|
|
309
|
+
` - Google Play data: ${hasGooglePlay ? "found" : "not found"}`
|
|
310
|
+
);
|
|
311
|
+
console.debug(` - App Store data: ${hasAppStore ? "found" : "not found"}`);
|
|
312
|
+
if (!hasGooglePlay && !hasAppStore) {
|
|
313
|
+
console.warn(`[loadAsoFromConfig] No ASO data generated for ${slug}`);
|
|
314
|
+
}
|
|
315
|
+
return asoData;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(
|
|
318
|
+
`[loadAsoFromConfig] Failed to load ASO data from config for ${slug}:`,
|
|
319
|
+
error
|
|
320
|
+
);
|
|
321
|
+
return {};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function saveAsoToConfig(slug, config) {
|
|
325
|
+
const productsDir = getProductsDir();
|
|
326
|
+
const configPath = path2.join(productsDir, slug, "config.json");
|
|
327
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
328
|
+
}
|
|
329
|
+
function saveAsoToAsoDir(slug, asoData) {
|
|
330
|
+
const rootDir = getPushDataDir();
|
|
331
|
+
if (asoData.googlePlay) {
|
|
332
|
+
const asoPath = path2.join(
|
|
333
|
+
rootDir,
|
|
334
|
+
"products",
|
|
335
|
+
slug,
|
|
336
|
+
"store",
|
|
337
|
+
"google-play",
|
|
338
|
+
"aso-data.json"
|
|
339
|
+
);
|
|
340
|
+
const dir = path2.dirname(asoPath);
|
|
341
|
+
if (!fs2.existsSync(dir)) {
|
|
342
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
const googlePlayData = asoData.googlePlay;
|
|
345
|
+
const multilingualData = "locales" in googlePlayData ? googlePlayData : {
|
|
346
|
+
locales: {
|
|
347
|
+
[googlePlayData.defaultLanguage || DEFAULT_LOCALE]: googlePlayData
|
|
348
|
+
},
|
|
349
|
+
defaultLocale: googlePlayData.defaultLanguage || DEFAULT_LOCALE
|
|
350
|
+
};
|
|
351
|
+
fs2.writeFileSync(
|
|
352
|
+
asoPath,
|
|
353
|
+
JSON.stringify({ googlePlay: multilingualData }, null, 2) + "\n",
|
|
354
|
+
"utf-8"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (asoData.appStore) {
|
|
358
|
+
const asoPath = path2.join(
|
|
359
|
+
rootDir,
|
|
360
|
+
"products",
|
|
361
|
+
slug,
|
|
362
|
+
"store",
|
|
363
|
+
"app-store",
|
|
364
|
+
"aso-data.json"
|
|
365
|
+
);
|
|
366
|
+
const dir = path2.dirname(asoPath);
|
|
367
|
+
if (!fs2.existsSync(dir)) {
|
|
368
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
const appStoreData = asoData.appStore;
|
|
371
|
+
const multilingualData = "locales" in appStoreData ? appStoreData : {
|
|
372
|
+
locales: {
|
|
373
|
+
[appStoreData.locale || DEFAULT_LOCALE]: appStoreData
|
|
374
|
+
},
|
|
375
|
+
defaultLocale: appStoreData.locale || DEFAULT_LOCALE
|
|
376
|
+
};
|
|
377
|
+
fs2.writeFileSync(
|
|
378
|
+
asoPath,
|
|
379
|
+
JSON.stringify({ appStore: multilingualData }, null, 2) + "\n",
|
|
380
|
+
"utf-8"
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export {
|
|
386
|
+
getAsoDataDir,
|
|
387
|
+
getPullDataDir,
|
|
388
|
+
getPushDataDir,
|
|
389
|
+
getPublicDir,
|
|
390
|
+
getKeywordResearchDir,
|
|
391
|
+
getProductsDir,
|
|
392
|
+
getGeminiApiKey,
|
|
393
|
+
loadAsoFromConfig,
|
|
394
|
+
saveAsoToConfig,
|
|
395
|
+
saveAsoToAsoDir
|
|
396
|
+
};
|
package/dist/index.js
CHANGED