@intlayer/cli 9.5.9 → 9.5.11
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/cjs/cli.cjs +8 -3
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/index.cjs +14 -0
- package/dist/cjs/init.cjs +110 -38
- package/dist/cjs/init.cjs.map +1 -1
- package/dist/cjs/initChromeExtension.cjs +36 -0
- package/dist/cjs/initChromeExtension.cjs.map +1 -0
- package/dist/cjs/initInfra.cjs +1 -1
- package/dist/cjs/initInfra.cjs.map +1 -1
- package/dist/cjs/scan.cjs +47 -11
- package/dist/cjs/scan.cjs.map +1 -1
- package/dist/cjs/searchDoc.cjs +1 -0
- package/dist/cjs/searchDoc.cjs.map +1 -1
- package/dist/cjs/utils/selectCmsEnvironment.cjs +1 -1
- package/dist/cjs/utils/selectCmsEnvironment.cjs.map +1 -1
- package/dist/esm/cli.mjs +8 -3
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/index.mjs +3 -2
- package/dist/esm/init.mjs +103 -40
- package/dist/esm/init.mjs.map +1 -1
- package/dist/esm/initChromeExtension.mjs +32 -0
- package/dist/esm/initChromeExtension.mjs.map +1 -0
- package/dist/esm/initInfra.mjs +1 -1
- package/dist/esm/initInfra.mjs.map +1 -1
- package/dist/esm/scan.mjs +48 -12
- package/dist/esm/scan.mjs.map +1 -1
- package/dist/esm/searchDoc.mjs +1 -0
- package/dist/esm/searchDoc.mjs.map +1 -1
- package/dist/esm/utils/selectCmsEnvironment.mjs +1 -1
- package/dist/esm/utils/selectCmsEnvironment.mjs.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/init.d.ts +43 -1
- package/dist/types/init.d.ts.map +1 -1
- package/dist/types/initChromeExtension.d.ts +11 -0
- package/dist/types/initChromeExtension.d.ts.map +1 -0
- package/dist/types/scan.d.ts.map +1 -1
- package/dist/types/searchDoc.d.ts.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initChromeExtension.mjs","names":[],"sources":["../../src/initChromeExtension.ts"],"sourcesContent":["import { loadPrompts } from './loadPrompts';\nimport { openBrowser } from './utils/openBrowser';\n\nexport const CHROME_EXTENSION_URL =\n 'https://chromewebstore.google.com/detail/intlayer_i18n_scanner/pmlehcgmjmfimmjnembihbakhnheiabc';\n\nexport const MOZILLA_EXTENSION_URL =\n 'https://addons.mozilla.org/en-US/firefox/addon/intlayer-i18n-scanner/';\n\nexport const BROWSER_EXTENSION_OPTIONS = [\n {\n value: CHROME_EXTENSION_URL,\n label: 'Chrome',\n hint: CHROME_EXTENSION_URL,\n },\n {\n value: MOZILLA_EXTENSION_URL,\n label: 'Mozilla Firefox',\n hint: MOZILLA_EXTENSION_URL,\n },\n];\n\nexport const initChromeExtension = async () => {\n const p = await loadPrompts();\n\n const selectedUrls = await p.multiselect({\n message: 'Select browser extension(s) to install:',\n options: BROWSER_EXTENSION_OPTIONS,\n required: false,\n });\n\n if (p.isCancel(selectedUrls)) {\n return;\n }\n\n if (Array.isArray(selectedUrls) && selectedUrls.length > 0) {\n for (const url of selectedUrls) {\n p.log.info(`Opening ${url} in your browser...`);\n openBrowser(url);\n }\n }\n};\n"],"mappings":";;;;AAGA,MAAa,uBACX;AAEF,MAAa,wBACX;AAEF,MAAa,4BAA4B,CACvC;CACE,OAAO;CACP,OAAO;CACP,MAAM;AACR,GACA;CACE,OAAO;CACP,OAAO;CACP,MAAM;AACR,CACF;AAEA,MAAa,sBAAsB,YAAY;CAC7C,MAAM,IAAI,MAAM,YAAY;CAE5B,MAAM,eAAe,MAAM,EAAE,YAAY;EACvC,SAAS;EACT,SAAS;EACT,UAAU;CACZ,CAAC;CAED,IAAI,EAAE,SAAS,YAAY,GACzB;CAGF,IAAI,MAAM,QAAQ,YAAY,KAAK,aAAa,SAAS,GACvD,KAAK,MAAM,OAAO,cAAc;EAC9B,EAAE,IAAI,KAAK,WAAW,IAAI,oBAAoB;EAC9C,YAAY,GAAG;CACjB;AAEJ"}
|
package/dist/esm/initInfra.mjs
CHANGED
|
@@ -74,7 +74,7 @@ const initInfra = async (options = {}) => {
|
|
|
74
74
|
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
75
75
|
script = await response.text();
|
|
76
76
|
} catch (error) {
|
|
77
|
-
spinner.stop("Could not download the installer"
|
|
77
|
+
spinner.stop("Could not download the installer");
|
|
78
78
|
p.log.error(`${error.message}\nRun it directly: ${process.platform === "win32" ? `irm ${scriptUrl} | iex` : `curl -fsSL ${scriptUrl} | sh`}`);
|
|
79
79
|
process.exitCode = 1;
|
|
80
80
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initInfra.mjs","names":[],"sources":["../../src/initInfra.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { loadPrompts } from './loadPrompts';\n\n/** Setup modes understood by the hosted installer. */\nexport const INFRA_MODES = ['desktop', 'docker', 'compose'] as const;\n\nexport type InfraMode = (typeof INFRA_MODES)[number];\n\n/**\n * The hosted installers are the single source of truth for the infrastructure\n * setup flow (desktop app, all-in-one container, Compose stack). The CLI runs\n * them rather than re-implementing the same steps in JavaScript.\n */\nexport const INSTALL_SCRIPT_URL = 'https://intlayer.org/install.sh';\nexport const INSTALL_SCRIPT_URL_WINDOWS = 'https://intlayer.org/install.ps1';\n\nexport type InitInfraOptions = {\n /** Skip the interactive menu and run this mode directly. */\n mode?: InfraMode;\n /** Installer URL override (forks, staging). Defaults to the platform's script. */\n scriptUrl?: string;\n};\n\n/** How to fetch and execute the installer on the current platform. */\ntype InstallerRunner = {\n url: string;\n fileName: string;\n /** Builds the command and arguments that execute the downloaded script. */\n command: (scriptPath: string, mode?: InfraMode) => [string, string[]];\n};\n\nconst POSIX_RUNNER: InstallerRunner = {\n url: INSTALL_SCRIPT_URL,\n fileName: 'install.sh',\n command: (scriptPath, mode) => [\n 'sh',\n [scriptPath, ...(mode ? ['--mode', mode] : [])],\n ],\n};\n\nconst WINDOWS_RUNNER: InstallerRunner = {\n url: INSTALL_SCRIPT_URL_WINDOWS,\n fileName: 'install.ps1',\n command: (scriptPath, mode) => [\n 'powershell',\n [\n '-NoProfile',\n '-ExecutionPolicy',\n 'Bypass',\n '-File',\n scriptPath,\n ...(mode ? ['-Mode', mode] : []),\n ],\n ],\n};\n\nconst getInstallerRunner = (): InstallerRunner =>\n process.platform === 'win32' ? WINDOWS_RUNNER : POSIX_RUNNER;\n\n/**\n * Validates a user-provided mode against {@link INFRA_MODES}.\n *\n * @throws When the value is not a known mode.\n */\nexport const parseInfraMode = (value: string): InfraMode => {\n if ((INFRA_MODES as readonly string[]).includes(value)) {\n return value as InfraMode;\n }\n\n throw new Error(\n `Unknown infra mode \"${value}\". Expected one of: ${INFRA_MODES.join(', ')}`\n );\n};\n\n/**\n * Runs the installer with the terminal attached, so its own prompts and\n * progress bars reach the user unchanged.\n *\n * @returns The script's exit code.\n */\nconst runInstaller = (command: string, args: string[]): Promise<number> =>\n new Promise((resolve, reject) => {\n const child = spawn(command, args, { stdio: 'inherit' });\n\n child.on('error', reject);\n child.on('close', (code) => resolve(code ?? 1));\n });\n\n/**\n * Downloads the hosted installer and runs it: desktop app, all-in-one Docker\n * container or Docker Compose stack, chosen interactively unless `mode` is set.\n *\n * Exposed as `intlayer init infra` and as a step of `intlayer init --interactive`.\n */\nexport const initInfra = async (\n options: InitInfraOptions = {}\n): Promise<void> => {\n const p = await loadPrompts();\n const runner = getInstallerRunner();\n const scriptUrl = options.scriptUrl ?? runner.url;\n\n const spinner = p.spinner();\n spinner.start(`Fetching ${scriptUrl}`);\n\n let script: string;\n\n try {\n const response = await fetch(scriptUrl);\n\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}`);\n }\n\n script = await response.text();\n } catch (error) {\n spinner.stop('Could not download the installer'
|
|
1
|
+
{"version":3,"file":"initInfra.mjs","names":[],"sources":["../../src/initInfra.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { loadPrompts } from './loadPrompts';\n\n/** Setup modes understood by the hosted installer. */\nexport const INFRA_MODES = ['desktop', 'docker', 'compose'] as const;\n\nexport type InfraMode = (typeof INFRA_MODES)[number];\n\n/**\n * The hosted installers are the single source of truth for the infrastructure\n * setup flow (desktop app, all-in-one container, Compose stack). The CLI runs\n * them rather than re-implementing the same steps in JavaScript.\n */\nexport const INSTALL_SCRIPT_URL = 'https://intlayer.org/install.sh';\nexport const INSTALL_SCRIPT_URL_WINDOWS = 'https://intlayer.org/install.ps1';\n\nexport type InitInfraOptions = {\n /** Skip the interactive menu and run this mode directly. */\n mode?: InfraMode;\n /** Installer URL override (forks, staging). Defaults to the platform's script. */\n scriptUrl?: string;\n};\n\n/** How to fetch and execute the installer on the current platform. */\ntype InstallerRunner = {\n url: string;\n fileName: string;\n /** Builds the command and arguments that execute the downloaded script. */\n command: (scriptPath: string, mode?: InfraMode) => [string, string[]];\n};\n\nconst POSIX_RUNNER: InstallerRunner = {\n url: INSTALL_SCRIPT_URL,\n fileName: 'install.sh',\n command: (scriptPath, mode) => [\n 'sh',\n [scriptPath, ...(mode ? ['--mode', mode] : [])],\n ],\n};\n\nconst WINDOWS_RUNNER: InstallerRunner = {\n url: INSTALL_SCRIPT_URL_WINDOWS,\n fileName: 'install.ps1',\n command: (scriptPath, mode) => [\n 'powershell',\n [\n '-NoProfile',\n '-ExecutionPolicy',\n 'Bypass',\n '-File',\n scriptPath,\n ...(mode ? ['-Mode', mode] : []),\n ],\n ],\n};\n\nconst getInstallerRunner = (): InstallerRunner =>\n process.platform === 'win32' ? WINDOWS_RUNNER : POSIX_RUNNER;\n\n/**\n * Validates a user-provided mode against {@link INFRA_MODES}.\n *\n * @throws When the value is not a known mode.\n */\nexport const parseInfraMode = (value: string): InfraMode => {\n if ((INFRA_MODES as readonly string[]).includes(value)) {\n return value as InfraMode;\n }\n\n throw new Error(\n `Unknown infra mode \"${value}\". Expected one of: ${INFRA_MODES.join(', ')}`\n );\n};\n\n/**\n * Runs the installer with the terminal attached, so its own prompts and\n * progress bars reach the user unchanged.\n *\n * @returns The script's exit code.\n */\nconst runInstaller = (command: string, args: string[]): Promise<number> =>\n new Promise((resolve, reject) => {\n const child = spawn(command, args, { stdio: 'inherit' });\n\n child.on('error', reject);\n child.on('close', (code) => resolve(code ?? 1));\n });\n\n/**\n * Downloads the hosted installer and runs it: desktop app, all-in-one Docker\n * container or Docker Compose stack, chosen interactively unless `mode` is set.\n *\n * Exposed as `intlayer init infra` and as a step of `intlayer init --interactive`.\n */\nexport const initInfra = async (\n options: InitInfraOptions = {}\n): Promise<void> => {\n const p = await loadPrompts();\n const runner = getInstallerRunner();\n const scriptUrl = options.scriptUrl ?? runner.url;\n\n const spinner = p.spinner();\n spinner.start(`Fetching ${scriptUrl}`);\n\n let script: string;\n\n try {\n const response = await fetch(scriptUrl);\n\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}`);\n }\n\n script = await response.text();\n } catch (error) {\n spinner.stop('Could not download the installer');\n p.log.error(\n `${(error as Error).message}\\nRun it directly: ${\n process.platform === 'win32'\n ? `irm ${scriptUrl} | iex`\n : `curl -fsSL ${scriptUrl} | sh`\n }`\n );\n process.exitCode = 1;\n return;\n }\n\n spinner.stop('Installer downloaded');\n\n const directory = await mkdtemp(join(tmpdir(), 'intlayer-install-'));\n const scriptPath = join(directory, runner.fileName);\n\n try {\n await writeFile(scriptPath, script, { mode: 0o700 });\n\n const [command, args] = runner.command(scriptPath, options.mode);\n const exitCode = await runInstaller(command, args);\n\n if (exitCode !== 0) {\n process.exitCode = exitCode;\n }\n } finally {\n await rm(directory, { recursive: true, force: true });\n }\n};\n"],"mappings":";;;;;;;;AAOA,MAAa,cAAc;CAAC;CAAW;CAAU;AAAS;;;;;;AAS1D,MAAa,qBAAqB;AAClC,MAAa,6BAA6B;AAiB1C,MAAM,eAAgC;CACpC,KAAK;CACL,UAAU;CACV,UAAU,YAAY,SAAS,CAC7B,MACA,CAAC,YAAY,GAAI,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAE,CAChD;AACF;AAEA,MAAM,iBAAkC;CACtC,KAAK;CACL,UAAU;CACV,UAAU,YAAY,SAAS,CAC7B,cACA;EACE;EACA;EACA;EACA;EACA;EACA,GAAI,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;CAChC,CACF;AACF;AAEA,MAAM,2BACJ,QAAQ,aAAa,UAAU,iBAAiB;;;;;;AAOlD,MAAa,kBAAkB,UAA6B;CAC1D,IAAK,YAAkC,SAAS,KAAK,GACnD,OAAO;CAGT,MAAM,IAAI,MACR,uBAAuB,MAAM,sBAAsB,YAAY,KAAK,IAAI,GAC1E;AACF;;;;;;;AAQA,MAAM,gBAAgB,SAAiB,SACrC,IAAI,SAAS,SAAS,WAAW;CAC/B,MAAM,QAAQ,MAAM,SAAS,MAAM,EAAE,OAAO,UAAU,CAAC;CAEvD,MAAM,GAAG,SAAS,MAAM;CACxB,MAAM,GAAG,UAAU,SAAS,QAAQ,QAAQ,CAAC,CAAC;AAChD,CAAC;;;;;;;AAQH,MAAa,YAAY,OACvB,UAA4B,CAAC,MACX;CAClB,MAAM,IAAI,MAAM,YAAY;CAC5B,MAAM,SAAS,mBAAmB;CAClC,MAAM,YAAY,QAAQ,aAAa,OAAO;CAE9C,MAAM,UAAU,EAAE,QAAQ;CAC1B,QAAQ,MAAM,YAAY,WAAW;CAErC,IAAI;CAEJ,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,SAAS;EAEtC,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,GAAG,SAAS,OAAO,GAAG,SAAS,YAAY;EAG7D,SAAS,MAAM,SAAS,KAAK;CAC/B,SAAS,OAAO;EACd,QAAQ,KAAK,kCAAkC;EAC/C,EAAE,IAAI,MACJ,GAAI,MAAgB,QAAQ,qBAC1B,QAAQ,aAAa,UACjB,OAAO,UAAU,UACjB,cAAc,UAAU,QAEhC;EACA,QAAQ,WAAW;EACnB;CACF;CAEA,QAAQ,KAAK,sBAAsB;CAEnC,MAAM,YAAY,MAAM,QAAQ,KAAK,OAAO,GAAG,mBAAmB,CAAC;CACnE,MAAM,aAAa,KAAK,WAAW,OAAO,QAAQ;CAElD,IAAI;EACF,MAAM,UAAU,YAAY,QAAQ,EAAE,MAAM,IAAM,CAAC;EAEnD,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,YAAY,QAAQ,IAAI;EAC/D,MAAM,WAAW,MAAM,aAAa,SAAS,IAAI;EAEjD,IAAI,aAAa,GACf,QAAQ,WAAW;CAEvB,UAAU;EACR,MAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACtD;AACF"}
|
package/dist/esm/scan.mjs
CHANGED
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
2
2
|
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
3
3
|
import { getConfiguration } from "@intlayer/config/node";
|
|
4
|
-
import { formatSize, scanWebsite } from "@intlayer/engine/scan";
|
|
4
|
+
import { formatSize, getCheckDetailLines, scanWebsite } from "@intlayer/engine/scan";
|
|
5
5
|
|
|
6
6
|
//#region src/scan.ts
|
|
7
7
|
/** Human-readable labels for each scorable check type. */
|
|
8
8
|
const checkLabels = {
|
|
9
9
|
robots_robotsPresent: "robots.txt present",
|
|
10
|
-
robots_noLocalizedUrlsForgotten: "robots.txt keeps
|
|
11
|
-
sitemap_sitemapPresent: "sitemap
|
|
10
|
+
robots_noLocalizedUrlsForgotten: "robots.txt keeps localized URLs crawlable",
|
|
11
|
+
sitemap_sitemapPresent: "sitemap present",
|
|
12
12
|
sitemap_noLocalizedUrlsForgotten: "sitemap lists every locale",
|
|
13
13
|
sitemap_hasAlternates: "sitemap has alternate links",
|
|
14
14
|
sitemap_hasXDefault: "sitemap has x-default",
|
|
15
15
|
url_htmlLang: "html lang attribute",
|
|
16
16
|
url_htmlDir: "html dir attribute",
|
|
17
|
+
url_currentLocale: "locale signals consistent (lang, URL, hreflang)",
|
|
18
|
+
url_ogLocale: "og:locale meta tag",
|
|
17
19
|
url_hasCanonical: "canonical link",
|
|
18
20
|
url_hreflang: "hreflang tags",
|
|
19
|
-
|
|
21
|
+
url_hreflangReciprocal: "hreflang alternates link back",
|
|
20
22
|
url_hasXDefault: "x-default hreflang",
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
url_hasLocalizedLinks: "localized internal links",
|
|
24
|
+
url_allAnchorsLocalized: "all internal links keep the locale",
|
|
25
|
+
url_hasLangSelector: "crawlable language switcher",
|
|
23
26
|
url_unusedBundleContent: "unused bundle locale content"
|
|
24
27
|
};
|
|
28
|
+
/** Human-readable labels of the routing strategies. */
|
|
29
|
+
const routingLabels = {
|
|
30
|
+
"prefix-all": "locale prefix on every URL (/en/…, /fr/…)",
|
|
31
|
+
"prefix-no-default": "locale prefix except for the default locale",
|
|
32
|
+
"search-params": "locale in a query parameter",
|
|
33
|
+
subdomain: "locale subdomain (fr.example.com)",
|
|
34
|
+
domain: "one domain per locale",
|
|
35
|
+
"no-prefix": "no locale in the URL (cookie / header based)",
|
|
36
|
+
unknown: "unknown"
|
|
37
|
+
};
|
|
38
|
+
/** Human-readable labels of the technology categories. */
|
|
39
|
+
const categoryLabels = {
|
|
40
|
+
framework: "Framework",
|
|
41
|
+
"i18n-library": "i18n library",
|
|
42
|
+
tms: "TMS",
|
|
43
|
+
"translation-proxy": "Translation proxy",
|
|
44
|
+
cms: "CMS"
|
|
45
|
+
};
|
|
46
|
+
/** Maximum number of detail lines printed under a failing check. */
|
|
47
|
+
const MAX_DETAIL_LINES = 3;
|
|
25
48
|
const statusIcon = (status) => {
|
|
26
49
|
if (status === "success") return colorize("✓", ANSIColors.GREEN);
|
|
27
50
|
if (status === "warning") return colorize("⚠", ANSIColors.YELLOW);
|
|
@@ -32,6 +55,18 @@ const scoreColor = (score) => {
|
|
|
32
55
|
if (score >= 50) return ANSIColors.YELLOW;
|
|
33
56
|
return ANSIColors.RED;
|
|
34
57
|
};
|
|
58
|
+
/** Print a check with, when it fails, a short explanation. */
|
|
59
|
+
const logCheck = (appLogger, event) => {
|
|
60
|
+
const type = event.type.split("\\")[0];
|
|
61
|
+
if (!type) return;
|
|
62
|
+
appLogger(` ${statusIcon(event.status)} ${checkLabels[type] ?? type}`);
|
|
63
|
+
if (event.status === "success") return;
|
|
64
|
+
const detailLines = getCheckDetailLines(event.details?.[event.status]);
|
|
65
|
+
detailLines.slice(0, MAX_DETAIL_LINES).forEach((line, index) => {
|
|
66
|
+
appLogger(colorize(`${index === 0 ? " " : " "}${line}`, ANSIColors.GREY));
|
|
67
|
+
});
|
|
68
|
+
if (detailLines.length > MAX_DETAIL_LINES) appLogger(colorize(` … ${detailLines.length - MAX_DETAIL_LINES} more (use --json)`, ANSIColors.GREY));
|
|
69
|
+
};
|
|
35
70
|
/**
|
|
36
71
|
* Scan a website for i18n/SEO health and bundle weight, printing a formatted
|
|
37
72
|
* report (or JSON with `--json`).
|
|
@@ -53,13 +88,14 @@ const scan = async (url, options = {}) => {
|
|
|
53
88
|
appLogger(`Score: ${colorize(`${result.score}/100`, scoreColor(result.score))}`);
|
|
54
89
|
appLogger(`Page size: ${colorize(formatSize(result.totalPageSize), ANSIColors.BLUE)} ${colorize(`(HTML ${formatSize(result.htmlSize)})`, ANSIColors.GREY)}`);
|
|
55
90
|
if (result.locales.length > 0) appLogger(`Locales: ${colorize(result.locales.join(", "), ANSIColors.GREEN)}`);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
appLogger(` ${statusIcon(event.status)} ${label}`);
|
|
91
|
+
const { routing } = result;
|
|
92
|
+
appLogger(`Routing: ${colorize(routingLabels[routing.strategy], ANSIColors.BLUE)} ${colorize(`(${routing.evidence}${routing.defaultLocale ? `, default locale: ${routing.defaultLocale}` : ""})`, ANSIColors.GREY)}`);
|
|
93
|
+
if (result.technologies.length > 0) {
|
|
94
|
+
appLogger("\nStack:");
|
|
95
|
+
for (const technology of result.technologies) appLogger(` ${colorize(categoryLabels[technology.category], ANSIColors.GREY)} ${technology.name}${technology.version ? ` ${technology.version}` : ""} ${colorize(`(${technology.evidence})`, ANSIColors.GREY)}`);
|
|
62
96
|
}
|
|
97
|
+
appLogger("\nChecks:");
|
|
98
|
+
for (const event of result.events) logCheck(appLogger, event);
|
|
63
99
|
if (result.bundle && result.bundle.totalLocaleSize > 0) {
|
|
64
100
|
const { bundle } = result;
|
|
65
101
|
appLogger("\nBundle locale weight:");
|
package/dist/esm/scan.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.mjs","names":[],"sources":["../../src/scan.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { formatSize
|
|
1
|
+
{"version":3,"file":"scan.mjs","names":[],"sources":["../../src/scan.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n type DetectedTechnology,\n formatSize,\n getCheckDetailLines,\n type ScanEvent,\n type ScanResult,\n scanWebsite,\n} from '@intlayer/engine/scan';\nimport type { ConfigurationOptions } from './cli';\n\n/** Options accepted by the {@link scan} command. */\nexport type ScanCommandOptions = {\n /** Disable the deeper puppeteer-based render scan. */\n deep?: boolean;\n /** Output the raw {@link ScanResult} as JSON instead of formatted text. */\n json?: boolean;\n configOptions?: ConfigurationOptions;\n};\n\n/** Human-readable labels for each scorable check type. */\nconst checkLabels: Record<string, string> = {\n robots_robotsPresent: 'robots.txt present',\n robots_noLocalizedUrlsForgotten: 'robots.txt keeps localized URLs crawlable',\n sitemap_sitemapPresent: 'sitemap present',\n sitemap_noLocalizedUrlsForgotten: 'sitemap lists every locale',\n sitemap_hasAlternates: 'sitemap has alternate links',\n sitemap_hasXDefault: 'sitemap has x-default',\n url_htmlLang: 'html lang attribute',\n url_htmlDir: 'html dir attribute',\n url_currentLocale: 'locale signals consistent (lang, URL, hreflang)',\n url_ogLocale: 'og:locale meta tag',\n url_hasCanonical: 'canonical link',\n url_hreflang: 'hreflang tags',\n url_hreflangReciprocal: 'hreflang alternates link back',\n url_hasXDefault: 'x-default hreflang',\n url_hasLocalizedLinks: 'localized internal links',\n url_allAnchorsLocalized: 'all internal links keep the locale',\n url_hasLangSelector: 'crawlable language switcher',\n url_unusedBundleContent: 'unused bundle locale content',\n};\n\n/** Human-readable labels of the routing strategies. */\nconst routingLabels: Record<ScanResult['routing']['strategy'], string> = {\n 'prefix-all': 'locale prefix on every URL (/en/…, /fr/…)',\n 'prefix-no-default': 'locale prefix except for the default locale',\n 'search-params': 'locale in a query parameter',\n subdomain: 'locale subdomain (fr.example.com)',\n domain: 'one domain per locale',\n 'no-prefix': 'no locale in the URL (cookie / header based)',\n unknown: 'unknown',\n};\n\n/** Human-readable labels of the technology categories. */\nconst categoryLabels: Record<DetectedTechnology['category'], string> = {\n framework: 'Framework',\n 'i18n-library': 'i18n library',\n tms: 'TMS',\n 'translation-proxy': 'Translation proxy',\n cms: 'CMS',\n};\n\n/** Maximum number of detail lines printed under a failing check. */\nconst MAX_DETAIL_LINES = 3;\n\nconst statusIcon = (status: ScanEvent['status']): string => {\n if (status === 'success') return colorize('✓', ANSIColors.GREEN);\n if (status === 'warning') return colorize('⚠', ANSIColors.YELLOW);\n return colorize('✗', ANSIColors.RED);\n};\n\nconst scoreColor = (score: number) => {\n if (score >= 80) return ANSIColors.GREEN;\n if (score >= 50) return ANSIColors.YELLOW;\n return ANSIColors.RED;\n};\n\n/** Print a check with, when it fails, a short explanation. */\nconst logCheck = (\n appLogger: ReturnType<typeof getAppLogger>,\n event: ScanEvent\n): void => {\n const type = event.type.split('\\\\')[0];\n if (!type) return;\n\n appLogger(` ${statusIcon(event.status)} ${checkLabels[type] ?? type}`);\n\n if (event.status === 'success') return;\n\n const detailLines = getCheckDetailLines(event.details?.[event.status]);\n detailLines.slice(0, MAX_DETAIL_LINES).forEach((line, index) => {\n appLogger(\n colorize(`${index === 0 ? ' ' : ' '}${line}`, ANSIColors.GREY)\n );\n });\n if (detailLines.length > MAX_DETAIL_LINES) {\n appLogger(\n colorize(\n ` … ${detailLines.length - MAX_DETAIL_LINES} more (use --json)`,\n ANSIColors.GREY\n )\n );\n }\n};\n\n/**\n * Scan a website for i18n/SEO health and bundle weight, printing a formatted\n * report (or JSON with `--json`).\n *\n * @example\n * ```sh\n * npx intlayer scan https://intlayer.org\n * ```\n */\nexport const scan = async (\n url: string,\n options: ScanCommandOptions = {}\n): Promise<void> => {\n const configuration = getConfiguration(options.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const result = await scanWebsite(url, { deep: options.deep });\n\n if (options.json) {\n appLogger(JSON.stringify(result, null, 2));\n return;\n }\n\n appLogger(\n `\\n🔍 Scanned ${colorize(result.url, ANSIColors.GREY_LIGHT)} ${colorize(\n `(${result.mode} mode)`,\n ANSIColors.GREY\n )}\\n`\n );\n\n appLogger(\n `Score: ${colorize(`${result.score}/100`, scoreColor(result.score))}`\n );\n appLogger(\n `Page size: ${colorize(formatSize(result.totalPageSize), ANSIColors.BLUE)} ${colorize(\n `(HTML ${formatSize(result.htmlSize)})`,\n ANSIColors.GREY\n )}`\n );\n if (result.locales.length > 0) {\n appLogger(\n `Locales: ${colorize(result.locales.join(', '), ANSIColors.GREEN)}`\n );\n }\n\n const { routing } = result;\n appLogger(\n `Routing: ${colorize(routingLabels[routing.strategy], ANSIColors.BLUE)} ${colorize(\n `(${routing.evidence}${routing.defaultLocale ? `, default locale: ${routing.defaultLocale}` : ''})`,\n ANSIColors.GREY\n )}`\n );\n\n if (result.technologies.length > 0) {\n appLogger('\\nStack:');\n for (const technology of result.technologies) {\n appLogger(\n ` ${colorize(categoryLabels[technology.category], ANSIColors.GREY)} ${technology.name}${\n technology.version ? ` ${technology.version}` : ''\n } ${colorize(`(${technology.evidence})`, ANSIColors.GREY)}`\n );\n }\n }\n\n appLogger('\\nChecks:');\n for (const event of result.events) {\n logCheck(appLogger, event);\n }\n\n if (result.bundle && result.bundle.totalLocaleSize > 0) {\n const { bundle } = result;\n appLogger('\\nBundle locale weight:');\n appLogger(\n ` Translations shipped: ${colorize(formatSize(bundle.totalLocaleSize), ANSIColors.BLUE)}`\n );\n appLogger(\n ` Unused (other locales): ${colorize(\n `${formatSize(bundle.totalUnusedLocaleSize)} (${bundle.unusedPercentOfLocale}%)`,\n bundle.unusedPercentOfLocale > 30\n ? ANSIColors.RED\n : bundle.unusedPercentOfLocale > 0\n ? ANSIColors.YELLOW\n : ANSIColors.GREEN\n )}`\n );\n }\n};\n"],"mappings":"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"}
|
package/dist/esm/searchDoc.mjs
CHANGED
|
@@ -27,6 +27,7 @@ const searchDoc = async ({ query, limit = 10, configOptions }) => {
|
|
|
27
27
|
appLogger(`${colorizeKey("Title")}: ${chunk.docName}`);
|
|
28
28
|
appLogger(`${colorizeKey("URL")}: ${chunk.docUrl}`);
|
|
29
29
|
appLogger(`${colorizeKey("Chunk")}: ${chunk.chunkNumber}`);
|
|
30
|
+
if (chunk.priority !== void 0) appLogger(`${colorizeKey("Priority")}: ${colorizeNumber(chunk.priority)}`);
|
|
30
31
|
appLogger(`${colorizeKey("Content")}:`);
|
|
31
32
|
appLogger(chunk.content);
|
|
32
33
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchDoc.mjs","names":[],"sources":["../../src/searchDoc.ts"],"sourcesContent":["import { getSearchAPI } from '@intlayer/api';\nimport {\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { logConfigDetails } from '@intlayer/engine/cli';\n\ninterface SearchDocOptions {\n query: string;\n limit?: number;\n configOptions?: GetConfigurationOptions;\n}\n\nexport const searchDoc = async ({\n query,\n limit = 10,\n configOptions,\n}: SearchDocOptions) => {\n const config = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger(config);\n\n try {\n const { searchDoc } = getSearchAPI(undefined, config);\n const response = await searchDoc({\n input: query,\n limit: limit.toString(),\n returnContent: 'true',\n });\n\n if (!response.data || !Array.isArray(response.data)) {\n appLogger('No relevant chunks found.');\n return;\n }\n\n const chunks = response.data;\n\n appLogger(`Found ${colorizeNumber(chunks.length)} relevant chunks:`);\n\n chunks.forEach((chunk
|
|
1
|
+
{"version":3,"file":"searchDoc.mjs","names":[],"sources":["../../src/searchDoc.ts"],"sourcesContent":["import { getSearchAPI } from '@intlayer/api';\nimport {\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { logConfigDetails } from '@intlayer/engine/cli';\n\n/** A documentation chunk returned by the search endpoint with `returnContent`. */\ntype SearchDocChunk = Exclude<\n NonNullable<\n Awaited<ReturnType<ReturnType<typeof getSearchAPI>['searchDoc']>>['data']\n >,\n string[]\n>[number];\n\ninterface SearchDocOptions {\n query: string;\n limit?: number;\n configOptions?: GetConfigurationOptions;\n}\n\nexport const searchDoc = async ({\n query,\n limit = 10,\n configOptions,\n}: SearchDocOptions) => {\n const config = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger(config);\n\n try {\n const { searchDoc } = getSearchAPI(undefined, config);\n const response = await searchDoc({\n input: query,\n limit: limit.toString(),\n returnContent: 'true',\n });\n\n if (!response.data || !Array.isArray(response.data)) {\n appLogger('No relevant chunks found.');\n return;\n }\n\n // Ranked by relevance, weighted by the `priority` front matter of each doc\n const chunks = response.data as SearchDocChunk[];\n\n appLogger(`Found ${colorizeNumber(chunks.length)} relevant chunks:`);\n\n chunks.forEach((chunk) => {\n appLogger('---');\n appLogger(`${colorizeKey('File')}: ${chunk.fileKey}`);\n appLogger(`${colorizeKey('Title')}: ${chunk.docName}`);\n appLogger(`${colorizeKey('URL')}: ${chunk.docUrl}`);\n appLogger(`${colorizeKey('Chunk')}: ${chunk.chunkNumber}`);\n if (chunk.priority !== undefined) {\n appLogger(\n `${colorizeKey('Priority')}: ${colorizeNumber(chunk.priority)}`\n );\n }\n appLogger(`${colorizeKey('Content')}:`);\n appLogger(chunk.content);\n });\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'An unknown error occurred';\n appLogger(`Search failed: ${errorMessage}`, { level: 'error' });\n }\n};\n"],"mappings":";;;;;;AA0BA,MAAa,YAAY,OAAO,EAC9B,OACA,QAAQ,IACR,oBACsB;CACtB,MAAM,SAAS,iBAAiB,aAAa;CAC7C,iBAAiB,aAAa;CAE9B,MAAM,YAAY,aAAa,MAAM;CAErC,IAAI;EACF,MAAM,EAAE,cAAc,aAAa,QAAW,MAAM;EACpD,MAAM,WAAW,MAAM,UAAU;GAC/B,OAAO;GACP,OAAO,MAAM,SAAS;GACtB,eAAe;EACjB,CAAC;EAED,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,QAAQ,SAAS,IAAI,GAAG;GACnD,UAAU,2BAA2B;GACrC;EACF;EAGA,MAAM,SAAS,SAAS;EAExB,UAAU,SAAS,eAAe,OAAO,MAAM,EAAE,kBAAkB;EAEnE,OAAO,SAAS,UAAU;GACxB,UAAU,KAAK;GACf,UAAU,GAAG,YAAY,MAAM,EAAE,IAAI,MAAM,SAAS;GACpD,UAAU,GAAG,YAAY,OAAO,EAAE,IAAI,MAAM,SAAS;GACrD,UAAU,GAAG,YAAY,KAAK,EAAE,IAAI,MAAM,QAAQ;GAClD,UAAU,GAAG,YAAY,OAAO,EAAE,IAAI,MAAM,aAAa;GACzD,IAAI,MAAM,aAAa,QACrB,UACE,GAAG,YAAY,UAAU,EAAE,IAAI,eAAe,MAAM,QAAQ,GAC9D;GAEF,UAAU,GAAG,YAAY,SAAS,EAAE,EAAE;GACtC,UAAU,MAAM,OAAO;EACzB,CAAC;CACH,SAAS,OAAO;EAGd,UAAU,kBADR,iBAAiB,QAAQ,MAAM,UAAU,+BACC,EAAE,OAAO,QAAQ,CAAC;CAChE;AACF"}
|
|
@@ -32,7 +32,7 @@ const selectCmsEnvironment = async (environmentName, intlayerAPI, config) => {
|
|
|
32
32
|
appLogger(`Using environment: ${colorize(targetEnvironment.name, ANSIColors.CYAN)} (default)`);
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
await intlayerAPI.
|
|
35
|
+
await intlayerAPI.environment.selectEnvironment(String(targetEnvironment.id));
|
|
36
36
|
appLogger(`Using environment: ${colorize(targetEnvironment.name, ANSIColors.CYAN)}`);
|
|
37
37
|
} catch (error) {
|
|
38
38
|
appLogger(`Failed to select environment "${environmentName}": ${error}`, { level: "warn" });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectCmsEnvironment.mjs","names":[],"sources":["../../../src/utils/selectCmsEnvironment.ts"],"sourcesContent":["import type { IntlayerAPIProxy } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Selects the CMS project environment that matches `environmentName`.\n *\n * Convention: session.activeEnvironmentId = null means \"production\" (default).\n * When the target environment is the project's default, we reset the session to\n * null instead of storing the env's ObjectId — so no API call is needed.\n *\n * No-op when `environmentName` is undefined.\n */\nexport const selectCmsEnvironment = async (\n environmentName: string | undefined,\n intlayerAPI: IntlayerAPIProxy,\n config: IntlayerConfig\n): Promise<void> => {\n if (!environmentName) return;\n\n const appLogger = getAppLogger(config);\n\n try {\n const sessionResult = await intlayerAPI.oAuth.getCliSessionMe();\n const project = sessionResult.data?.project;\n\n if (!project) {\n appLogger(\n `Cannot resolve environment \"${environmentName}\": no project selected in session.`,\n { level: 'warn' }\n );\n return;\n }\n\n const projectEnvironments: any[] = (project as any).environments ?? [];\n\n if (projectEnvironments.length === 0) {\n // No environments stored — everything is implicitly production; nothing to select\n return;\n }\n\n const targetEnvironment = projectEnvironments.find(\n (environment: any) =>\n environment.name.toLowerCase() === environmentName.toLowerCase() ||\n String(environment.id) === environmentName\n );\n\n if (!targetEnvironment) {\n const availableNames = projectEnvironments\n .map((environment: any) => environment.name)\n .join(', ');\n appLogger(\n `Environment \"${environmentName}\" not found in project. Available: ${colorize(availableNames, ANSIColors.BLUE)}.`,\n { level: 'error' }\n );\n return;\n }\n\n // Production / default env → null convention; no API call needed\n if (targetEnvironment.isDefault) {\n appLogger(\n `Using environment: ${colorize(targetEnvironment.name, ANSIColors.CYAN)} (default)`\n );\n return;\n }\n\n await intlayerAPI.
|
|
1
|
+
{"version":3,"file":"selectCmsEnvironment.mjs","names":[],"sources":["../../../src/utils/selectCmsEnvironment.ts"],"sourcesContent":["import type { IntlayerAPIProxy } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Selects the CMS project environment that matches `environmentName`.\n *\n * Convention: session.activeEnvironmentId = null means \"production\" (default).\n * When the target environment is the project's default, we reset the session to\n * null instead of storing the env's ObjectId — so no API call is needed.\n *\n * No-op when `environmentName` is undefined.\n */\nexport const selectCmsEnvironment = async (\n environmentName: string | undefined,\n intlayerAPI: IntlayerAPIProxy,\n config: IntlayerConfig\n): Promise<void> => {\n if (!environmentName) return;\n\n const appLogger = getAppLogger(config);\n\n try {\n const sessionResult = await intlayerAPI.oAuth.getCliSessionMe();\n const project = sessionResult.data?.project;\n\n if (!project) {\n appLogger(\n `Cannot resolve environment \"${environmentName}\": no project selected in session.`,\n { level: 'warn' }\n );\n return;\n }\n\n const projectEnvironments: any[] = (project as any).environments ?? [];\n\n if (projectEnvironments.length === 0) {\n // No environments stored — everything is implicitly production; nothing to select\n return;\n }\n\n const targetEnvironment = projectEnvironments.find(\n (environment: any) =>\n environment.name.toLowerCase() === environmentName.toLowerCase() ||\n String(environment.id) === environmentName\n );\n\n if (!targetEnvironment) {\n const availableNames = projectEnvironments\n .map((environment: any) => environment.name)\n .join(', ');\n appLogger(\n `Environment \"${environmentName}\" not found in project. Available: ${colorize(availableNames, ANSIColors.BLUE)}.`,\n { level: 'error' }\n );\n return;\n }\n\n // Production / default env → null convention; no API call needed\n if (targetEnvironment.isDefault) {\n appLogger(\n `Using environment: ${colorize(targetEnvironment.name, ANSIColors.CYAN)} (default)`\n );\n return;\n }\n\n await intlayerAPI.environment.selectEnvironment(\n String(targetEnvironment.id)\n );\n\n appLogger(\n `Using environment: ${colorize(targetEnvironment.name, ANSIColors.CYAN)}`\n );\n } catch (error) {\n appLogger(`Failed to select environment \"${environmentName}\": ${error}`, {\n level: 'warn',\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAa,uBAAuB,OAClC,iBACA,aACA,WACkB;CAClB,IAAI,CAAC,iBAAiB;CAEtB,MAAM,YAAY,aAAa,MAAM;CAErC,IAAI;EAEF,MAAM,WAAU,MADY,YAAY,MAAM,gBAAgB,EACjC,CAAC,MAAM;EAEpC,IAAI,CAAC,SAAS;GACZ,UACE,+BAA+B,gBAAgB,qCAC/C,EAAE,OAAO,OAAO,CAClB;GACA;EACF;EAEA,MAAM,sBAA8B,QAAgB,gBAAgB,CAAC;EAErE,IAAI,oBAAoB,WAAW,GAEjC;EAGF,MAAM,oBAAoB,oBAAoB,MAC3C,gBACC,YAAY,KAAK,YAAY,MAAM,gBAAgB,YAAY,KAC/D,OAAO,YAAY,EAAE,MAAM,eAC/B;EAEA,IAAI,CAAC,mBAAmB;GACtB,MAAM,iBAAiB,oBACpB,KAAK,gBAAqB,YAAY,IAAI,CAAC,CAC3C,KAAK,IAAI;GACZ,UACE,gBAAgB,gBAAgB,qCAAqC,SAAS,gBAAgB,WAAW,IAAI,EAAE,IAC/G,EAAE,OAAO,QAAQ,CACnB;GACA;EACF;EAGA,IAAI,kBAAkB,WAAW;GAC/B,UACE,sBAAsB,SAAS,kBAAkB,MAAM,WAAW,IAAI,EAAE,WAC1E;GACA;EACF;EAEA,MAAM,YAAY,YAAY,kBAC5B,OAAO,kBAAkB,EAAE,CAC7B;EAEA,UACE,sBAAsB,SAAS,kBAAkB,MAAM,WAAW,IAAI,GACxE;CACF,SAAS,OAAO;EACd,UAAU,iCAAiC,gBAAgB,KAAK,SAAS,EACvE,OAAO,OACT,CAAC;CACH;AACF"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { ConfigurationOptions, dirname, setAPI } from "./cli.js";
|
|
|
4
4
|
import { startEditor } from "./editor.js";
|
|
5
5
|
import { extract } from "./extract.js";
|
|
6
6
|
import { FillOptions, fill } from "./fill/fill.js";
|
|
7
|
-
import { findProjectRoot, init } from "./init.js";
|
|
7
|
+
import { ESLINT_CONFIG_FILES, INIT_STEP_GROUPS, InitStep, InitStepOption, LOCALE_ROUTING_CHOICES, LocaleRoutingChoice, OPT_IN_INIT_STEPS, findProjectRoot, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, isEslintInstalled, parseLocaleRoutingChoice } from "./init.js";
|
|
8
|
+
import { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL, initChromeExtension } from "./initChromeExtension.js";
|
|
8
9
|
import { INFRA_MODES, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, InfraMode, InitInfraOptions, initInfra, parseInfraMode } from "./initInfra.js";
|
|
9
10
|
import { PLATFORM_OPTIONS, getDetectedPlatform, initSkills, promptPlatform } from "./initSkills.js";
|
|
10
11
|
import { listContentDeclaration, listContentDeclarationRows } from "./listContentDeclaration.js";
|
|
@@ -24,4 +25,4 @@ export type * from "@intlayer/engine/cli";
|
|
|
24
25
|
export type * from "@intlayer/engine/build";
|
|
25
26
|
export type * from "@intlayer/engine/utils";
|
|
26
27
|
export type * from "@intlayer/engine/watcher";
|
|
27
|
-
export { ConfigurationOptions, FillOptions, INFRA_MODES, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, InfraMode, InitInfraOptions, ListProjectsCommandOptions, PLATFORM_OPTIONS, ReviewDocMode, ScanCommandOptions, build, bundle, dirname, extract, fill, findProjectRoot, getDetectedPlatform, init, initInfra, initSkills, listContentDeclaration, listContentDeclarationRows, listMissingTranslations, listMissingTranslationsWithConfig, listProjectsCommand, liveSync, parseInfraMode, promptPlatform, pull, push, pushConfig, reviewDoc, scan, searchDoc, setAPI, startEditor, testMissingTranslations, translateDoc };
|
|
28
|
+
export { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, ConfigurationOptions, ESLINT_CONFIG_FILES, FillOptions, INFRA_MODES, INIT_STEP_GROUPS, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, InfraMode, InitInfraOptions, InitStep, InitStepOption, LOCALE_ROUTING_CHOICES, ListProjectsCommandOptions, LocaleRoutingChoice, MOZILLA_EXTENSION_URL, OPT_IN_INIT_STEPS, PLATFORM_OPTIONS, ReviewDocMode, ScanCommandOptions, build, bundle, dirname, extract, fill, findProjectRoot, getDetectedPlatform, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initChromeExtension, initInfra, initSkills, isEslintInstalled, listContentDeclaration, listContentDeclarationRows, listMissingTranslations, listMissingTranslationsWithConfig, listProjectsCommand, liveSync, parseInfraMode, parseLocaleRoutingChoice, promptPlatform, pull, push, pushConfig, reviewDoc, scan, searchDoc, setAPI, startEditor, testMissingTranslations, translateDoc };
|
package/dist/types/init.d.ts
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
|
-
import { InitOptions } from "@intlayer/engine/cli";
|
|
1
|
+
import { InitOptions, RoutingMode } from "@intlayer/engine/cli";
|
|
2
2
|
//#region src/init.d.ts
|
|
3
3
|
export declare const findProjectRoot: (startDir: string) => string;
|
|
4
|
+
/** Individually selectable setup steps exposed by the interactive init flow. */
|
|
5
|
+
export type InitStep = 'packages' | 'githubActions' | 'projectSetup' | 'vscodeExtension' | 'lsp' | 'eslint' | 'skills' | 'mcp' | 'infra' | 'cms' | 'chromeExtension';
|
|
6
|
+
/** A checkbox entry of the interactive init flow. */
|
|
7
|
+
export type InitStepOption = {
|
|
8
|
+
value: InitStep;
|
|
9
|
+
label: string;
|
|
10
|
+
hint: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Steps shown but not pre-selected. The infrastructure installer touches the
|
|
14
|
+
* machine (downloads an app, pulls Docker images) and browser extensions open external URLs
|
|
15
|
+
* rather than modifying the project, so they must be an explicit choice.
|
|
16
|
+
*/
|
|
17
|
+
export declare const OPT_IN_INIT_STEPS: InitStep[];
|
|
18
|
+
/** Grouped checkbox entries of the interactive init flow, in display order. */
|
|
19
|
+
export declare const INIT_STEP_GROUPS: Record<string, InitStepOption[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Locale routing choice: a `routing.mode` served through the locale proxy, or
|
|
22
|
+
* `none` to leave routing to the app (`no-prefix` with the proxy disabled).
|
|
23
|
+
*/
|
|
24
|
+
export type LocaleRoutingChoice = RoutingMode | 'none';
|
|
25
|
+
/** Locale routing choices accepted by `--routing`. */
|
|
26
|
+
export declare const LOCALE_ROUTING_CHOICES: LocaleRoutingChoice[];
|
|
27
|
+
/** Validates a `--routing` value, throwing on an unknown choice. */
|
|
28
|
+
export declare const parseLocaleRoutingChoice: (value: string) => LocaleRoutingChoice;
|
|
29
|
+
/** Maps a routing choice to the init options it sets. */
|
|
30
|
+
export declare const getRoutingInitOptions: (choice: LocaleRoutingChoice) => Pick<InitOptions, 'routingMode' | 'enableProxy'>;
|
|
31
|
+
/** Known ESLint and oxlint configuration file names. */
|
|
32
|
+
export declare const ESLINT_CONFIG_FILES: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Checks whether ESLint or a compatible linter (oxlint) is installed or
|
|
35
|
+
* configured in the project.
|
|
36
|
+
*/
|
|
37
|
+
export declare const isEslintInstalled: (dependencies: Record<string, string>, root?: string) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Computes the initial setup steps selected by default in the interactive prompt.
|
|
40
|
+
* Steps like infrastructure and browser extension require opt-in, while
|
|
41
|
+
* eslint is preselected only if installed on the project.
|
|
42
|
+
*/
|
|
43
|
+
export declare const getInitialInitSteps: (dependencies: Record<string, string>, root?: string) => InitStep[];
|
|
44
|
+
/** Reads the merged dependencies of the project at `root`. */
|
|
45
|
+
export declare const getProjectDependencies: (root: string) => Record<string, string>;
|
|
4
46
|
export declare const init: (projectRoot?: string, options?: InitOptions, interactive?: boolean) => Promise<void>;
|
|
5
47
|
//#endregion
|
|
6
48
|
//# sourceMappingURL=init.d.ts.map
|
package/dist/types/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","names":[],"sources":["../../src/init.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"init.d.ts","names":[],"sources":["../../src/init.ts"],"mappings":";;qBAmBa,kBAAe;;YAgBhB;;YAcA;EACV,OAAO;EACP;EACA;;;;;;;qBAQW,mBAAmB;;qBAGnB,kBAAkB,eAAe;;;;;YAsElC,sBAAsB;;qBAoCrB,wBAAsB;;qBAKtB,2BAAwB,kBAElC;;qBAaU,wBAAqB,QACxB,wBACP,KAAK;;qBAMK;;;;;qBAoBA,oBAAiB,cACd,wBAAsB;;;;;;qBAkBzB,sBAAmB,cAChB,wBAAsB,kBAEnC;;qBAcU,yBAAsB,iBAEhC;qBAgJU,OAAI,sBACK,UACV,aAAW,0BACA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/initChromeExtension.d.ts
|
|
2
|
+
export declare const CHROME_EXTENSION_URL = "https://chromewebstore.google.com/detail/intlayer_i18n_scanner/pmlehcgmjmfimmjnembihbakhnheiabc";
|
|
3
|
+
export declare const MOZILLA_EXTENSION_URL = "https://addons.mozilla.org/en-US/firefox/addon/intlayer-i18n-scanner/";
|
|
4
|
+
export declare const BROWSER_EXTENSION_OPTIONS: {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
hint: string;
|
|
8
|
+
}[];
|
|
9
|
+
export declare const initChromeExtension: () => Promise<void>;
|
|
10
|
+
//#endregion
|
|
11
|
+
//# sourceMappingURL=initChromeExtension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initChromeExtension.d.ts","names":[],"sources":["../../src/initChromeExtension.ts"],"mappings":";qBAGa;qBAGA;qBAGA;;;;;qBAaA,2BAAmB"}
|
package/dist/types/scan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","names":[],"sources":["../../src/scan.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"scan.d.ts","names":[],"sources":["../../src/scan.ts"],"mappings":";;;YAcY;;EAEV;;EAEA;EACA,gBAAgB;;;;;;;;;;;qBAiGL,OAAI,aACJ,UACF,uBACR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchDoc.d.ts","names":[],"sources":["../../src/searchDoc.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"searchDoc.d.ts","names":[],"sources":["../../src/searchDoc.ts"],"mappings":";;UAoBU;EACR;EACA;EACA,gBAAgB;;qBAGL,cAAS,OAAA,OAAA,iBAInB,qBAAgB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/cli",
|
|
3
|
-
"version": "9.5.
|
|
3
|
+
"version": "9.5.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Provides uniform command-line interface scripts for Intlayer, used in packages like intlayer-cli and intlayer.",
|
|
6
6
|
"keywords": [
|
|
@@ -68,20 +68,20 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@clack/prompts": "1.7.0",
|
|
71
|
-
"@intlayer/api": "9.5.
|
|
72
|
-
"@intlayer/babel": "9.5.
|
|
73
|
-
"@intlayer/config": "9.5.
|
|
74
|
-
"@intlayer/core": "9.5.
|
|
75
|
-
"@intlayer/dictionaries-entry": "9.5.
|
|
76
|
-
"@intlayer/engine": "9.5.
|
|
77
|
-
"@intlayer/types": "9.5.
|
|
71
|
+
"@intlayer/api": "9.5.11",
|
|
72
|
+
"@intlayer/babel": "9.5.11",
|
|
73
|
+
"@intlayer/config": "9.5.11",
|
|
74
|
+
"@intlayer/core": "9.5.11",
|
|
75
|
+
"@intlayer/dictionaries-entry": "9.5.11",
|
|
76
|
+
"@intlayer/engine": "9.5.11",
|
|
77
|
+
"@intlayer/types": "9.5.11",
|
|
78
78
|
"commander": "15.0.0",
|
|
79
79
|
"enquirer": "2.4.1",
|
|
80
80
|
"eventsource": "5.1.1",
|
|
81
81
|
"fast-glob": "3.3.3"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@intlayer/ai": "9.5.
|
|
84
|
+
"@intlayer/ai": "9.5.11",
|
|
85
85
|
"@types/node": "^22",
|
|
86
86
|
"@utils/ts-config": "1.0.4",
|
|
87
87
|
"@utils/ts-config-types": "1.0.4",
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
"rimraf": "6.1.3",
|
|
90
90
|
"tsdown": "0.23.0",
|
|
91
91
|
"typescript": "7.0.2",
|
|
92
|
-
"vitest": "5.0.
|
|
92
|
+
"vitest": "5.0.2"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"@intlayer/ai": "9.5.
|
|
95
|
+
"@intlayer/ai": "9.5.11"
|
|
96
96
|
},
|
|
97
97
|
"peerDependenciesMeta": {
|
|
98
98
|
"@intlayer/ai": {
|