@cocreate/cli 1.52.0 → 1.54.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ # [1.54.0](https://github.com/CoCreate-app/CoCreate-cli/compare/v1.53.0...v1.54.0) (2025-10-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * Implement symlink creation command with error handling and path validation ([dce81f2](https://github.com/CoCreate-app/CoCreate-cli/commit/dce81f23900388bbc26cfbbfa9501dedd1e02596))
7
+
8
+ # [1.53.0](https://github.com/CoCreate-app/CoCreate-cli/compare/v1.52.0...v1.53.0) (2025-10-08)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Correct directory handling in upload and getConfig functions ([1af7531](https://github.com/CoCreate-app/CoCreate-cli/commit/1af7531a157dd3ce430776c2b669c6976b06cdc6))
14
+ * Improve JSON extraction from AI response and update getConfig function for clarity ([8f22fc1](https://github.com/CoCreate-app/CoCreate-cli/commit/8f22fc15991615b47ef3dfad8c0532e023a5e412))
15
+ * Update config file path handling in getConfig function ([e32e20a](https://github.com/CoCreate-app/CoCreate-cli/commit/e32e20a87124aca74f02a9cd56f13adbcb784569))
16
+
17
+
18
+ ### Features
19
+
20
+ * Add translation functionality to upload command and integrate translate module ([a9ff96a](https://github.com/CoCreate-app/CoCreate-cli/commit/a9ff96ac873f6f2ed14001a5877db4a7bbab0cd8))
21
+ * Enhance translation object structure to include placeholders in generated JSON ([6257ed1](https://github.com/CoCreate-app/CoCreate-cli/commit/6257ed18ea9152c052e32a13c43b4c51e1646cfc))
22
+
1
23
  # [1.52.0](https://github.com/CoCreate-app/CoCreate-cli/compare/v1.51.2...v1.52.0) (2025-09-01)
2
24
 
3
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/cli",
3
- "version": "1.52.0",
3
+ "version": "1.54.0",
4
4
  "description": "Polyrepo management bash CLI tool. Run all git commands and yarn commands on multiple repositories. Also includes a few custom macros for cloning, installing, etc.",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @fileoverview
3
+ * This script is designed to generate SEO-friendly content for a given HTML source.
4
+ * It uses the Gemini API to translate and generate content based on the provided HTML and languages.
5
+ *
6
+ * Prerequisites:
7
+ * - Node.js installed on your system.
8
+ * - The Google Generative AI library installed:
9
+ * `npm install @google/generative-ai`
10
+ * - A valid Gemini API key.
11
+ *
12
+ * Usage:
13
+ * Import and call the `translateHtml` function with the HTML source, languages array, and options.
14
+ */
15
+
16
+ /**
17
+ * Example for structured data translation:
18
+ *
19
+ * {
20
+ * "selector": "script[type='application/ld+json']",
21
+ * "innerHTML": {
22
+ * "en": {
23
+ * "@context": "http://schema.org",
24
+ * "@type": "WebPage",
25
+ * "name": "Basketball Betting Sportsbook | NBA, EuroLeague, NCAA & Global Leagues",
26
+ * "description": "Unlock premier basketball betting at Amapola Sportsbook. Get competitive odds for NBA, EuroLeague, NCAA, and global basketball. Enjoy live betting, swift payouts, and insightful picks for an unmatched wagering experience.",
27
+ * "url": "https://amapolacasino.com/sportsbook/basketball/",
28
+ * "image": "https://amapolacasino.com/assets/basketball-og.jpg",
29
+ * "author": {
30
+ * "@type": "Organization",
31
+ * "name": "AmapolaCasino"
32
+ * }
33
+ * },
34
+ * "es": { ... },
35
+ * "fr": { ... },
36
+ * "pt": { ... },
37
+ * "ht": { ... },
38
+ * "nl": { ... },
39
+ * "gn": { ... }
40
+ * }
41
+ * }
42
+ */
43
+
44
+ const { GoogleGenerativeAI } = require("@google/generative-ai");
45
+ const Config = require("@cocreate/config");
46
+ const MODEL_NAME = "gemini-2.5-flash-lite";
47
+
48
+ // Send HTML to Gemini AI and get translation JSON
49
+ // Exported function to generate translation object for HTML source and languages
50
+ async function getApiKey(options) {
51
+ if (options.apiKey) return options.apiKey;
52
+ const config = await Config({
53
+ GoogleGenerativeAIApiKey: {
54
+ prompt: "Enter your Google Generative AI API key: "
55
+ }
56
+ });
57
+ return config.GoogleGenerativeAIApiKey;
58
+ }
59
+
60
+ module.exports = async function translateHtml(html, languages, options = {}) {
61
+ const apiKey = await getApiKey(options);
62
+ if (!apiKey)
63
+ throw new Error(
64
+ "Google Generative AI API key is required in options.apiKey, process.env, or via prompt."
65
+ );
66
+ const genAI = new GoogleGenerativeAI(apiKey);
67
+ const model =
68
+ options.model || genAI.getGenerativeModel({ model: MODEL_NAME });
69
+ const translationObj = await generateTranslationObject(
70
+ html,
71
+ model,
72
+ languages
73
+ );
74
+ return translationObj;
75
+ };
76
+
77
+ // Update generateTranslationObject to accept only html, model, languages
78
+ async function generateTranslationObject(html, model, languages) {
79
+ const langList = languages.map((l) => `"${l}"`).join(", ");
80
+ const prompt = `
81
+ You are an expert web localization AI. Given the following HTML file, extract all translatable content (titles, meta tags, headers, buttons, video/image alt/title, labels, aria-label, all aria-* attributes, and placeholders) and generate a JSON object in the following format:
82
+
83
+ {
84
+ "translations": [
85
+ {
86
+ "selector": "<css selector>",
87
+ "innerHTML": {
88
+ ${languages
89
+ .map((l) => `\"${l}\"`)
90
+ .join(
91
+ ", \
92
+ "
93
+ )}
94
+ }
95
+ },
96
+ {
97
+ "selector": "<css selector>",
98
+ "attributes": {
99
+ "alt": { ${langList} },
100
+ "label": { ${langList} },
101
+ "aria-label": { ${langList} },
102
+ "aria-*": { ${langList} },
103
+ "title": { ${langList} },
104
+ "placeholder": { ${langList} }
105
+ }
106
+ },
107
+ // Example for structured data translation:
108
+ {
109
+ "selector": "script[type='application/ld+json']",
110
+ "innerHTML": {
111
+ "en": { "@context": "http://schema.org", "@type": "WebPage", "name": "English name", "description": "English description" },
112
+ "es": { "@context": "http://schema.org", "@type": "WebPage", "name": "Spanish name", "description": "Spanish description" },
113
+ "fr": { "@context": "http://schema.org", "@type": "WebPage", "name": "French name", "description": "French description" }
114
+ // ...other languages
115
+ }
116
+ }
117
+ // ...more selectors as needed
118
+ ]
119
+ }
120
+
121
+ Do not add any extra keys or key names not shown in this structure. Only use the keys: name, directory, path, content-type, translations, selector, innerHTML, attributes, alt, label, aria-label, aria-*, title, placeholder, and the language codes (${languages.join(
122
+ ", "
123
+ )}).
124
+
125
+ For every translatable item (innerHTML and attributes), provide a translation for each language: ${languages.join(
126
+ ", "
127
+ )}. Do not leave any language blank. For Guarani (\"gn\"), always translate to Guarani and never leave it in English.
128
+
129
+ Only output the JSON object, do not include any explanation or extra text.
130
+
131
+ HTML:
132
+ ${html}
133
+ `;
134
+
135
+ try {
136
+ const result = await model.generateContent(prompt);
137
+ let jsonText = result.response.candidates[0].content.parts[0].text;
138
+ // Extract the first JSON object from the response
139
+ const match = jsonText.match(/\{[\s\S]*\}/);
140
+ if (!match) throw new Error("No JSON object found in AI response");
141
+ jsonText = match[0];
142
+ return JSON.parse(jsonText).translations;
143
+ } catch (err) {
144
+ console.log(`AI error:`, err);
145
+ return null;
146
+ }
147
+ }
@@ -0,0 +1,36 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ module.exports = async function (repos, args) {
5
+ try {
6
+ if (args.length < 2) {
7
+ console.error("Please provide both source and destination paths.");
8
+ return;
9
+ }
10
+
11
+ const srcPath = path.resolve(process.cwd(), args[0]);
12
+ const destPath = path.resolve(process.cwd(), args[1]);
13
+
14
+ if (!fs.existsSync(srcPath)) {
15
+ console.error(`Source path does not exist: ${srcPath}`);
16
+ return;
17
+ }
18
+
19
+ try {
20
+ // Always remove the destination entry if it exists
21
+ if (fs.existsSync(destPath)) {
22
+ await fs.promises.rm(destPath, { recursive: true, force: true });
23
+ }
24
+
25
+ // Create a symlink for the directory
26
+ await fs.promises.symlink(srcPath, destPath, "dir");
27
+ console.log(`Directory symlink created: ${srcPath} -> ${destPath}`);
28
+ } catch (err) {
29
+ console.error(`Failed to create symlink for ${srcPath}:`, err);
30
+ }
31
+
32
+ console.log("Symlinking complete.");
33
+ } catch (err) {
34
+ console.error("Error during symlinking process:", err);
35
+ }
36
+ };
@@ -2,19 +2,31 @@ const file = require("@cocreate/file");
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
4
  const { getConfig } = require("../getConfig");
5
+ const translate = require("./other/translate");
5
6
 
6
7
  module.exports = async function upload(directory, args) {
7
8
  if (args && !Array.isArray(args)) args = [args];
8
9
 
9
10
  let isWatch = false;
11
+ let translateFn = null;
12
+
13
+ if (args && (args.includes("-t") || args.includes("--translate"))) {
14
+ translateFn = translate;
15
+ args = args.filter((arg) => arg !== "-t" && arg !== "--translate");
16
+ }
17
+
10
18
  if (directory && typeof directory === "string") {
11
19
  if (["-w", "--watch"].includes(directory)) {
12
20
  isWatch = true;
13
- directory = process.cwd();
14
21
  }
15
- } else directory = process.cwd();
22
+ }
23
+
24
+ directory = process.cwd();
16
25
 
17
- if (isWatch || args.includes("-w") || args.includes("--watch")) {
26
+ if (
27
+ isWatch ||
28
+ (args && (args.includes("-w") || args.includes("--watch")))
29
+ ) {
18
30
  for (let i = 0; i < args.length; i++) {
19
31
  if (args[i].startsWith("-")) continue;
20
32
  else if (path.isAbsolute(args[i])) directory = args[i];
@@ -29,7 +41,9 @@ module.exports = async function upload(directory, args) {
29
41
  if (!filename.includes("CoCreate.config.js")) {
30
42
  const config = await getConfig(directory, filename);
31
43
  if (config.configPath) {
32
- await file(config, config.configPath, config.filePath);
44
+ await file(config, config.configPath, config.filePath, {
45
+ translate: translateFn
46
+ });
33
47
  } else {
34
48
  console.log(
35
49
  "Failed to read or parse CoCreate.config.js."
@@ -39,19 +53,21 @@ module.exports = async function upload(directory, args) {
39
53
  }
40
54
  );
41
55
  } else {
42
- if (!args.length) {
56
+ if (!args || !args.length) {
43
57
  const CoCreateConfig = await getConfig(directory);
44
58
  if (CoCreateConfig.configPath) {
45
59
  await file(
46
60
  CoCreateConfig,
47
61
  CoCreateConfig.configPath,
48
- CoCreateConfig.filePath
62
+ CoCreateConfig.filePath,
63
+ { translate: translateFn }
49
64
  );
50
65
  } else {
51
66
  console.log("Failed to read or parse CoCreate.config.js.");
52
67
  }
53
68
  } else {
54
69
  for (let arg of args) {
70
+ arg = path.resolve(directory, arg);
55
71
  let CoCreateConfig;
56
72
 
57
73
  try {
@@ -64,7 +80,8 @@ module.exports = async function upload(directory, args) {
64
80
  await file(
65
81
  CoCreateConfig,
66
82
  CoCreateConfig.configPath,
67
- CoCreateConfig.filePath
83
+ CoCreateConfig.filePath,
84
+ { translate: translateFn }
68
85
  );
69
86
  } else {
70
87
  console.log(
package/src/getConfig.js CHANGED
@@ -1,25 +1,20 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
- async function getConfig(directory, filename = "CoCreate.config.js") {
5
- let config;
6
- const filePath = path.resolve(directory, filename);
7
- if (!filePath.includes("node_modules/")) {
8
- const configPath = findClosestConfig(filePath, "CoCreate.config.js");
9
- if (configPath) {
10
- config = require(configPath);
11
- config.configPath = configPath;
12
- config.filePath = filePath;
13
- } else {
14
- console.log("No CoCreate.config file found in parent directories.");
15
- }
4
+ async function getConfig(directory, filename = "") {
5
+ let configPath = findClosestConfig(directory, "CoCreate.config.js");
6
+ if (configPath) {
7
+ let config = require(configPath);
8
+ config.configPath = configPath;
9
+ config.filePath = path.resolve(directory, filename);
10
+ return config;
11
+ } else {
12
+ console.log("No CoCreate.config file found in parent directories.");
16
13
  }
17
-
18
- return config;
19
14
  }
20
15
 
21
- function findClosestConfig(filePath, filename) {
22
- let currentDir = filePath;
16
+ function findClosestConfig(directory, filename) {
17
+ let currentDir = directory;
23
18
 
24
19
  while (currentDir !== "/" && currentDir !== ".") {
25
20
  let configFile = path.join(currentDir, filename);