@edtools/cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-BI3UJPWA.js +690 -0
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +150 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +34 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +11 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/index.js +395 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +6 -4
- package/dist/core/generator.js.map +1 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +1 -1
- package/dist/types/hosting.d.ts +19 -0
- package/dist/types/hosting.d.ts.map +1 -0
- package/dist/types/hosting.js +2 -0
- package/dist/types/hosting.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/hosting-detection.d.ts +6 -0
- package/dist/utils/hosting-detection.d.ts.map +1 -0
- package/dist/utils/hosting-detection.js +173 -0
- package/dist/utils/hosting-detection.js.map +1 -0
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ContentGenerator
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-BI3UJPWA.js";
|
|
5
5
|
import "../chunk-INVECVSW.js";
|
|
6
6
|
|
|
7
7
|
// src/cli/index.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
-
import
|
|
9
|
+
import chalk7 from "chalk";
|
|
10
10
|
|
|
11
11
|
// src/cli/commands/init.ts
|
|
12
|
-
import
|
|
13
|
-
import
|
|
12
|
+
import fs2 from "fs-extra";
|
|
13
|
+
import path2 from "path";
|
|
14
14
|
import chalk2 from "chalk";
|
|
15
15
|
import ora from "ora";
|
|
16
16
|
import inquirer from "inquirer";
|
|
@@ -104,12 +104,160 @@ function warningBox(message) {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// src/utils/hosting-detection.ts
|
|
108
|
+
import fs from "fs";
|
|
109
|
+
import path from "path";
|
|
110
|
+
import yaml from "yaml";
|
|
111
|
+
var detectors = [
|
|
112
|
+
{
|
|
113
|
+
name: "Firebase",
|
|
114
|
+
file: "firebase.json",
|
|
115
|
+
parse: (content, projectPath) => {
|
|
116
|
+
try {
|
|
117
|
+
const config = JSON.parse(content);
|
|
118
|
+
const publicDir = config.hosting?.public || "public";
|
|
119
|
+
return {
|
|
120
|
+
platform: "Firebase",
|
|
121
|
+
publicDir,
|
|
122
|
+
configFile: "firebase.json"
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "AWS Amplify",
|
|
131
|
+
file: "amplify.yml",
|
|
132
|
+
parse: (content, projectPath) => {
|
|
133
|
+
try {
|
|
134
|
+
const config = yaml.parse(content);
|
|
135
|
+
const baseDirectory = config?.frontend?.artifacts?.baseDirectory || "build";
|
|
136
|
+
return {
|
|
137
|
+
platform: "AWS Amplify",
|
|
138
|
+
publicDir: baseDirectory,
|
|
139
|
+
configFile: "amplify.yml"
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "Vercel",
|
|
148
|
+
file: "vercel.json",
|
|
149
|
+
parse: (content, projectPath) => {
|
|
150
|
+
try {
|
|
151
|
+
const config = JSON.parse(content);
|
|
152
|
+
const outputDirectory = config.outputDirectory || config.buildCommand?.match(/out|dist|build/)?.[0] || "public";
|
|
153
|
+
return {
|
|
154
|
+
platform: "Vercel",
|
|
155
|
+
publicDir: outputDirectory,
|
|
156
|
+
configFile: "vercel.json"
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "Netlify",
|
|
165
|
+
file: "netlify.toml",
|
|
166
|
+
parse: (content, projectPath) => {
|
|
167
|
+
try {
|
|
168
|
+
const match = content.match(/publish\s*=\s*"([^"]+)"/);
|
|
169
|
+
const publicDir = match?.[1] || "public";
|
|
170
|
+
return {
|
|
171
|
+
platform: "Netlify",
|
|
172
|
+
publicDir,
|
|
173
|
+
configFile: "netlify.toml"
|
|
174
|
+
};
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "Next.js",
|
|
182
|
+
file: "next.config.js",
|
|
183
|
+
parse: (content, projectPath) => {
|
|
184
|
+
if (content.includes("output:") && content.includes("export")) {
|
|
185
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
186
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
187
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
188
|
+
const buildScript = packageJson.scripts?.build || "";
|
|
189
|
+
if (buildScript.includes("out")) {
|
|
190
|
+
return {
|
|
191
|
+
platform: "Next.js (Static Export)",
|
|
192
|
+
publicDir: "out",
|
|
193
|
+
configFile: "next.config.js"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
platform: "Next.js (Static Export)",
|
|
199
|
+
publicDir: "out",
|
|
200
|
+
configFile: "next.config.js"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
];
|
|
207
|
+
function detectHostingConfig(projectPath) {
|
|
208
|
+
for (const detector of detectors) {
|
|
209
|
+
const configPath = path.join(projectPath, detector.file);
|
|
210
|
+
if (fs.existsSync(configPath)) {
|
|
211
|
+
try {
|
|
212
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
213
|
+
const result = detector.parse(content, projectPath);
|
|
214
|
+
if (result) {
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error(`Error parsing ${detector.file}:`, error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
function validateOutputDir(outputDir, hostingConfig) {
|
|
225
|
+
if (!hostingConfig) {
|
|
226
|
+
return { valid: true };
|
|
227
|
+
}
|
|
228
|
+
const normalizedOutputDir = outputDir.replace(/^\.\//, "");
|
|
229
|
+
const normalizedPublicDir = hostingConfig.publicDir.replace(/^\.\//, "");
|
|
230
|
+
const isInPublicDir = normalizedOutputDir.startsWith(`${normalizedPublicDir}/`) || normalizedOutputDir === normalizedPublicDir;
|
|
231
|
+
const issues = [];
|
|
232
|
+
if (!isInPublicDir) {
|
|
233
|
+
issues.push(
|
|
234
|
+
`Output directory "${outputDir}" is outside the public directory "${hostingConfig.publicDir}"`
|
|
235
|
+
);
|
|
236
|
+
issues.push("Generated files will not be accessible in production");
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
valid: isInPublicDir,
|
|
240
|
+
platform: hostingConfig.platform,
|
|
241
|
+
publicDir: hostingConfig.publicDir,
|
|
242
|
+
currentOutputDir: outputDir,
|
|
243
|
+
suggestion: `./${normalizedPublicDir}/blog`,
|
|
244
|
+
issues: issues.length > 0 ? issues : void 0
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function getSuggestedOutputDir(projectPath) {
|
|
248
|
+
const hostingConfig = detectHostingConfig(projectPath);
|
|
249
|
+
if (hostingConfig) {
|
|
250
|
+
return `./${hostingConfig.publicDir}/blog`;
|
|
251
|
+
}
|
|
252
|
+
return "./blog";
|
|
253
|
+
}
|
|
254
|
+
|
|
107
255
|
// src/cli/commands/init.ts
|
|
108
256
|
async function initCommand(options) {
|
|
109
257
|
console.log(chalk2.cyan.bold("\n\u{1F680} Initializing Edtools...\n"));
|
|
110
|
-
const projectPath =
|
|
111
|
-
const configPath =
|
|
112
|
-
if (await
|
|
258
|
+
const projectPath = path2.resolve(options.path);
|
|
259
|
+
const configPath = path2.join(projectPath, "edtools.config.js");
|
|
260
|
+
if (await fs2.pathExists(configPath)) {
|
|
113
261
|
const { overwrite } = await inquirer.prompt([
|
|
114
262
|
{
|
|
115
263
|
type: "confirm",
|
|
@@ -126,9 +274,9 @@ async function initCommand(options) {
|
|
|
126
274
|
const spinner = ora("Analyzing your landing page...").start();
|
|
127
275
|
let productInfo = {};
|
|
128
276
|
try {
|
|
129
|
-
const indexPath =
|
|
130
|
-
if (await
|
|
131
|
-
const html = await
|
|
277
|
+
const indexPath = path2.join(projectPath, "index.html");
|
|
278
|
+
if (await fs2.pathExists(indexPath)) {
|
|
279
|
+
const html = await fs2.readFile(indexPath, "utf-8");
|
|
132
280
|
const $ = loadCheerio(html);
|
|
133
281
|
productInfo = {
|
|
134
282
|
name: $("title").text() || $("h1").first().text() || "My Product",
|
|
@@ -248,6 +396,15 @@ async function initCommand(options) {
|
|
|
248
396
|
...productInfo,
|
|
249
397
|
...answers
|
|
250
398
|
};
|
|
399
|
+
const suggestedOutputDir = getSuggestedOutputDir(projectPath);
|
|
400
|
+
const hostingConfig = detectHostingConfig(projectPath);
|
|
401
|
+
if (hostingConfig) {
|
|
402
|
+
console.log("");
|
|
403
|
+
console.log(chalk2.cyan("\u2713 Detected hosting platform: ") + chalk2.white(hostingConfig.platform));
|
|
404
|
+
console.log(chalk2.cyan(" Public directory: ") + chalk2.white(hostingConfig.publicDir));
|
|
405
|
+
console.log(chalk2.cyan(" Suggested blog directory: ") + chalk2.white(suggestedOutputDir));
|
|
406
|
+
console.log("");
|
|
407
|
+
}
|
|
251
408
|
const config = `module.exports = {
|
|
252
409
|
product: {
|
|
253
410
|
name: ${JSON.stringify(finalProductInfo.name)},
|
|
@@ -261,7 +418,7 @@ async function initCommand(options) {
|
|
|
261
418
|
},
|
|
262
419
|
|
|
263
420
|
content: {
|
|
264
|
-
outputDir:
|
|
421
|
+
outputDir: ${JSON.stringify(suggestedOutputDir)},
|
|
265
422
|
generateBlog: true,
|
|
266
423
|
},
|
|
267
424
|
|
|
@@ -272,17 +429,17 @@ async function initCommand(options) {
|
|
|
272
429
|
},
|
|
273
430
|
};
|
|
274
431
|
`;
|
|
275
|
-
await
|
|
276
|
-
const edtoolsDir =
|
|
277
|
-
await
|
|
432
|
+
await fs2.writeFile(configPath, config, "utf-8");
|
|
433
|
+
const edtoolsDir = path2.join(projectPath, ".edtools");
|
|
434
|
+
await fs2.ensureDir(edtoolsDir);
|
|
278
435
|
const edtoolsConfig = {
|
|
279
436
|
apiKey: apiKeyAnswer.apiKey,
|
|
280
437
|
provider: finalProductInfo.preferredProvider
|
|
281
438
|
};
|
|
282
|
-
const edtoolsConfigPath =
|
|
283
|
-
await
|
|
284
|
-
const gitignorePath =
|
|
285
|
-
await
|
|
439
|
+
const edtoolsConfigPath = path2.join(edtoolsDir, "config.json");
|
|
440
|
+
await fs2.writeFile(edtoolsConfigPath, JSON.stringify(edtoolsConfig, null, 2), "utf-8");
|
|
441
|
+
const gitignorePath = path2.join(edtoolsDir, ".gitignore");
|
|
442
|
+
await fs2.writeFile(gitignorePath, "*\n!.gitignore\n", "utf-8");
|
|
286
443
|
console.log("");
|
|
287
444
|
console.log(successBox("Configuration created successfully!"));
|
|
288
445
|
const filesCreated = `${chalk2.cyan("Files created:")}
|
|
@@ -302,17 +459,18 @@ ${chalk2.gray("API key stored securely (gitignored)")}`;
|
|
|
302
459
|
}
|
|
303
460
|
|
|
304
461
|
// src/cli/commands/generate.ts
|
|
305
|
-
import
|
|
306
|
-
import
|
|
462
|
+
import fs3 from "fs-extra";
|
|
463
|
+
import path3 from "path";
|
|
307
464
|
import { pathToFileURL } from "url";
|
|
308
465
|
import chalk3 from "chalk";
|
|
309
466
|
import ora2 from "ora";
|
|
310
467
|
import Table from "cli-table3";
|
|
468
|
+
import inquirer2 from "inquirer";
|
|
311
469
|
async function generateCommand(options) {
|
|
312
470
|
console.log(chalk3.cyan.bold("\n\u{1F4DD} Generating content...\n"));
|
|
313
471
|
const projectPath = process.cwd();
|
|
314
|
-
const configPath =
|
|
315
|
-
if (!await
|
|
472
|
+
const configPath = path3.join(projectPath, "edtools.config.js");
|
|
473
|
+
if (!await fs3.pathExists(configPath)) {
|
|
316
474
|
console.log(chalk3.red("\u2717 No edtools.config.js found"));
|
|
317
475
|
console.log(chalk3.yellow(' Run "edtools init" first\n'));
|
|
318
476
|
process.exit(1);
|
|
@@ -326,10 +484,10 @@ async function generateCommand(options) {
|
|
|
326
484
|
}
|
|
327
485
|
const provider = productInfo.preferredProvider || "anthropic";
|
|
328
486
|
let storedApiKey;
|
|
329
|
-
const edtoolsConfigPath =
|
|
330
|
-
if (await
|
|
487
|
+
const edtoolsConfigPath = path3.join(projectPath, ".edtools", "config.json");
|
|
488
|
+
if (await fs3.pathExists(edtoolsConfigPath)) {
|
|
331
489
|
try {
|
|
332
|
-
const edtoolsConfig = await
|
|
490
|
+
const edtoolsConfig = await fs3.readJson(edtoolsConfigPath);
|
|
333
491
|
storedApiKey = edtoolsConfig.apiKey;
|
|
334
492
|
} catch (error) {
|
|
335
493
|
}
|
|
@@ -356,14 +514,14 @@ async function generateCommand(options) {
|
|
|
356
514
|
}
|
|
357
515
|
let topics = options.topics;
|
|
358
516
|
if (options.fromCsv) {
|
|
359
|
-
const opportunitiesPath =
|
|
360
|
-
if (!await
|
|
517
|
+
const opportunitiesPath = path3.join(projectPath, ".edtools", "opportunities.json");
|
|
518
|
+
if (!await fs3.pathExists(opportunitiesPath)) {
|
|
361
519
|
console.log(chalk3.red("\u2717 No CSV analysis found"));
|
|
362
520
|
console.log(chalk3.yellow(' Run "edtools analyze" first to analyze your GSC data\n'));
|
|
363
521
|
process.exit(1);
|
|
364
522
|
}
|
|
365
523
|
try {
|
|
366
|
-
const oppData = await
|
|
524
|
+
const oppData = await fs3.readJson(opportunitiesPath);
|
|
367
525
|
topics = oppData.opportunities.slice(0, parseInt(options.posts, 10)).map((opp) => opp.suggestedTitle);
|
|
368
526
|
console.log(chalk3.cyan("\u{1F4CA} Using topics from CSV analysis:\n"));
|
|
369
527
|
topics.forEach((topic, i) => {
|
|
@@ -384,8 +542,40 @@ async function generateCommand(options) {
|
|
|
384
542
|
console.log(chalk3.yellow(`\u26A0\uFE0F Generating ${count} posts at once may trigger spam detection`));
|
|
385
543
|
console.log(chalk3.yellow(" Recommended: 3-5 posts per week\n"));
|
|
386
544
|
}
|
|
387
|
-
const outputDir =
|
|
388
|
-
await
|
|
545
|
+
const outputDir = path3.resolve(projectPath, options.output);
|
|
546
|
+
await fs3.ensureDir(outputDir);
|
|
547
|
+
const hostingConfig = detectHostingConfig(projectPath);
|
|
548
|
+
if (hostingConfig) {
|
|
549
|
+
const validation = validateOutputDir(options.output, hostingConfig);
|
|
550
|
+
if (!validation.valid) {
|
|
551
|
+
console.log(chalk3.yellow.bold("\n\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk3.white(validation.platform));
|
|
552
|
+
console.log(chalk3.yellow(" - Public directory: ") + chalk3.white(validation.publicDir));
|
|
553
|
+
console.log(chalk3.yellow(" - Your outputDir: ") + chalk3.white(validation.currentOutputDir));
|
|
554
|
+
console.log(chalk3.yellow(" - Problem: Files outside ") + chalk3.white(validation.publicDir + "/") + chalk3.yellow(" won't be accessible"));
|
|
555
|
+
console.log("");
|
|
556
|
+
console.log(chalk3.red.bold("\u274C ISSUE: Output directory misconfiguration"));
|
|
557
|
+
console.log(chalk3.red(" Generated files will return 404 in production"));
|
|
558
|
+
console.log("");
|
|
559
|
+
console.log(chalk3.cyan.bold("\u{1F4DD} Suggested fix:"));
|
|
560
|
+
console.log(chalk3.cyan(" Update edtools.config.js:"));
|
|
561
|
+
console.log("");
|
|
562
|
+
console.log(chalk3.gray(" content: {"));
|
|
563
|
+
console.log(chalk3.green(` outputDir: '${validation.suggestion}'`) + chalk3.gray(" // \u2705 Correct path"));
|
|
564
|
+
console.log(chalk3.gray(" }"));
|
|
565
|
+
console.log("");
|
|
566
|
+
const answer = await inquirer2.prompt([{
|
|
567
|
+
type: "confirm",
|
|
568
|
+
name: "continue",
|
|
569
|
+
message: "Continue anyway?",
|
|
570
|
+
default: false
|
|
571
|
+
}]);
|
|
572
|
+
if (!answer.continue) {
|
|
573
|
+
console.log(chalk3.yellow("\n\u2717 Generation cancelled\n"));
|
|
574
|
+
process.exit(0);
|
|
575
|
+
}
|
|
576
|
+
console.log("");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
389
579
|
console.log(chalk3.cyan("Configuration:"));
|
|
390
580
|
console.log(` Product: ${chalk3.white(productInfo.name)}`);
|
|
391
581
|
console.log(` Category: ${chalk3.white(productInfo.category)}`);
|
|
@@ -543,17 +733,17 @@ function truncateTitle(title, maxLen) {
|
|
|
543
733
|
}
|
|
544
734
|
|
|
545
735
|
// src/cli/commands/analyze.ts
|
|
546
|
-
import
|
|
547
|
-
import
|
|
736
|
+
import fs5 from "fs-extra";
|
|
737
|
+
import path4 from "path";
|
|
548
738
|
import chalk4 from "chalk";
|
|
549
739
|
import ora3 from "ora";
|
|
550
740
|
import Table2 from "cli-table3";
|
|
551
741
|
|
|
552
742
|
// src/integrations/gsc-csv-analyzer.ts
|
|
553
|
-
import
|
|
743
|
+
import fs4 from "fs-extra";
|
|
554
744
|
import { parse } from "csv-parse/sync";
|
|
555
745
|
async function parseGSCCSV(filePath) {
|
|
556
|
-
const content = await
|
|
746
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
557
747
|
const records = parse(content, {
|
|
558
748
|
columns: true,
|
|
559
749
|
skip_empty_lines: true,
|
|
@@ -649,7 +839,7 @@ async function saveOpportunities(opportunities, outputPath) {
|
|
|
649
839
|
count: opportunities.length,
|
|
650
840
|
opportunities
|
|
651
841
|
};
|
|
652
|
-
await
|
|
842
|
+
await fs4.writeJson(outputPath, data, { spaces: 2 });
|
|
653
843
|
}
|
|
654
844
|
|
|
655
845
|
// src/cli/commands/analyze.ts
|
|
@@ -658,9 +848,9 @@ async function analyzeCommand(options) {
|
|
|
658
848
|
const projectPath = process.cwd();
|
|
659
849
|
let csvPath;
|
|
660
850
|
if (options.file) {
|
|
661
|
-
csvPath =
|
|
851
|
+
csvPath = path4.resolve(options.file);
|
|
662
852
|
} else {
|
|
663
|
-
const files = await
|
|
853
|
+
const files = await fs5.readdir(projectPath);
|
|
664
854
|
const csvFiles = files.filter((f) => f.endsWith(".csv") && f.toLowerCase().includes("search"));
|
|
665
855
|
if (csvFiles.length === 0) {
|
|
666
856
|
console.log(chalk4.red("\u2717 No CSV file found"));
|
|
@@ -673,9 +863,9 @@ async function analyzeCommand(options) {
|
|
|
673
863
|
csvFiles.forEach((f) => console.log(` - ${f}`));
|
|
674
864
|
console.log(chalk4.yellow("\nUsing first file. Specify with --file to use a different one.\n"));
|
|
675
865
|
}
|
|
676
|
-
csvPath =
|
|
866
|
+
csvPath = path4.join(projectPath, csvFiles[0]);
|
|
677
867
|
}
|
|
678
|
-
if (!await
|
|
868
|
+
if (!await fs5.pathExists(csvPath)) {
|
|
679
869
|
console.log(chalk4.red(`\u2717 File not found: ${csvPath}
|
|
680
870
|
`));
|
|
681
871
|
process.exit(1);
|
|
@@ -684,7 +874,7 @@ async function analyzeCommand(options) {
|
|
|
684
874
|
let data;
|
|
685
875
|
try {
|
|
686
876
|
data = await parseGSCCSV(csvPath);
|
|
687
|
-
spinner.succeed(`Parsed ${chalk4.white(data.length)} keywords from ${chalk4.white(
|
|
877
|
+
spinner.succeed(`Parsed ${chalk4.white(data.length)} keywords from ${chalk4.white(path4.basename(csvPath))}`);
|
|
688
878
|
} catch (error) {
|
|
689
879
|
spinner.fail("Failed to parse CSV");
|
|
690
880
|
console.log(chalk4.red(`
|
|
@@ -789,9 +979,9 @@ Error: ${error.message}
|
|
|
789
979
|
console.log(topTable.toString());
|
|
790
980
|
console.log("");
|
|
791
981
|
}
|
|
792
|
-
const edtoolsDir =
|
|
793
|
-
await
|
|
794
|
-
const opportunitiesPath =
|
|
982
|
+
const edtoolsDir = path4.join(projectPath, ".edtools");
|
|
983
|
+
await fs5.ensureDir(edtoolsDir);
|
|
984
|
+
const opportunitiesPath = path4.join(edtoolsDir, "opportunities.json");
|
|
795
985
|
await saveOpportunities(analysis.opportunities, opportunitiesPath);
|
|
796
986
|
console.log(successBox(`Analysis saved to ${chalk4.white(".edtools/opportunities.json")}`));
|
|
797
987
|
console.log(chalk4.cyan.bold("Next steps:"));
|
|
@@ -802,16 +992,16 @@ Error: ${error.message}
|
|
|
802
992
|
}
|
|
803
993
|
|
|
804
994
|
// src/cli/commands/validate.ts
|
|
805
|
-
import
|
|
806
|
-
import
|
|
995
|
+
import fs7 from "fs-extra";
|
|
996
|
+
import path5 from "path";
|
|
807
997
|
import chalk5 from "chalk";
|
|
808
998
|
import Table3 from "cli-table3";
|
|
809
999
|
|
|
810
1000
|
// src/utils/seo-validator.ts
|
|
811
1001
|
import * as cheerio from "cheerio";
|
|
812
|
-
import
|
|
1002
|
+
import fs6 from "fs-extra";
|
|
813
1003
|
async function validatePost(htmlPath) {
|
|
814
|
-
const html = await
|
|
1004
|
+
const html = await fs6.readFile(htmlPath, "utf-8");
|
|
815
1005
|
const $ = cheerio.load(html);
|
|
816
1006
|
const issues = [];
|
|
817
1007
|
const passed = [];
|
|
@@ -1038,15 +1228,15 @@ async function validateCommand(options) {
|
|
|
1038
1228
|
const projectPath = process.cwd();
|
|
1039
1229
|
let htmlFiles = [];
|
|
1040
1230
|
if (options.post) {
|
|
1041
|
-
const postPath =
|
|
1042
|
-
if (!await
|
|
1231
|
+
const postPath = path5.resolve(projectPath, options.post);
|
|
1232
|
+
if (!await fs7.pathExists(postPath)) {
|
|
1043
1233
|
console.log(errorBox(`Post not found: ${postPath}`));
|
|
1044
1234
|
process.exit(1);
|
|
1045
1235
|
}
|
|
1046
1236
|
htmlFiles = [postPath];
|
|
1047
1237
|
} else if (options.posts) {
|
|
1048
|
-
const postsDir =
|
|
1049
|
-
if (!await
|
|
1238
|
+
const postsDir = path5.resolve(projectPath, options.posts);
|
|
1239
|
+
if (!await fs7.pathExists(postsDir)) {
|
|
1050
1240
|
console.log(errorBox(`Directory not found: ${postsDir}`));
|
|
1051
1241
|
process.exit(1);
|
|
1052
1242
|
}
|
|
@@ -1056,8 +1246,8 @@ async function validateCommand(options) {
|
|
|
1056
1246
|
process.exit(1);
|
|
1057
1247
|
}
|
|
1058
1248
|
} else {
|
|
1059
|
-
const defaultBlogDir =
|
|
1060
|
-
if (await
|
|
1249
|
+
const defaultBlogDir = path5.join(projectPath, "blog");
|
|
1250
|
+
if (await fs7.pathExists(defaultBlogDir)) {
|
|
1061
1251
|
htmlFiles = await findHtmlFiles(defaultBlogDir);
|
|
1062
1252
|
if (htmlFiles.length === 0) {
|
|
1063
1253
|
console.log(errorBox("No HTML files found in blog/ directory"));
|
|
@@ -1102,8 +1292,8 @@ async function validateCommand(options) {
|
|
|
1102
1292
|
})),
|
|
1103
1293
|
stats: stats2
|
|
1104
1294
|
};
|
|
1105
|
-
const outputPath =
|
|
1106
|
-
await
|
|
1295
|
+
const outputPath = path5.resolve(projectPath, options.output);
|
|
1296
|
+
await fs7.writeJson(outputPath, report, { spaces: 2 });
|
|
1107
1297
|
console.log(successBox(`Validation report saved to ${outputPath}`));
|
|
1108
1298
|
}
|
|
1109
1299
|
if (filteredResults.length > 0) {
|
|
@@ -1181,9 +1371,9 @@ async function validateCommand(options) {
|
|
|
1181
1371
|
}
|
|
1182
1372
|
async function findHtmlFiles(dir) {
|
|
1183
1373
|
const files = [];
|
|
1184
|
-
const entries = await
|
|
1374
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
1185
1375
|
for (const entry of entries) {
|
|
1186
|
-
const fullPath =
|
|
1376
|
+
const fullPath = path5.join(dir, entry.name);
|
|
1187
1377
|
if (entry.isDirectory()) {
|
|
1188
1378
|
const subFiles = await findHtmlFiles(fullPath);
|
|
1189
1379
|
files.push(...subFiles);
|
|
@@ -1207,18 +1397,163 @@ function capitalize(str) {
|
|
|
1207
1397
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1208
1398
|
}
|
|
1209
1399
|
|
|
1400
|
+
// src/cli/commands/doctor.ts
|
|
1401
|
+
import fs8 from "fs-extra";
|
|
1402
|
+
import path6 from "path";
|
|
1403
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1404
|
+
import chalk6 from "chalk";
|
|
1405
|
+
async function doctorCommand(options) {
|
|
1406
|
+
console.log(chalk6.cyan.bold("\n\u{1F50D} Diagnosing project configuration...\n"));
|
|
1407
|
+
const projectPath = process.cwd();
|
|
1408
|
+
const configPath = path6.join(projectPath, "edtools.config.js");
|
|
1409
|
+
const issues = [];
|
|
1410
|
+
const warnings = [];
|
|
1411
|
+
const suggestions = [];
|
|
1412
|
+
if (await fs8.pathExists(configPath)) {
|
|
1413
|
+
console.log(chalk6.green("\u2713 Configuration file found: ") + chalk6.white("edtools.config.js"));
|
|
1414
|
+
} else {
|
|
1415
|
+
console.log(chalk6.red("\u2717 Configuration file not found"));
|
|
1416
|
+
console.log(chalk6.yellow(' Run "edtools init" to create configuration\n'));
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
let productInfo;
|
|
1420
|
+
let outputDir;
|
|
1421
|
+
try {
|
|
1422
|
+
const configUrl = pathToFileURL2(configPath).href;
|
|
1423
|
+
const config = await import(configUrl);
|
|
1424
|
+
productInfo = config.default.product;
|
|
1425
|
+
outputDir = config.default.content?.outputDir || "./blog";
|
|
1426
|
+
if (!productInfo || !productInfo.name) {
|
|
1427
|
+
issues.push("Invalid product configuration in edtools.config.js");
|
|
1428
|
+
}
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
issues.push(`Failed to load edtools.config.js: ${error.message}`);
|
|
1431
|
+
console.log(chalk6.red("\u2717 Failed to load configuration file"));
|
|
1432
|
+
console.log(chalk6.red(` Error: ${error.message}
|
|
1433
|
+
`));
|
|
1434
|
+
process.exit(1);
|
|
1435
|
+
}
|
|
1436
|
+
const outputDirPath = path6.resolve(projectPath, outputDir);
|
|
1437
|
+
if (await fs8.pathExists(outputDirPath)) {
|
|
1438
|
+
console.log(chalk6.green("\u2713 Output directory exists: ") + chalk6.white(outputDir));
|
|
1439
|
+
} else {
|
|
1440
|
+
warnings.push(`Output directory does not exist: ${outputDir}`);
|
|
1441
|
+
console.log(chalk6.yellow("\u26A0\uFE0F Output directory does not exist: ") + chalk6.white(outputDir));
|
|
1442
|
+
console.log(chalk6.yellow(' It will be created when you run "edtools generate"'));
|
|
1443
|
+
}
|
|
1444
|
+
const edtoolsConfigPath = path6.join(projectPath, ".edtools", "config.json");
|
|
1445
|
+
let hasApiKey = false;
|
|
1446
|
+
if (await fs8.pathExists(edtoolsConfigPath)) {
|
|
1447
|
+
try {
|
|
1448
|
+
const edtoolsConfig = await fs8.readJson(edtoolsConfigPath);
|
|
1449
|
+
hasApiKey = !!edtoolsConfig.apiKey;
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
const hasEnvApiKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
1454
|
+
if (hasApiKey || hasEnvApiKey) {
|
|
1455
|
+
console.log(chalk6.green("\u2713 API key configured"));
|
|
1456
|
+
} else {
|
|
1457
|
+
warnings.push('No API key found (set via environment variable or "edtools init")');
|
|
1458
|
+
console.log(chalk6.yellow("\u26A0\uFE0F No API key configured"));
|
|
1459
|
+
console.log(chalk6.yellow(" Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
|
|
1460
|
+
console.log(chalk6.yellow(' Or run "edtools init" to store API key'));
|
|
1461
|
+
}
|
|
1462
|
+
console.log("");
|
|
1463
|
+
const hostingConfig = detectHostingConfig(projectPath);
|
|
1464
|
+
if (hostingConfig) {
|
|
1465
|
+
console.log(chalk6.cyan("\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk6.white(hostingConfig.platform));
|
|
1466
|
+
console.log(chalk6.cyan(" - Public directory: ") + chalk6.white(hostingConfig.publicDir));
|
|
1467
|
+
console.log(chalk6.cyan(" - Your outputDir: ") + chalk6.white(outputDir));
|
|
1468
|
+
const validation = validateOutputDir(outputDir, hostingConfig);
|
|
1469
|
+
if (!validation.valid) {
|
|
1470
|
+
issues.push("Output directory misconfiguration");
|
|
1471
|
+
console.log(chalk6.red(" - Problem: Files outside ") + chalk6.white(hostingConfig.publicDir + "/") + chalk6.red(" won't be accessible"));
|
|
1472
|
+
console.log("");
|
|
1473
|
+
console.log(chalk6.red.bold("\u274C ISSUE: Output directory misconfiguration"));
|
|
1474
|
+
console.log(chalk6.red(" Generated files will return 404 in production"));
|
|
1475
|
+
console.log("");
|
|
1476
|
+
console.log(chalk6.cyan.bold("\u{1F4DD} Suggested fix:"));
|
|
1477
|
+
console.log(chalk6.cyan(" Update edtools.config.js:"));
|
|
1478
|
+
console.log("");
|
|
1479
|
+
console.log(chalk6.gray(" content: {"));
|
|
1480
|
+
console.log(chalk6.green(` outputDir: '${validation.suggestion}'`) + chalk6.gray(" // \u2705 Correct path"));
|
|
1481
|
+
console.log(chalk6.gray(" }"));
|
|
1482
|
+
console.log("");
|
|
1483
|
+
suggestions.push(`Update outputDir to '${validation.suggestion}'`);
|
|
1484
|
+
if (options.fix) {
|
|
1485
|
+
console.log(chalk6.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
|
|
1486
|
+
try {
|
|
1487
|
+
const configContent = await fs8.readFile(configPath, "utf-8");
|
|
1488
|
+
const updatedConfig = configContent.replace(
|
|
1489
|
+
/outputDir:\s*['"]([^'"]+)['"]/,
|
|
1490
|
+
`outputDir: '${validation.suggestion}'`
|
|
1491
|
+
);
|
|
1492
|
+
await fs8.writeFile(configPath, updatedConfig, "utf-8");
|
|
1493
|
+
console.log(chalk6.green("\u2713 Updated edtools.config.js"));
|
|
1494
|
+
console.log(chalk6.green(` outputDir: '${validation.suggestion}'`));
|
|
1495
|
+
console.log("");
|
|
1496
|
+
console.log(chalk6.cyan('\u{1F389} Configuration fixed! Run "edtools generate" to create content.\n'));
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
console.log(chalk6.red("\u2717 Failed to auto-fix configuration"));
|
|
1499
|
+
console.log(chalk6.red(` Error: ${error.message}`));
|
|
1500
|
+
console.log(chalk6.yellow(" Please update edtools.config.js manually\n"));
|
|
1501
|
+
}
|
|
1502
|
+
} else {
|
|
1503
|
+
console.log(chalk6.cyan.bold("Run: ") + chalk6.white("edtools doctor --fix"));
|
|
1504
|
+
console.log(chalk6.cyan("To automatically apply suggested fixes\n"));
|
|
1505
|
+
}
|
|
1506
|
+
} else {
|
|
1507
|
+
console.log(chalk6.green(" - Status: \u2705 Output directory correctly configured"));
|
|
1508
|
+
console.log("");
|
|
1509
|
+
}
|
|
1510
|
+
} else {
|
|
1511
|
+
console.log(chalk6.gray("No hosting platform configuration detected"));
|
|
1512
|
+
console.log(chalk6.gray("(No firebase.json, vercel.json, netlify.toml, or amplify.yml found)"));
|
|
1513
|
+
console.log("");
|
|
1514
|
+
}
|
|
1515
|
+
console.log(chalk6.cyan.bold("Summary:\n"));
|
|
1516
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
1517
|
+
console.log(chalk6.green.bold("\u2705 All checks passed! Your project is ready to generate content.\n"));
|
|
1518
|
+
} else {
|
|
1519
|
+
if (issues.length > 0) {
|
|
1520
|
+
console.log(chalk6.red.bold(`\u274C ${issues.length} issue(s) found:`));
|
|
1521
|
+
issues.forEach((issue) => {
|
|
1522
|
+
console.log(chalk6.red(` - ${issue}`));
|
|
1523
|
+
});
|
|
1524
|
+
console.log("");
|
|
1525
|
+
}
|
|
1526
|
+
if (warnings.length > 0) {
|
|
1527
|
+
console.log(chalk6.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
|
|
1528
|
+
warnings.forEach((warning) => {
|
|
1529
|
+
console.log(chalk6.yellow(` - ${warning}`));
|
|
1530
|
+
});
|
|
1531
|
+
console.log("");
|
|
1532
|
+
}
|
|
1533
|
+
if (suggestions.length > 0 && !options.fix) {
|
|
1534
|
+
console.log(chalk6.cyan.bold("\u{1F4A1} Suggestions:"));
|
|
1535
|
+
suggestions.forEach((suggestion) => {
|
|
1536
|
+
console.log(chalk6.cyan(` - ${suggestion}`));
|
|
1537
|
+
});
|
|
1538
|
+
console.log("");
|
|
1539
|
+
console.log(chalk6.cyan("Run ") + chalk6.white("edtools doctor --fix") + chalk6.cyan(" to automatically apply fixes\n"));
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1210
1544
|
// src/cli/index.ts
|
|
1211
1545
|
var program = new Command();
|
|
1212
|
-
program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.
|
|
1546
|
+
program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.1");
|
|
1213
1547
|
program.command("init").description("Initialize edtools in your project").option("-p, --path <path>", "Project path", process.cwd()).action(initCommand);
|
|
1214
1548
|
program.command("generate").description("Generate SEO-optimized blog posts").option("-n, --posts <number>", "Number of posts to generate (1-10)", "3").option("-t, --topics <topics...>", "Specific topics to write about").option("-o, --output <dir>", "Output directory", "./blog").option("--api-key <key>", "API key (or use ANTHROPIC_API_KEY/OPENAI_API_KEY env var)").option("--from-csv", "Generate from CSV analysis opportunities").option("--dry-run", "Preview what would be generated without writing files").option("--report <format>", "Output format: json, table, pretty (default: table)", "table").action(generateCommand);
|
|
1215
1549
|
program.command("analyze").description("Analyze Google Search Console CSV data").option("-f, --file <path>", "Path to CSV file (auto-detects if not provided)").option("--min-impressions <number>", "Minimum impressions for opportunities", "50").option("--min-position <number>", "Minimum position for opportunities", "20").option("--limit <number>", "Number of opportunities to show", "10").action(analyzeCommand);
|
|
1216
1550
|
program.command("validate").description("Validate SEO quality of existing posts").option("--posts <dir>", "Posts directory to validate").option("--post <file>", "Single post file to validate").option("--output <file>", "Save validation report as JSON").option("--threshold <score>", "Only show posts below this score").action(validateCommand);
|
|
1551
|
+
program.command("doctor").description("Diagnose project configuration and hosting setup").option("--fix", "Automatically fix issues").action(doctorCommand);
|
|
1217
1552
|
program.command("config").description("View or set configuration").option("--set-api-key <key>", "Set Anthropic API key").action(async (options) => {
|
|
1218
1553
|
if (options.setApiKey) {
|
|
1219
|
-
console.log(
|
|
1554
|
+
console.log(chalk7.green("\u2713 API key saved"));
|
|
1220
1555
|
} else {
|
|
1221
|
-
console.log(
|
|
1556
|
+
console.log(chalk7.cyan("Configuration:"));
|
|
1222
1557
|
console.log(` API Key: ${process.env.ANTHROPIC_API_KEY ? "[set]" : "[not set]"}`);
|
|
1223
1558
|
}
|
|
1224
1559
|
});
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,iFAAiF,CAAC;KAC9F,OAAO,CAAC,OAAO,CAAC,CAAC;AAGpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,mBAAmB,EAAE,cAAc,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;KAC1D,MAAM,CAAC,WAAW,CAAC,CAAC;AAGvB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,sBAAsB,EAAE,oCAAoC,EAAE,GAAG,CAAC;KACzE,MAAM,CAAC,0BAA0B,EAAE,gCAAgC,CAAC;KACpE,MAAM,CAAC,oBAAoB,EAAE,kBAAkB,EAAE,QAAQ,CAAC;KAC1D,MAAM,CAAC,iBAAiB,EAAE,2DAA2D,CAAC;KACtF,MAAM,CAAC,YAAY,EAAE,0CAA0C,CAAC;KAChE,MAAM,CAAC,WAAW,EAAE,uDAAuD,CAAC;KAC5E,MAAM,CAAC,mBAAmB,EAAE,qDAAqD,EAAE,OAAO,CAAC;KAC3F,MAAM,CAAC,eAAe,CAAC,CAAC;AAG3B,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,wCAAwC,CAAC;KACrD,MAAM,CAAC,mBAAmB,EAAE,iDAAiD,CAAC;KAC9E,MAAM,CAAC,4BAA4B,EAAE,uCAAuC,EAAE,IAAI,CAAC;KACnF,MAAM,CAAC,yBAAyB,EAAE,oCAAoC,EAAE,IAAI,CAAC;KAC7E,MAAM,CAAC,kBAAkB,EAAE,iCAAiC,EAAE,IAAI,CAAC;KACnE,MAAM,CAAC,cAAc,CAAC,CAAC;AAG1B,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,wCAAwC,CAAC;KACrD,MAAM,CAAC,eAAe,EAAE,6BAA6B,CAAC;KACtD,MAAM,CAAC,eAAe,EAAE,8BAA8B,CAAC;KACvD,MAAM,CAAC,iBAAiB,EAAE,gCAAgC,CAAC;KAC3D,MAAM,CAAC,qBAAqB,EAAE,kCAAkC,CAAC;KACjE,MAAM,CAAC,eAAe,CAAC,CAAC;AAG3B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,OAAO,EAAE,0BAA0B,CAAC;KAC3C,MAAM,CAAC,aAAa,CAAC,CAAC;AAGzB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;KACtD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QAEtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC9C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACrF,CAAC;AACH,CAAC,CAAC,CAAC;AAGL,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IAC9B,WAAW,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAGD,OAAO,CAAC,KAAK,EAAE,CAAC"}
|