@intlayer/cli 9.5.12 → 9.7.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/dist/cjs/IntlayerEventListener.cjs.map +1 -1
- package/dist/cjs/_virtual/_utils_asset.cjs +1 -1
- package/dist/cjs/auth/login.cjs +11 -0
- package/dist/cjs/auth/login.cjs.map +1 -1
- package/dist/cjs/auth/setupRepository.cjs +95 -0
- package/dist/cjs/auth/setupRepository.cjs.map +1 -0
- package/dist/cjs/ci.cjs +1 -1
- package/dist/cjs/cli.cjs +59 -14
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/editor.cjs +61 -32
- package/dist/cjs/editor.cjs.map +1 -1
- package/dist/cjs/extract.cjs +1 -1
- package/dist/cjs/fill/fill.cjs +25 -9
- package/dist/cjs/fill/fill.cjs.map +1 -1
- package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
- package/dist/cjs/getTargetDictionary.cjs +1 -1
- package/dist/cjs/index.cjs +29 -1
- package/dist/cjs/init.cjs +261 -36
- package/dist/cjs/init.cjs.map +1 -1
- package/dist/cjs/initChromeExtension.cjs +26 -3
- package/dist/cjs/initChromeExtension.cjs.map +1 -1
- package/dist/cjs/initInfra.cjs +6 -0
- package/dist/cjs/initInfra.cjs.map +1 -1
- package/dist/cjs/initMCP.cjs +18 -7
- package/dist/cjs/initMCP.cjs.map +1 -1
- package/dist/cjs/initSkills.cjs +37 -34
- package/dist/cjs/initSkills.cjs.map +1 -1
- package/dist/cjs/listContentDeclaration.cjs +1 -1
- package/dist/cjs/listProjects.cjs +1 -1
- package/dist/cjs/liveSync.cjs.map +1 -1
- package/dist/cjs/pull.cjs +1 -1
- package/dist/cjs/push/push.cjs +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs +1 -1
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateDoc.cjs +1 -1
- package/dist/cjs/upgrade.cjs +1 -1
- package/dist/cjs/utils/checkAccess.cjs.map +1 -1
- package/dist/cjs/utils/detectGitRepository.cjs +87 -0
- package/dist/cjs/utils/detectGitRepository.cjs.map +1 -0
- package/dist/cjs/utils/getParentPackageJSON.cjs +1 -1
- package/dist/cjs/utils/isInteractiveTerminal.cjs +12 -0
- package/dist/cjs/utils/isInteractiveTerminal.cjs.map +1 -0
- package/dist/cjs/utils/parseChoice.cjs +21 -0
- package/dist/cjs/utils/parseChoice.cjs.map +1 -0
- package/dist/esm/IntlayerEventListener.mjs.map +1 -1
- package/dist/esm/_virtual/_utils_asset.mjs +1 -1
- package/dist/esm/auth/login.mjs +11 -0
- package/dist/esm/auth/login.mjs.map +1 -1
- package/dist/esm/auth/setupRepository.mjs +90 -0
- package/dist/esm/auth/setupRepository.mjs.map +1 -0
- package/dist/esm/ci.mjs +1 -1
- package/dist/esm/cli.mjs +59 -14
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/editor.mjs +62 -34
- package/dist/esm/editor.mjs.map +1 -1
- package/dist/esm/extract.mjs +1 -1
- package/dist/esm/fill/fill.mjs +25 -9
- package/dist/esm/fill/fill.mjs.map +1 -1
- package/dist/esm/fill/translateDictionary.mjs.map +1 -1
- package/dist/esm/getTargetDictionary.mjs +1 -1
- package/dist/esm/index.mjs +10 -5
- package/dist/esm/init.mjs +253 -38
- package/dist/esm/init.mjs.map +1 -1
- package/dist/esm/initChromeExtension.mjs +24 -4
- package/dist/esm/initChromeExtension.mjs.map +1 -1
- package/dist/esm/initInfra.mjs +6 -0
- package/dist/esm/initInfra.mjs.map +1 -1
- package/dist/esm/initMCP.mjs +21 -11
- package/dist/esm/initMCP.mjs.map +1 -1
- package/dist/esm/initSkills.mjs +37 -36
- package/dist/esm/initSkills.mjs.map +1 -1
- package/dist/esm/listContentDeclaration.mjs +1 -1
- package/dist/esm/listProjects.mjs +1 -1
- package/dist/esm/liveSync.mjs.map +1 -1
- package/dist/esm/pull.mjs +1 -1
- package/dist/esm/push/push.mjs +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs +1 -1
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
- package/dist/esm/translateDoc/translateDoc.mjs +1 -1
- package/dist/esm/upgrade.mjs +1 -1
- package/dist/esm/utils/checkAccess.mjs.map +1 -1
- package/dist/esm/utils/detectGitRepository.mjs +85 -0
- package/dist/esm/utils/detectGitRepository.mjs.map +1 -0
- package/dist/esm/utils/getParentPackageJSON.mjs +1 -1
- package/dist/esm/utils/isInteractiveTerminal.mjs +11 -0
- package/dist/esm/utils/isInteractiveTerminal.mjs.map +1 -0
- package/dist/esm/utils/parseChoice.mjs +20 -0
- package/dist/esm/utils/parseChoice.mjs.map +1 -0
- package/dist/types/IntlayerEventListener.d.ts +1 -1
- package/dist/types/IntlayerEventListener.d.ts.map +1 -1
- package/dist/types/auth/login.d.ts +5 -0
- package/dist/types/auth/login.d.ts.map +1 -1
- package/dist/types/auth/setupRepository.d.ts +25 -0
- package/dist/types/auth/setupRepository.d.ts.map +1 -0
- package/dist/types/editor.d.ts +10 -0
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/types/fill/fill.d.ts +23 -2
- package/dist/types/fill/fill.d.ts.map +1 -1
- package/dist/types/fill/index.d.ts +2 -2
- package/dist/types/fill/translateDictionary.d.ts +1 -2
- package/dist/types/fill/translateDictionary.d.ts.map +1 -1
- package/dist/types/index.d.ts +11 -6
- package/dist/types/init.d.ts +55 -2
- package/dist/types/init.d.ts.map +1 -1
- package/dist/types/initChromeExtension.d.ts +19 -1
- package/dist/types/initChromeExtension.d.ts.map +1 -1
- package/dist/types/initInfra.d.ts.map +1 -1
- package/dist/types/initMCP.d.ts +16 -2
- package/dist/types/initMCP.d.ts.map +1 -1
- package/dist/types/initSkills.d.ts +23 -6
- package/dist/types/initSkills.d.ts.map +1 -1
- package/dist/types/liveSync.d.ts.map +1 -1
- package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
- package/dist/types/reviewDoc/reviewDoc.d.ts.map +1 -1
- package/dist/types/reviewDoc/reviewDocBlockAware.d.ts +1 -2
- package/dist/types/reviewDoc/reviewDocBlockAware.d.ts.map +1 -1
- package/dist/types/translateDoc/types.d.ts +1 -1
- package/dist/types/utils/checkAccess.d.ts +2 -1
- package/dist/types/utils/checkAccess.d.ts.map +1 -1
- package/dist/types/utils/detectGitRepository.d.ts +34 -0
- package/dist/types/utils/detectGitRepository.d.ts.map +1 -0
- package/dist/types/utils/isInteractiveTerminal.d.ts +9 -0
- package/dist/types/utils/isInteractiveTerminal.d.ts.map +1 -0
- package/dist/types/utils/parseChoice.d.ts +13 -0
- package/dist/types/utils/parseChoice.d.ts.map +1 -0
- package/dist/types/utils/selectCmsEnvironment.d.ts +1 -1
- package/package.json +12 -12
package/dist/esm/initMCP.mjs
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { loadPrompts } from "./loadPrompts.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { isInteractiveTerminal } from "./utils/isInteractiveTerminal.mjs";
|
|
3
|
+
import { parseChoice } from "./utils/parseChoice.mjs";
|
|
4
|
+
import { promptPlatform, resolvePlatformWithoutPrompt } from "./initSkills.mjs";
|
|
5
|
+
import { resolveProjectRoot } from "./init.mjs";
|
|
6
|
+
import { MCP_TRANSPORTS, installMCP } from "@intlayer/engine/cli";
|
|
6
7
|
|
|
7
8
|
//#region src/initMCP.ts
|
|
8
|
-
|
|
9
|
+
/** Validates a `--transport` value, ignoring case. */
|
|
10
|
+
const parseMCPTransport = (value) => parseChoice(value, MCP_TRANSPORTS, "--transport");
|
|
11
|
+
/**
|
|
12
|
+
* Configures the Intlayer MCP server for an AI platform. Without a terminal
|
|
13
|
+
* (AI agents, CI), nothing is prompted: the platform defaults to the detected
|
|
14
|
+
* one and the transport to `stdio`.
|
|
15
|
+
*/
|
|
16
|
+
const initMCP = async (projectRoot, options = {}) => {
|
|
9
17
|
const p = await loadPrompts();
|
|
10
|
-
const root =
|
|
18
|
+
const root = resolveProjectRoot(projectRoot);
|
|
19
|
+
const isInteractive = isInteractiveTerminal();
|
|
11
20
|
p.intro("Initializing Intlayer MCP Server");
|
|
12
|
-
const platform =
|
|
21
|
+
const platform = isInteractive ? options.platform ?? await promptPlatform(root) : await resolvePlatformWithoutPrompt(root, options.platform, "intlayer init mcp");
|
|
13
22
|
if (!platform) {
|
|
14
23
|
p.cancel("Operation cancelled. No platform selected.");
|
|
15
24
|
return;
|
|
16
25
|
}
|
|
17
|
-
const transport = await p.select({
|
|
26
|
+
const transport = options.transport ?? (isInteractive ? await p.select({
|
|
18
27
|
message: "Which transport method do you want to use?",
|
|
19
28
|
options: [{
|
|
20
29
|
value: "stdio",
|
|
@@ -25,8 +34,8 @@ const initMCP = async (projectRoot, preselectedPlatform) => {
|
|
|
25
34
|
label: "Remote server (SSE)",
|
|
26
35
|
hint: "Hosted by Intlayer. Documentation only."
|
|
27
36
|
}]
|
|
28
|
-
});
|
|
29
|
-
if (p.isCancel(transport)
|
|
37
|
+
}) : "stdio");
|
|
38
|
+
if (p.isCancel(transport)) {
|
|
30
39
|
p.cancel("Operation cancelled.");
|
|
31
40
|
return;
|
|
32
41
|
}
|
|
@@ -39,10 +48,11 @@ const initMCP = async (projectRoot, preselectedPlatform) => {
|
|
|
39
48
|
} catch (error) {
|
|
40
49
|
s.stop("Failed to configure MCP Server");
|
|
41
50
|
p.log.error(error instanceof Error ? error.message : String(error));
|
|
51
|
+
process.exitCode = 1;
|
|
42
52
|
}
|
|
43
53
|
p.outro("Intlayer MCP Server initialization complete");
|
|
44
54
|
};
|
|
45
55
|
|
|
46
56
|
//#endregion
|
|
47
|
-
export { initMCP };
|
|
57
|
+
export { initMCP, parseMCPTransport };
|
|
48
58
|
//# sourceMappingURL=initMCP.mjs.map
|
package/dist/esm/initMCP.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initMCP.mjs","names":[],"sources":["../../src/initMCP.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"initMCP.mjs","names":[],"sources":["../../src/initMCP.ts"],"sourcesContent":["import {\n installMCP,\n MCP_TRANSPORTS,\n type MCPTransport,\n type Platform,\n} from '@intlayer/engine/cli';\nimport { resolveProjectRoot } from './init';\nimport { promptPlatform, resolvePlatformWithoutPrompt } from './initSkills';\nimport { loadPrompts } from './loadPrompts';\nimport { isInteractiveTerminal } from './utils/isInteractiveTerminal';\nimport { parseChoice } from './utils/parseChoice';\n\n/** Options of {@link initMCP}; anything omitted is prompted for. */\nexport type InitMCPOptions = {\n /** AI platform to configure the MCP server for. */\n platform?: Platform;\n /** MCP transport. Defaults to `stdio` without a terminal. */\n transport?: MCPTransport;\n};\n\n/** Validates a `--transport` value, ignoring case. */\nexport const parseMCPTransport = (value: string): MCPTransport =>\n parseChoice(value, MCP_TRANSPORTS, '--transport');\n\n/**\n * Configures the Intlayer MCP server for an AI platform. Without a terminal\n * (AI agents, CI), nothing is prompted: the platform defaults to the detected\n * one and the transport to `stdio`.\n */\nexport const initMCP = async (\n projectRoot?: string,\n options: InitMCPOptions = {}\n) => {\n const p = await loadPrompts();\n\n const root = resolveProjectRoot(projectRoot);\n const isInteractive = isInteractiveTerminal();\n\n p.intro('Initializing Intlayer MCP Server');\n\n const platform = isInteractive\n ? (options.platform ?? (await promptPlatform(root)))\n : await resolvePlatformWithoutPrompt(\n root,\n options.platform,\n 'intlayer init mcp'\n );\n\n if (!platform) {\n p.cancel('Operation cancelled. No platform selected.');\n return;\n }\n\n const transport =\n options.transport ??\n (isInteractive\n ? await p.select<MCPTransport>({\n message: 'Which transport method do you want to use?',\n options: [\n {\n value: 'stdio',\n label: 'Local server (stdio)',\n hint: 'Recommended. Integrates all features including CLI tools.',\n },\n {\n value: 'sse',\n label: 'Remote server (SSE)',\n hint: 'Hosted by Intlayer. Documentation only.',\n },\n ],\n })\n : 'stdio');\n\n if (p.isCancel(transport)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const s = p.spinner();\n s.start('Configuring MCP Server...');\n\n try {\n const result = await installMCP(root, platform, transport);\n\n s.stop('MCP Server configured successfully');\n\n p.note(result, 'Success');\n } catch (error) {\n s.stop('Failed to configure MCP Server');\n p.log.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n\n p.outro('Intlayer MCP Server initialization complete');\n};\n"],"mappings":";;;;;;;;;AAqBA,MAAa,qBAAqB,UAChC,YAAY,OAAO,gBAAgB,aAAa;;;;;;AAOlD,MAAa,UAAU,OACrB,aACA,UAA0B,CAAC,MACxB;CACH,MAAM,IAAI,MAAM,YAAY;CAE5B,MAAM,OAAO,mBAAmB,WAAW;CAC3C,MAAM,gBAAgB,sBAAsB;CAE5C,EAAE,MAAM,kCAAkC;CAE1C,MAAM,WAAW,gBACZ,QAAQ,YAAa,MAAM,eAAe,IAAI,IAC/C,MAAM,6BACJ,MACA,QAAQ,UACR,mBACF;CAEJ,IAAI,CAAC,UAAU;EACb,EAAE,OAAO,4CAA4C;EACrD;CACF;CAEA,MAAM,YACJ,QAAQ,cACP,gBACG,MAAM,EAAE,OAAqB;EAC3B,SAAS;EACT,SAAS,CACP;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,GACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,CACF;CACF,CAAC,IACD;CAEN,IAAI,EAAE,SAAS,SAAS,GAAG;EACzB,EAAE,OAAO,sBAAsB;EAC/B;CACF;CAEA,MAAM,IAAI,EAAE,QAAQ;CACpB,EAAE,MAAM,2BAA2B;CAEnC,IAAI;EACF,MAAM,SAAS,MAAM,WAAW,MAAM,UAAU,SAAS;EAEzD,EAAE,KAAK,oCAAoC;EAE3C,EAAE,KAAK,QAAQ,SAAS;CAC1B,SAAS,OAAO;EACd,EAAE,KAAK,gCAAgC;EACvC,EAAE,IAAI,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;EAClE,QAAQ,WAAW;CACrB;CAEA,EAAE,MAAM,6CAA6C;AACvD"}
|
package/dist/esm/initSkills.mjs
CHANGED
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
import { loadPrompts } from "./loadPrompts.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { isInteractiveTerminal } from "./utils/isInteractiveTerminal.mjs";
|
|
3
|
+
import { parseChoice } from "./utils/parseChoice.mjs";
|
|
4
|
+
import { getProjectDependencies, resolveProjectRoot } from "./init.mjs";
|
|
5
|
+
import { PLATFORMS, PLATFORMS_METADATA, SKILLS, SKILLS_METADATA, detectPlatform, getInitialSkills, installSkills } from "@intlayer/engine/cli";
|
|
6
6
|
import enquirer from "enquirer";
|
|
7
7
|
|
|
8
8
|
//#region src/initSkills.ts
|
|
9
|
-
const PLATFORM_CHECKS = PLATFORMS.filter((platform) => PLATFORMS_METADATA[platform]?.check).map((platform) => ({
|
|
10
|
-
check: PLATFORMS_METADATA[platform]?.check ?? (() => false),
|
|
11
|
-
platform
|
|
12
|
-
}));
|
|
13
9
|
const PLATFORM_OPTIONS = PLATFORMS.map((platform) => ({
|
|
14
10
|
value: platform,
|
|
15
11
|
label: PLATFORMS_METADATA[platform]?.label ?? "",
|
|
16
12
|
hint: `(${PLATFORMS_METADATA[platform]?.dir})`
|
|
17
13
|
}));
|
|
18
|
-
const getDetectedPlatform = () => PLATFORM_CHECKS.find(({ check }) => check())?.platform;
|
|
19
|
-
const getDependencies = (root) => {
|
|
20
|
-
try {
|
|
21
|
-
const packageJsonPath = join(root, "package.json");
|
|
22
|
-
if (!existsSync(packageJsonPath)) return {};
|
|
23
|
-
const { dependencies = {}, devDependencies = {} } = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
24
|
-
return {
|
|
25
|
-
...dependencies,
|
|
26
|
-
...devDependencies
|
|
27
|
-
};
|
|
28
|
-
} catch {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
14
|
/**
|
|
33
15
|
* Asks which AI platform the user is using, preselecting the detected one.
|
|
34
16
|
* Resolves to `undefined` when the prompt is cancelled.
|
|
35
17
|
*/
|
|
36
|
-
const promptPlatform = async () => {
|
|
37
|
-
const detectedPlatform =
|
|
18
|
+
const promptPlatform = async (projectRoot) => {
|
|
19
|
+
const detectedPlatform = detectPlatform(projectRoot);
|
|
38
20
|
try {
|
|
39
21
|
return (await enquirer.prompt({
|
|
40
22
|
type: "autocomplete",
|
|
@@ -52,32 +34,50 @@ const promptPlatform = async () => {
|
|
|
52
34
|
return;
|
|
53
35
|
}
|
|
54
36
|
};
|
|
37
|
+
/** Validates a `--platform` value, ignoring case. */
|
|
38
|
+
const parsePlatform = (value) => parseChoice(value, PLATFORMS, "--platform");
|
|
39
|
+
/** Validates the `--skills` values, ignoring case. */
|
|
40
|
+
const parseSkills = (values) => values.flatMap((value) => value.split(",")).filter((value) => value.trim() !== "").map((value) => parseChoice(value, SKILLS, "--skills"));
|
|
41
|
+
/**
|
|
42
|
+
* Resolves the platform without prompting: the given one, else the detected
|
|
43
|
+
* one. Logs how to pass it and sets a failing exit code when neither exists.
|
|
44
|
+
*/
|
|
45
|
+
const resolvePlatformWithoutPrompt = async (projectRoot, platform, command) => {
|
|
46
|
+
const resolvedPlatform = platform ?? detectPlatform(projectRoot);
|
|
47
|
+
if (!resolvedPlatform) {
|
|
48
|
+
(await loadPrompts()).log.error(`No AI platform detected. Pass it explicitly: ${command} --platform <platform>\nPlatforms: ${PLATFORMS.join(", ")}`);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
return resolvedPlatform;
|
|
52
|
+
};
|
|
55
53
|
/**
|
|
56
54
|
* Installs the Intlayer documentation skills. The skills are picked first, then
|
|
57
|
-
* the platform
|
|
58
|
-
*
|
|
55
|
+
* the platform. Without a terminal (AI agents, CI), nothing is prompted: the
|
|
56
|
+
* skills default to the project stack and the platform to the detected one.
|
|
57
|
+
* Resolves to the platform used, so a following step (e.g. MCP) can reuse it
|
|
58
|
+
* without asking again.
|
|
59
59
|
*/
|
|
60
|
-
const initSkills = async (projectRoot,
|
|
60
|
+
const initSkills = async (projectRoot, options = {}) => {
|
|
61
61
|
const p = await loadPrompts();
|
|
62
|
-
const root =
|
|
62
|
+
const root = resolveProjectRoot(projectRoot);
|
|
63
|
+
const isInteractive = isInteractiveTerminal();
|
|
63
64
|
p.intro("Initializing Intlayer skills");
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const selectedSkills = await p.multiselect({
|
|
65
|
+
const initialSkills = getInitialSkills(getProjectDependencies(root));
|
|
66
|
+
const selectedSkills = options.skills ?? (isInteractive ? await p.multiselect({
|
|
67
67
|
message: "Select the documentation skills to provide to your AI:",
|
|
68
|
-
initialValues,
|
|
68
|
+
initialValues: initialSkills,
|
|
69
69
|
options: SKILLS.map((skill) => ({
|
|
70
70
|
value: skill,
|
|
71
71
|
label: skill,
|
|
72
72
|
hint: SKILLS_METADATA[skill]
|
|
73
73
|
})),
|
|
74
74
|
required: false
|
|
75
|
-
});
|
|
76
|
-
if (p.isCancel(selectedSkills) ||
|
|
75
|
+
}) : initialSkills);
|
|
76
|
+
if (p.isCancel(selectedSkills) || selectedSkills.length === 0) {
|
|
77
77
|
p.cancel("Operation cancelled. No skills selected.");
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
|
-
const platform =
|
|
80
|
+
const platform = isInteractive ? options.platform ?? await promptPlatform(root) : await resolvePlatformWithoutPrompt(root, options.platform, "intlayer init skills");
|
|
81
81
|
if (!platform) {
|
|
82
82
|
p.cancel("Operation cancelled. No platform selected.");
|
|
83
83
|
return;
|
|
@@ -91,11 +91,12 @@ const initSkills = async (projectRoot, preselectedPlatform) => {
|
|
|
91
91
|
} catch (error) {
|
|
92
92
|
s.stop("Failed to install skills");
|
|
93
93
|
p.log.error(error instanceof Error ? error.message : String(error));
|
|
94
|
+
process.exitCode = 1;
|
|
94
95
|
}
|
|
95
96
|
p.outro("Intlayer skills initialization complete");
|
|
96
97
|
return platform;
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
//#endregion
|
|
100
|
-
export { PLATFORM_OPTIONS,
|
|
101
|
+
export { PLATFORM_OPTIONS, initSkills, parsePlatform, parseSkills, promptPlatform, resolvePlatformWithoutPrompt };
|
|
101
102
|
//# sourceMappingURL=initSkills.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initSkills.mjs","names":[],"sources":["../../src/initSkills.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"initSkills.mjs","names":[],"sources":["../../src/initSkills.ts"],"sourcesContent":["import {\n detectPlatform,\n getInitialSkills,\n installSkills,\n PLATFORMS,\n PLATFORMS_METADATA,\n type Platform,\n SKILLS,\n SKILLS_METADATA,\n type Skill,\n} from '@intlayer/engine/cli';\nimport enquirer from 'enquirer';\nimport { getProjectDependencies, resolveProjectRoot } from './init';\nimport { loadPrompts } from './loadPrompts';\nimport { isInteractiveTerminal } from './utils/isInteractiveTerminal';\nimport { parseChoice } from './utils/parseChoice';\n\nexport const PLATFORM_OPTIONS: Array<{\n value: Platform;\n label: string;\n hint: string;\n}> = PLATFORMS.map((platform) => ({\n value: platform,\n label: PLATFORMS_METADATA[platform]?.label ?? '',\n hint: `(${PLATFORMS_METADATA[platform]?.dir})`,\n}));\n\n/**\n * Asks which AI platform the user is using, preselecting the detected one.\n * Resolves to `undefined` when the prompt is cancelled.\n */\nexport const promptPlatform = async (\n projectRoot: string\n): Promise<Platform | undefined> => {\n const detectedPlatform = detectPlatform(projectRoot);\n\n try {\n const response = await enquirer.prompt<{ platform: Platform }>({\n type: 'autocomplete',\n name: 'platform',\n message: 'Which platform are you using? (Type to search)',\n multiple: false,\n initial: detectedPlatform\n ? PLATFORMS.indexOf(detectedPlatform)\n : undefined,\n choices: PLATFORM_OPTIONS.map((option) => ({\n name: option.value,\n message: option.label,\n hint: option.hint,\n })),\n });\n\n return response.platform;\n } catch {\n return undefined;\n }\n};\n\n/** Options of {@link initSkills}; anything omitted is prompted for. */\nexport type InitSkillsOptions = {\n /** AI platform to install the skills for. */\n platform?: Platform;\n /** Skills to install. Defaults to the ones matching the project stack. */\n skills?: Skill[];\n};\n\n/** Validates a `--platform` value, ignoring case. */\nexport const parsePlatform = (value: string): Platform =>\n parseChoice(value, PLATFORMS, '--platform');\n\n/** Validates the `--skills` values, ignoring case. */\nexport const parseSkills = (values: string[]): Skill[] =>\n values\n .flatMap((value) => value.split(','))\n .filter((value) => value.trim() !== '')\n .map((value) => parseChoice(value, SKILLS, '--skills'));\n\n/**\n * Resolves the platform without prompting: the given one, else the detected\n * one. Logs how to pass it and sets a failing exit code when neither exists.\n */\nexport const resolvePlatformWithoutPrompt = async (\n projectRoot: string,\n platform: Platform | undefined,\n command: string\n): Promise<Platform | undefined> => {\n const resolvedPlatform = platform ?? detectPlatform(projectRoot);\n\n if (!resolvedPlatform) {\n const p = await loadPrompts();\n\n p.log.error(\n `No AI platform detected. Pass it explicitly: ${command} --platform <platform>\\nPlatforms: ${PLATFORMS.join(', ')}`\n );\n process.exitCode = 1;\n }\n\n return resolvedPlatform;\n};\n\n/**\n * Installs the Intlayer documentation skills. The skills are picked first, then\n * the platform. Without a terminal (AI agents, CI), nothing is prompted: the\n * skills default to the project stack and the platform to the detected one.\n * Resolves to the platform used, so a following step (e.g. MCP) can reuse it\n * without asking again.\n */\nexport const initSkills = async (\n projectRoot?: string,\n options: InitSkillsOptions = {}\n): Promise<Platform | undefined> => {\n const p = await loadPrompts();\n\n const root = resolveProjectRoot(projectRoot);\n const isInteractive = isInteractiveTerminal();\n\n p.intro('Initializing Intlayer skills');\n\n const initialSkills = getInitialSkills(getProjectDependencies(root));\n\n const selectedSkills =\n options.skills ??\n (isInteractive\n ? await p.multiselect({\n message: 'Select the documentation skills to provide to your AI:',\n initialValues: initialSkills,\n options: SKILLS.map((skill) => ({\n value: skill,\n label: skill,\n hint: SKILLS_METADATA[skill],\n })),\n required: false,\n })\n : initialSkills);\n\n if (p.isCancel(selectedSkills) || selectedSkills.length === 0) {\n p.cancel('Operation cancelled. No skills selected.');\n return;\n }\n\n const platform = isInteractive\n ? (options.platform ?? (await promptPlatform(root)))\n : await resolvePlatformWithoutPrompt(\n root,\n options.platform,\n 'intlayer init skills'\n );\n\n if (!platform) {\n p.cancel('Operation cancelled. No platform selected.');\n return;\n }\n\n const s = p.spinner();\n s.start('Installing skills...');\n\n try {\n const result = await installSkills(root, platform, selectedSkills);\n\n s.stop('Skills installed successfully');\n\n p.note(result, 'Success');\n } catch (error) {\n s.stop('Failed to install skills');\n p.log.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n\n p.outro('Intlayer skills initialization complete');\n\n return platform;\n};\n"],"mappings":";;;;;;;;AAiBA,MAAa,mBAIR,UAAU,KAAK,cAAc;CAChC,OAAO;CACP,OAAO,mBAAmB,SAAS,EAAE,SAAS;CAC9C,MAAM,IAAI,mBAAmB,SAAS,EAAE,IAAI;AAC9C,EAAE;;;;;AAMF,MAAa,iBAAiB,OAC5B,gBACkC;CAClC,MAAM,mBAAmB,eAAe,WAAW;CAEnD,IAAI;EAgBF,QAAO,MAfgB,SAAS,OAA+B;GAC7D,MAAM;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS,mBACL,UAAU,QAAQ,gBAAgB,IAClC;GACJ,SAAS,iBAAiB,KAAK,YAAY;IACzC,MAAM,OAAO;IACb,SAAS,OAAO;IAChB,MAAM,OAAO;GACf,EAAE;EACJ,CAAC,EAEc,CAAC;CAClB,QAAQ;EACN;CACF;AACF;;AAWA,MAAa,iBAAiB,UAC5B,YAAY,OAAO,WAAW,YAAY;;AAG5C,MAAa,eAAe,WAC1B,OACG,SAAS,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC,CACpC,QAAQ,UAAU,MAAM,KAAK,MAAM,EAAE,CAAC,CACtC,KAAK,UAAU,YAAY,OAAO,QAAQ,UAAU,CAAC;;;;;AAM1D,MAAa,+BAA+B,OAC1C,aACA,UACA,YACkC;CAClC,MAAM,mBAAmB,YAAY,eAAe,WAAW;CAE/D,IAAI,CAAC,kBAAkB;EAGrB,OAFgB,YAAY,EAE3B,CAAC,IAAI,MACJ,gDAAgD,QAAQ,qCAAqC,UAAU,KAAK,IAAI,GAClH;EACA,QAAQ,WAAW;CACrB;CAEA,OAAO;AACT;;;;;;;;AASA,MAAa,aAAa,OACxB,aACA,UAA6B,CAAC,MACI;CAClC,MAAM,IAAI,MAAM,YAAY;CAE5B,MAAM,OAAO,mBAAmB,WAAW;CAC3C,MAAM,gBAAgB,sBAAsB;CAE5C,EAAE,MAAM,8BAA8B;CAEtC,MAAM,gBAAgB,iBAAiB,uBAAuB,IAAI,CAAC;CAEnE,MAAM,iBACJ,QAAQ,WACP,gBACG,MAAM,EAAE,YAAY;EAClB,SAAS;EACT,eAAe;EACf,SAAS,OAAO,KAAK,WAAW;GAC9B,OAAO;GACP,OAAO;GACP,MAAM,gBAAgB;EACxB,EAAE;EACF,UAAU;CACZ,CAAC,IACD;CAEN,IAAI,EAAE,SAAS,cAAc,KAAK,eAAe,WAAW,GAAG;EAC7D,EAAE,OAAO,0CAA0C;EACnD;CACF;CAEA,MAAM,WAAW,gBACZ,QAAQ,YAAa,MAAM,eAAe,IAAI,IAC/C,MAAM,6BACJ,MACA,QAAQ,UACR,sBACF;CAEJ,IAAI,CAAC,UAAU;EACb,EAAE,OAAO,4CAA4C;EACrD;CACF;CAEA,MAAM,IAAI,EAAE,QAAQ;CACpB,EAAE,MAAM,sBAAsB;CAE9B,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,MAAM,UAAU,cAAc;EAEjE,EAAE,KAAK,+BAA+B;EAEtC,EAAE,KAAK,QAAQ,SAAS;CAC1B,SAAS,OAAO;EACd,EAAE,KAAK,0BAA0B;EACjC,EAAE,IAAI,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;EAClE,QAAQ,WAAW;CACrB;CAEA,EAAE,MAAM,yCAAyC;CAEjD,OAAO;AACT"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { relative } from "node:path";
|
|
2
1
|
import { prepareIntlayer } from "@intlayer/engine/cli";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
3
|
import { colon, colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
|
|
4
4
|
import { getConfiguration } from "@intlayer/config/node";
|
|
5
5
|
import { formatPath } from "@intlayer/engine/utils";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"liveSync.mjs","names":[],"sources":["../../src/liveSync.ts"],"sourcesContent":["import { createServer } from 'node:http';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { getLocalizedContent } from '@intlayer/core/plugins';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport { buildDictionary } from '@intlayer/engine/build';\nimport { type ParallelHandle, runParallel } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { IntlayerEventListener } from './IntlayerEventListener';\n\ntype LiveSyncOptions = {\n with?: string | string[];\n configOptions?: GetConfigurationOptions;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let parallelProcess: ParallelHandle | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let _isHotReloadConnected = false;\n let _connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.with) {\n parallelProcess = runParallel(options.with);\n // Handle the promise to avoid unhandled rejection\n parallelProcess.result.catch(() => {\n // Parallel process failed or was terminated\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n _connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n _connectionStatus = 'connected';\n _isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n _connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n `Hot reload is ${colorize('disabled', ANSIColors.RED)}. Please enable it in the configuration (${colorize('editor.liveSync', ANSIColors.BLUE)}).`\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries(configuration);\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n if (!key) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const dictionary = dictionaries[key] ?? null;\n\n if (!dictionary) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n if (locale) {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const sourceLocaleContent = getLocalizedContent(\n dictionary.content,\n locale,\n {\n dictionaryKey: key,\n keyPath: [],\n }\n );\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries(configuration);\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.with ? `\\x1b[90m${Array.isArray(options.with) ? options.with.join(' ') : options.with}\\x1b[0m` : '-'}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n if (parallelProcess) {\n parallelProcess.kill();\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"liveSync.mjs","names":[],"sources":["../../src/liveSync.ts"],"sourcesContent":["import { createServer } from 'node:http';\nimport type { DictionaryAPI } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { getLocalizedContent } from '@intlayer/core/plugins';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport { buildDictionary } from '@intlayer/engine/build';\nimport { type ParallelHandle, runParallel } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { IntlayerEventListener } from './IntlayerEventListener';\n\ntype LiveSyncOptions = {\n with?: string | string[];\n configOptions?: GetConfigurationOptions;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let parallelProcess: ParallelHandle | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let _isHotReloadConnected = false;\n let _connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.with) {\n parallelProcess = runParallel(options.with);\n // Handle the promise to avoid unhandled rejection\n parallelProcess.result.catch(() => {\n // Parallel process failed or was terminated\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n _connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n _connectionStatus = 'connected';\n _isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n _connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n `Hot reload is ${colorize('disabled', ANSIColors.RED)}. Please enable it in the configuration (${colorize('editor.liveSync', ANSIColors.BLUE)}).`\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries(configuration);\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n if (!key) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const dictionary = dictionaries[key] ?? null;\n\n if (!dictionary) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n if (locale) {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const sourceLocaleContent = getLocalizedContent(\n dictionary.content,\n locale,\n {\n dictionaryKey: key,\n keyPath: [],\n }\n );\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries(configuration);\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.with ? `\\x1b[90m${Array.isArray(options.with) ? options.with.join(' ') : options.with}\\x1b[0m` : '-'}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n if (parallelProcess) {\n parallelProcess.kill();\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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"}
|
package/dist/esm/pull.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { checkCMSAuth, getAuthenticatedAPI } from "./utils/checkAccess.mjs";
|
|
2
2
|
import { PullLogger } from "./push/pullLog.mjs";
|
|
3
3
|
import { selectCmsEnvironment } from "./utils/selectCmsEnvironment.mjs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
4
|
import { logConfigDetails } from "@intlayer/engine/cli";
|
|
6
5
|
import { existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
7
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
8
8
|
import { getAppLogger } from "@intlayer/config/logger";
|
|
9
9
|
import { getConfiguration } from "@intlayer/config/node";
|
package/dist/esm/push/push.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import { loadPrompts } from "../loadPrompts.mjs";
|
|
|
2
2
|
import { checkCMSAuth, getAuthenticatedAPI } from "../utils/checkAccess.mjs";
|
|
3
3
|
import { selectCmsEnvironment } from "../utils/selectCmsEnvironment.mjs";
|
|
4
4
|
import { PushLogger } from "../pushLog.mjs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
5
|
import { listGitFiles, logConfigDetails } from "@intlayer/engine/cli";
|
|
6
|
+
import { join } from "node:path";
|
|
7
7
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
8
8
|
import { colorize, colorizeKey, getAppLogger } from "@intlayer/config/logger";
|
|
9
9
|
import { getConfiguration } from "@intlayer/config/node";
|
|
@@ -6,9 +6,9 @@ import { reviewFileBlockAware } from "./reviewDocBlockAware.mjs";
|
|
|
6
6
|
import { buildFileReviewReport, logReviewFileBlocks } from "./reviewDocLog.mjs";
|
|
7
7
|
import { createReviewProgressLogger } from "./reviewDocProgress.mjs";
|
|
8
8
|
import { formatReviewSynthesis } from "./reviewDocSynthesis.mjs";
|
|
9
|
-
import { join, relative } from "node:path";
|
|
10
9
|
import { listGitFiles, listGitLines, logConfigDetails } from "@intlayer/engine/cli";
|
|
11
10
|
import { existsSync } from "node:fs";
|
|
11
|
+
import { join, relative } from "node:path";
|
|
12
12
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
13
13
|
import { colorize, colorizeNumber, getAppLogger, x } from "@intlayer/config/logger";
|
|
14
14
|
import { getConfiguration } from "@intlayer/config/node";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewDoc.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeNumber,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n type ListGitFilesOptions,\n listGitFiles,\n listGitLines,\n logConfigDetails,\n} from '@intlayer/engine/cli';\nimport {\n buildReviewReport,\n type ReviewReportSummary,\n} from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath, parallelize } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport fg from 'fast-glob';\nimport { checkFileModifiedRange } from '../utils/checkFileModifiedRange';\nimport { formatLineRanges } from '../utils/formatLineRanges';\nimport { getOutputFilePath } from '../utils/getOutputFilePath';\nimport { type AIClient, setupAI } from '../utils/setupAI';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\nimport { buildFileReviewReport, logReviewFileBlocks } from './reviewDocLog';\nimport { createReviewProgressLogger } from './reviewDocProgress';\nimport {\n formatReviewSynthesis,\n type ReviewFileResult,\n type ReviewOutcome,\n} from './reviewDocSynthesis';\n\n/**\n * How the review should be run.\n *\n * - `apply`: translate the diverging blocks with AI and write the target files.\n * - `report`: no AI. Log every block that needs attention (line numbers and\n * content, for both locales) so another agent can generate the translations.\n * - `synthesis`: no AI, no per-block output. Only log the final synthesis of the\n * documents that are up to date and the ones that still have blocks to edit.\n */\nexport type ReviewDocMode = 'apply' | 'report' | 'synthesis';\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locale[];\n excludedGlobPattern: string[];\n baseLocale: Locale;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n skipIfExists?: boolean;\n gitOptions?: ListGitFilesOptions;\n /** How the review should be run. Defaults to `apply`. See {@link ReviewDocMode}. */\n mode?: ReviewDocMode;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n mode = 'apply',\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n // The `report` and `synthesis` modes never call an AI, so the AI access\n // checks are skipped.\n let aiClient: AIClient | undefined;\n let aiConfig: AIConfig | undefined;\n\n if (mode === 'apply') {\n const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return [];\n\n aiClient = aiResult.aiClient;\n aiConfig = aiResult.aiConfig;\n\n if (aiResult.isCustomAI && aiClient && aiConfig) {\n const { hasAIAccess, error } = await aiClient.checkAISDKAccess(aiConfig);\n if (!hasAIAccess) {\n appLogger(`${x} ${error}`);\n return [];\n }\n }\n }\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n\n if (gitChangedFiles) {\n // Convert dictionary file paths to be relative to git root for comparison\n\n // Filter dictionaries based on git changed files\n docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n // OAuth handled by API proxy internally\n\n appLogger(`Base locale is ${formatLocale(baseLocale)}`);\n appLogger(\n `Reviewing ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Reviewing ${colorizeNumber(docList.length)} files:`);\n appLogger(docList.map((path) => ` - ${formatPath(path)}\\n`));\n\n // In `synthesis` mode only the final recap is printed, so the per-file\n // progress logs are muted.\n const logProgress: typeof appLogger = (...content) => {\n if (mode === 'synthesis') return;\n appLogger(...content);\n };\n\n // Filled by every task, then rendered as the end-of-run synthesis.\n const reviewResults: ReviewFileResult[] = [];\n\n /** A document is up to date when no block has to be added, reviewed or removed. */\n const getOutcome = (summary: ReviewReportSummary): ReviewOutcome =>\n summary.review + summary.insertNew + summary.delete > 0\n ? 'toEdit'\n : 'upToDate';\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n logProgress(\n `Reviewing file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.system.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip if file exists and skipIfExists option is enabled\n if (skipIfExists && existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.system.baseDir,\n outputFilePath\n );\n logProgress(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\n return;\n }\n\n // Check modification range only if the file exists\n if (existsSync(outputFilePath)) {\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n logProgress(fileModificationData.message);\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\n return;\n }\n } else if (skipIfModifiedBefore || skipIfModifiedAfter) {\n // Log if we intended to check modification time but couldn't because the file doesn't exist\n logProgress(\n `${colorize('!', ANSIColors.YELLOW)} File ${formatPath(outputFilePath)} does not exist, skipping modification date check.`\n );\n }\n\n let changedLines: number[] | undefined;\n // FIXED: Enable git optimization that was previously commented out\n if (gitOptions) {\n const gitChangedLines = await listGitLines(\n absoluteBaseFilePath,\n gitOptions\n );\n changedLines = gitChangedLines;\n\n // Report the base lines that changed, then the corresponding line span\n // in the target document — i.e. the blocks the alignment will actually\n // re-translate. `review` blocks are exactly the aligned base blocks the\n // changed lines touched, so their `targetLineRange` is the matching span\n // in the existing translation.\n logProgress(\n `Changed lines (${formatLocale(baseLocale)}): ${formatLineRanges(gitChangedLines)}`\n );\n\n const baseText = await readFile(absoluteBaseFilePath, 'utf-8').catch(\n () => ''\n );\n const targetText = existsSync(outputFilePath)\n ? await readFile(outputFilePath, 'utf-8').catch(() => '')\n : '';\n\n const { blocks } = buildReviewReport({\n baseText,\n targetText,\n changedLines: gitChangedLines,\n });\n\n const correspondingTargetLines = blocks.flatMap((block) => {\n if (block.action !== 'review' || !block.targetLineRange) return [];\n\n const { start, end } = block.targetLineRange;\n return Array.from(\n { length: end - start + 1 },\n (_unused, offset) => start + offset\n );\n });\n\n logProgress(\n `Corresponding block (${formatLocale(locale)}): ${formatLineRanges(correspondingTargetLines)}`\n );\n }\n\n if (mode !== 'apply') {\n const report = await buildFileReviewReport(\n absoluteBaseFilePath,\n outputFilePath,\n changedLines\n );\n\n if (mode === 'report') {\n logReviewFileBlocks(\n report,\n absoluteBaseFilePath,\n locale as Locale,\n baseLocale,\n configOptions\n );\n }\n\n reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(report.summary),\n summary: report.summary,\n });\n return;\n }\n\n const reviewSummary = await reviewFileBlockAware(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n aiOptions,\n configOptions,\n customInstructions,\n changedLines,\n aiClient,\n aiConfig\n );\n\n reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(reviewSummary),\n summary: reviewSummary,\n });\n })\n );\n\n // The per-file logs are muted in `synthesis` mode, so a progress line keeps\n // the run readable while every document is compared.\n const progressLogger = createReviewProgressLogger(allTasks.length);\n\n if (mode === 'synthesis') progressLogger.start();\n\n await parallelize(\n allTasks,\n async (task) => {\n await task();\n progressLogger.increment();\n },\n nbSimultaneousFileProcessed ?? 3\n );\n\n progressLogger.stop();\n\n const synthesis = formatReviewSynthesis(reviewResults, {\n hasAppliedChanges: mode === 'apply',\n });\n\n for (const line of synthesis.split('\\n')) {\n appLogger(line);\n }\n\n return reviewResults;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reviewDoc.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { AIConfig, AIOptions } from '@intlayer/ai';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeNumber,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n type ListGitFilesOptions,\n listGitFiles,\n listGitLines,\n logConfigDetails,\n} from '@intlayer/engine/cli';\nimport {\n buildReviewReport,\n type ReviewReportSummary,\n} from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath, parallelize } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport fg from 'fast-glob';\nimport { checkFileModifiedRange } from '../utils/checkFileModifiedRange';\nimport { formatLineRanges } from '../utils/formatLineRanges';\nimport { getOutputFilePath } from '../utils/getOutputFilePath';\nimport { type AIClient, setupAI } from '../utils/setupAI';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\nimport { buildFileReviewReport, logReviewFileBlocks } from './reviewDocLog';\nimport { createReviewProgressLogger } from './reviewDocProgress';\nimport {\n formatReviewSynthesis,\n type ReviewFileResult,\n type ReviewOutcome,\n} from './reviewDocSynthesis';\n\n/**\n * How the review should be run.\n *\n * - `apply`: translate the diverging blocks with AI and write the target files.\n * - `report`: no AI. Log every block that needs attention (line numbers and\n * content, for both locales) so another agent can generate the translations.\n * - `synthesis`: no AI, no per-block output. Only log the final synthesis of the\n * documents that are up to date and the ones that still have blocks to edit.\n */\nexport type ReviewDocMode = 'apply' | 'report' | 'synthesis';\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locale[];\n excludedGlobPattern: string[];\n baseLocale: Locale;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n skipIfExists?: boolean;\n gitOptions?: ListGitFilesOptions;\n /** How the review should be run. Defaults to `apply`. See {@link ReviewDocMode}. */\n mode?: ReviewDocMode;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n mode = 'apply',\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n // The `report` and `synthesis` modes never call an AI, so the AI access\n // checks are skipped.\n let aiClient: AIClient | undefined;\n let aiConfig: AIConfig | undefined;\n\n if (mode === 'apply') {\n const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return [];\n\n aiClient = aiResult.aiClient;\n aiConfig = aiResult.aiConfig;\n\n if (aiResult.isCustomAI && aiClient && aiConfig) {\n const { hasAIAccess, error } = await aiClient.checkAISDKAccess(aiConfig);\n if (!hasAIAccess) {\n appLogger(`${x} ${error}`);\n return [];\n }\n }\n }\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n\n if (gitChangedFiles) {\n // Convert dictionary file paths to be relative to git root for comparison\n\n // Filter dictionaries based on git changed files\n docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n // OAuth handled by API proxy internally\n\n appLogger(`Base locale is ${formatLocale(baseLocale)}`);\n appLogger(\n `Reviewing ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Reviewing ${colorizeNumber(docList.length)} files:`);\n appLogger(docList.map((path) => ` - ${formatPath(path)}\\n`));\n\n // In `synthesis` mode only the final recap is printed, so the per-file\n // progress logs are muted.\n const logProgress: typeof appLogger = (...content) => {\n if (mode === 'synthesis') return;\n appLogger(...content);\n };\n\n // Filled by every task, then rendered as the end-of-run synthesis.\n const reviewResults: ReviewFileResult[] = [];\n\n /** A document is up to date when no block has to be added, reviewed or removed. */\n const getOutcome = (summary: ReviewReportSummary): ReviewOutcome =>\n summary.review + summary.insertNew + summary.delete > 0\n ? 'toEdit'\n : 'upToDate';\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n logProgress(\n `Reviewing file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.system.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip if file exists and skipIfExists option is enabled\n if (skipIfExists && existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.system.baseDir,\n outputFilePath\n );\n logProgress(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\n return;\n }\n\n // Check modification range only if the file exists\n if (existsSync(outputFilePath)) {\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n logProgress(fileModificationData.message);\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\n return;\n }\n } else if (skipIfModifiedBefore || skipIfModifiedAfter) {\n // Log if we intended to check modification time but couldn't because the file doesn't exist\n logProgress(\n `${colorize('!', ANSIColors.YELLOW)} File ${formatPath(outputFilePath)} does not exist, skipping modification date check.`\n );\n }\n\n let changedLines: number[] | undefined;\n // FIXED: Enable git optimization that was previously commented out\n if (gitOptions) {\n const gitChangedLines = await listGitLines(\n absoluteBaseFilePath,\n gitOptions\n );\n changedLines = gitChangedLines;\n\n // Report the base lines that changed, then the corresponding line span\n // in the target document — i.e. the blocks the alignment will actually\n // re-translate. `review` blocks are exactly the aligned base blocks the\n // changed lines touched, so their `targetLineRange` is the matching span\n // in the existing translation.\n logProgress(\n `Changed lines (${formatLocale(baseLocale)}): ${formatLineRanges(gitChangedLines)}`\n );\n\n const baseText = await readFile(absoluteBaseFilePath, 'utf-8').catch(\n () => ''\n );\n const targetText = existsSync(outputFilePath)\n ? await readFile(outputFilePath, 'utf-8').catch(() => '')\n : '';\n\n const { blocks } = buildReviewReport({\n baseText,\n targetText,\n changedLines: gitChangedLines,\n });\n\n const correspondingTargetLines = blocks.flatMap((block) => {\n if (block.action !== 'review' || !block.targetLineRange) return [];\n\n const { start, end } = block.targetLineRange;\n return Array.from(\n { length: end - start + 1 },\n (_unused, offset) => start + offset\n );\n });\n\n logProgress(\n `Corresponding block (${formatLocale(locale)}): ${formatLineRanges(correspondingTargetLines)}`\n );\n }\n\n if (mode !== 'apply') {\n const report = await buildFileReviewReport(\n absoluteBaseFilePath,\n outputFilePath,\n changedLines\n );\n\n if (mode === 'report') {\n logReviewFileBlocks(\n report,\n absoluteBaseFilePath,\n locale as Locale,\n baseLocale,\n configOptions\n );\n }\n\n reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(report.summary),\n summary: report.summary,\n });\n return;\n }\n\n const reviewSummary = await reviewFileBlockAware(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n aiOptions,\n configOptions,\n customInstructions,\n changedLines,\n aiClient,\n aiConfig\n );\n\n reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(reviewSummary),\n summary: reviewSummary,\n });\n })\n );\n\n // The per-file logs are muted in `synthesis` mode, so a progress line keeps\n // the run readable while every document is compared.\n const progressLogger = createReviewProgressLogger(allTasks.length);\n\n if (mode === 'synthesis') progressLogger.start();\n\n await parallelize(\n allTasks,\n async (task) => {\n await task();\n progressLogger.increment();\n },\n nbSimultaneousFileProcessed ?? 3\n );\n\n progressLogger.stop();\n\n const synthesis = formatReviewSynthesis(reviewResults, {\n hasAppliedChanges: mode === 'apply',\n });\n\n for (const line of synthesis.split('\\n')) {\n appLogger(line);\n }\n\n return reviewResults;\n};\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { readAsset } from "../_virtual/_utils_asset.mjs";
|
|
|
2
2
|
import { sanitizeChunk, validateTranslation } from "../translateDoc/validation.mjs";
|
|
3
3
|
import { chunkInference } from "../utils/chunkInference.mjs";
|
|
4
4
|
import { fixChunkStartEndChars } from "../utils/fixChunkStartEndChars.mjs";
|
|
5
|
-
import { dirname } from "node:path";
|
|
6
5
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
7
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
8
8
|
import { colon, colorize, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
|
|
9
9
|
import { getConfiguration } from "@intlayer/config/node";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewDocBlockAware.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDocBlockAware.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colon,\n colorize,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { retryManager } from '@intlayer/config/utils';\nimport { getLocaleName } from '@intlayer/core/localization';\nimport {\n buildAlignmentPlan,\n mergeReviewedSegments,\n type PlannedAction,\n type ReviewReportSummary,\n} from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport { sanitizeChunk, validateTranslation } from '../translateDoc/validation';\nimport { chunkInference } from '../utils/chunkInference';\nimport { fixChunkStartEndChars } from '../utils/fixChunkStartEndChars';\nimport type { AIClient } from '../utils/setupAI';\n\n/**\n * Review a file using block-aware alignment.\n *\n * 1. Segments both base and target documents into semantic blocks.\n * 2. Aligns blocks using structure (special chars, numbers) and context.\n * 3. Detects which blocks changed, were added, or deleted.\n * 4. Applies deletions immediately without AI.\n * 5. Sends changed/new blocks to AI in bottom-up order (last block first), so\n * line numbers of earlier blocks are not shifted by edits below them.\n * 6. Rewrites the file after each block so progress is persisted incrementally.\n *\n * @returns The per-action block counts of the alignment plan, so the caller can\n * synthesize what was reused and what had to be edited.\n */\nexport const reviewFileBlockAware = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n customInstructions?: string,\n changedLines?: number[],\n aiClient?: AIClient,\n aiConfig?: AIConfig\n): Promise<ReviewReportSummary> => {\n const configuration = getConfiguration(configOptions);\n const applicationLogger = getAppLogger({\n log: { ...configuration.log, prefix: '' },\n });\n\n const baseText = await readFile(baseFilePath, 'utf-8');\n const targetText = await readFile(outputFilePath, 'utf-8').catch(() => '');\n\n const basePrompt = readAsset('./prompts/REVIEW_PROMPT.md', 'utf-8')\n .replaceAll('{{localeName}}', `${formatLocale(locale, false)}`)\n .replaceAll('{{baseLocaleName}}', `${formatLocale(baseLocale, false)}`)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const filePrefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}] `;\n const filePrefix = [\n colon(filePrefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n const prefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}][${formatLocale(locale)}${ANSIColors.GREY_DARK}] `;\n const prefix = [\n colon(prefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n\n // Build block-aware alignment and plan\n const { baseBlocks, targetBlocks, plan, segmentsToReview } =\n buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const countActions = (kind: PlannedAction['kind']): number =>\n plan.actions.filter((action: PlannedAction) => action.kind === kind).length;\n\n const reviewSummary: ReviewReportSummary = {\n reuse: countActions('reuse'),\n review: countActions('review'),\n insertNew: countActions('insert_new'),\n delete: countActions('delete'),\n };\n\n const deleteCount = reviewSummary.delete;\n\n applicationLogger(\n `${filePrefix}Block-aware alignment complete. Total blocks: base=${colorizeNumber(baseBlocks.length)}, target=${colorizeNumber(targetBlocks.length)}`\n );\n applicationLogger(\n `${filePrefix}Actions: reuse=${colorizeNumber(reviewSummary.reuse)}, review=${colorizeNumber(reviewSummary.review)}, new=${colorizeNumber(reviewSummary.insertNew)}, delete=${colorizeNumber(deleteCount)}`\n );\n\n // Map shared across the entire run: each entry overrides the default behavior\n // of mergeReviewedSegments for that action index.\n const reviewedSegmentsMap = new Map<number, string>();\n\n // --- Step 1: apply deletions immediately (no AI needed) ---\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'delete') {\n reviewedSegmentsMap.set(actionIndex, '');\n }\n }\n\n const writeCurrentState = (): void => {\n const output = mergeReviewedSegments(\n plan,\n targetBlocks,\n reviewedSegmentsMap\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, output);\n };\n\n if (deleteCount > 0) {\n writeCurrentState();\n applicationLogger(\n `${filePrefix}${colorizeNumber(deleteCount)} block(s) deleted without AI.`\n );\n }\n\n if (segmentsToReview.length === 0) {\n if (deleteCount === 0) {\n applicationLogger(\n `${filePrefix}No segments need review, reusing existing translation`\n );\n writeCurrentState();\n }\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} updated successfully (no changes needed).`\n );\n return reviewSummary;\n }\n\n applicationLogger(\n `${filePrefix}Segments to review: ${colorizeNumber(segmentsToReview.length)} (processing bottom-up)`\n );\n\n // --- Step 2: process AI segments in bottom-up order ---\n // Reversing ensures edits near the end of the file don't shift line numbers\n // that matter for blocks higher up, and each intermediate file write is valid.\n const segmentsBottomUp = segmentsToReview\n .map((segment, originalIndex) => ({\n segment,\n displayNumber: originalIndex + 1,\n }))\n .reverse();\n\n for (const { segment, displayNumber } of segmentsBottomUp) {\n const baseBlock = segment.baseBlock;\n\n const getBaseChunkContextPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the base block in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///\\n` +\n baseBlock.content +\n `///chunksEnd///`;\n\n const getTargetChunkPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the current block to review in ${formatLocale(locale, false)}.\\n` +\n `///chunksStart///\\n` +\n (segment.targetBlockText ?? '') +\n `///chunksEnd///`;\n\n const reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getTargetChunkPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **BLOCK ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}** that should be translated in ${getLocaleName(locale, ENGLISH)} (${locale}).`,\n },\n ],\n [{ role: 'user', content: baseBlock.content }],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n applicationLogger(\n `${prefix}${colorizeNumber(result.tokenUsed)} tokens used - Block ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}`\n );\n\n let processedChunk = sanitizeChunk(\n result?.fileContent,\n baseBlock.content\n );\n processedChunk = fixChunkStartEndChars(processedChunk, baseBlock.content);\n\n const isValid = validateTranslation(\n baseBlock.content,\n processedChunk,\n applicationLogger\n );\n\n if (!isValid) {\n throw new Error(\n 'Validation failed for chunk (structure or length mismatch). Retrying...'\n );\n }\n\n return processedChunk;\n })();\n\n reviewedSegmentsMap.set(segment.actionIndex, reviewedChunkResult);\n\n // Rewrite the file after every block so progress is never lost.\n writeCurrentState();\n }\n\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} created/updated successfully.`\n );\n\n return reviewSummary;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reviewDocBlockAware.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDocBlockAware.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig, AIOptions } from '@intlayer/ai';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colon,\n colorize,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { retryManager } from '@intlayer/config/utils';\nimport { getLocaleName } from '@intlayer/core/localization';\nimport {\n buildAlignmentPlan,\n mergeReviewedSegments,\n type PlannedAction,\n type ReviewReportSummary,\n} from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport { sanitizeChunk, validateTranslation } from '../translateDoc/validation';\nimport { chunkInference } from '../utils/chunkInference';\nimport { fixChunkStartEndChars } from '../utils/fixChunkStartEndChars';\nimport type { AIClient } from '../utils/setupAI';\n\n/**\n * Review a file using block-aware alignment.\n *\n * 1. Segments both base and target documents into semantic blocks.\n * 2. Aligns blocks using structure (special chars, numbers) and context.\n * 3. Detects which blocks changed, were added, or deleted.\n * 4. Applies deletions immediately without AI.\n * 5. Sends changed/new blocks to AI in bottom-up order (last block first), so\n * line numbers of earlier blocks are not shifted by edits below them.\n * 6. Rewrites the file after each block so progress is persisted incrementally.\n *\n * @returns The per-action block counts of the alignment plan, so the caller can\n * synthesize what was reused and what had to be edited.\n */\nexport const reviewFileBlockAware = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n customInstructions?: string,\n changedLines?: number[],\n aiClient?: AIClient,\n aiConfig?: AIConfig\n): Promise<ReviewReportSummary> => {\n const configuration = getConfiguration(configOptions);\n const applicationLogger = getAppLogger({\n log: { ...configuration.log, prefix: '' },\n });\n\n const baseText = await readFile(baseFilePath, 'utf-8');\n const targetText = await readFile(outputFilePath, 'utf-8').catch(() => '');\n\n const basePrompt = readAsset('./prompts/REVIEW_PROMPT.md', 'utf-8')\n .replaceAll('{{localeName}}', `${formatLocale(locale, false)}`)\n .replaceAll('{{baseLocaleName}}', `${formatLocale(baseLocale, false)}`)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const filePrefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}] `;\n const filePrefix = [\n colon(filePrefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n const prefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}][${formatLocale(locale)}${ANSIColors.GREY_DARK}] `;\n const prefix = [\n colon(prefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n\n // Build block-aware alignment and plan\n const { baseBlocks, targetBlocks, plan, segmentsToReview } =\n buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const countActions = (kind: PlannedAction['kind']): number =>\n plan.actions.filter((action: PlannedAction) => action.kind === kind).length;\n\n const reviewSummary: ReviewReportSummary = {\n reuse: countActions('reuse'),\n review: countActions('review'),\n insertNew: countActions('insert_new'),\n delete: countActions('delete'),\n };\n\n const deleteCount = reviewSummary.delete;\n\n applicationLogger(\n `${filePrefix}Block-aware alignment complete. Total blocks: base=${colorizeNumber(baseBlocks.length)}, target=${colorizeNumber(targetBlocks.length)}`\n );\n applicationLogger(\n `${filePrefix}Actions: reuse=${colorizeNumber(reviewSummary.reuse)}, review=${colorizeNumber(reviewSummary.review)}, new=${colorizeNumber(reviewSummary.insertNew)}, delete=${colorizeNumber(deleteCount)}`\n );\n\n // Map shared across the entire run: each entry overrides the default behavior\n // of mergeReviewedSegments for that action index.\n const reviewedSegmentsMap = new Map<number, string>();\n\n // --- Step 1: apply deletions immediately (no AI needed) ---\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'delete') {\n reviewedSegmentsMap.set(actionIndex, '');\n }\n }\n\n const writeCurrentState = (): void => {\n const output = mergeReviewedSegments(\n plan,\n targetBlocks,\n reviewedSegmentsMap\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, output);\n };\n\n if (deleteCount > 0) {\n writeCurrentState();\n applicationLogger(\n `${filePrefix}${colorizeNumber(deleteCount)} block(s) deleted without AI.`\n );\n }\n\n if (segmentsToReview.length === 0) {\n if (deleteCount === 0) {\n applicationLogger(\n `${filePrefix}No segments need review, reusing existing translation`\n );\n writeCurrentState();\n }\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} updated successfully (no changes needed).`\n );\n return reviewSummary;\n }\n\n applicationLogger(\n `${filePrefix}Segments to review: ${colorizeNumber(segmentsToReview.length)} (processing bottom-up)`\n );\n\n // --- Step 2: process AI segments in bottom-up order ---\n // Reversing ensures edits near the end of the file don't shift line numbers\n // that matter for blocks higher up, and each intermediate file write is valid.\n const segmentsBottomUp = segmentsToReview\n .map((segment, originalIndex) => ({\n segment,\n displayNumber: originalIndex + 1,\n }))\n .reverse();\n\n for (const { segment, displayNumber } of segmentsBottomUp) {\n const baseBlock = segment.baseBlock;\n\n const getBaseChunkContextPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the base block in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///\\n` +\n baseBlock.content +\n `///chunksEnd///`;\n\n const getTargetChunkPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the current block to review in ${formatLocale(locale, false)}.\\n` +\n `///chunksStart///\\n` +\n (segment.targetBlockText ?? '') +\n `///chunksEnd///`;\n\n const reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getTargetChunkPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **BLOCK ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}** that should be translated in ${getLocaleName(locale, ENGLISH)} (${locale}).`,\n },\n ],\n [{ role: 'user', content: baseBlock.content }],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n applicationLogger(\n `${prefix}${colorizeNumber(result.tokenUsed)} tokens used - Block ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}`\n );\n\n let processedChunk = sanitizeChunk(\n result?.fileContent,\n baseBlock.content\n );\n processedChunk = fixChunkStartEndChars(processedChunk, baseBlock.content);\n\n const isValid = validateTranslation(\n baseBlock.content,\n processedChunk,\n applicationLogger\n );\n\n if (!isValid) {\n throw new Error(\n 'Validation failed for chunk (structure or length mismatch). Retrying...'\n );\n }\n\n return processedChunk;\n })();\n\n reviewedSegmentsMap.set(segment.actionIndex, reviewedChunkResult);\n\n // Rewrite the file after every block so progress is never lost.\n writeCurrentState();\n }\n\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} created/updated successfully.`\n );\n\n return reviewSummary;\n};\n"],"mappings":"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"}
|
|
@@ -2,9 +2,9 @@ import { setupAI } from "../utils/setupAI.mjs";
|
|
|
2
2
|
import { getOutputFilePath } from "../utils/getOutputFilePath.mjs";
|
|
3
3
|
import { checkFileModifiedRange } from "../utils/checkFileModifiedRange.mjs";
|
|
4
4
|
import { translateFile } from "./translateFile.mjs";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
5
|
import { listGitFiles, logConfigDetails } from "@intlayer/engine/cli";
|
|
7
6
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
8
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
9
9
|
import { colorize, colorizeNumber, getAppLogger, x } from "@intlayer/config/logger";
|
|
10
10
|
import { getConfiguration } from "@intlayer/config/node";
|
package/dist/esm/upgrade.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkAccess.mjs","names":[],"sources":["../../../src/utils/checkAccess.ts"],"sourcesContent":["import type { AIOptions
|
|
1
|
+
{"version":3,"file":"checkAccess.mjs","names":[],"sources":["../../../src/utils/checkAccess.ts"],"sourcesContent":["import type { AIOptions } from '@intlayer/ai';\nimport type { IntlayerAPIProxy } from '@intlayer/api';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { extractErrorMessage } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { login } from '../auth/login';\nimport { type CliSessionData, readCliSessionToken } from '../auth/sessionToken';\nimport { checkConfigConsistency } from './checkConfigConsistency';\n\nexport const getAuthenticatedAPI = async (\n configuration?: IntlayerConfig\n): Promise<IntlayerAPIProxy> => {\n let sessionData: CliSessionData | null = null;\n\n // First use session data if it exists\n if (configuration) {\n sessionData = await readCliSessionToken(configuration);\n }\n\n return getIntlayerAPIProxy(undefined, configuration, sessionData?.token);\n};\n\nconst checkProjectConfigConsistency = (\n project: { configuration?: any } | null | undefined,\n configuration: IntlayerConfig,\n appLogger: ReturnType<typeof getAppLogger>\n): void => {\n if (!project?.configuration) return;\n\n try {\n let remoteConfigToCheck = project.configuration;\n\n if (\n remoteConfigToCheck.ai &&\n 'apiKeyConfigured' in remoteConfigToCheck.ai\n ) {\n const { apiKeyConfigured, ...restAi } = remoteConfigToCheck.ai as any;\n remoteConfigToCheck = { ...remoteConfigToCheck, ai: restAi };\n }\n\n checkConfigConsistency(remoteConfigToCheck, configuration);\n } catch {\n appLogger(\n [\n 'Remote configuration is not up to date. The project configuration does not match the local configuration.',\n 'You can push the configuration by running',\n colorize('npx intlayer configuration push', ANSIColors.CYAN),\n colorize('(see doc:', ANSIColors.GREY_LIGHT),\n colorize(\n 'https://intlayer.org/doc/concept/cli/push',\n ANSIColors.GREY_DARK\n ),\n colorize(')', ANSIColors.GREY_LIGHT),\n '.',\n ],\n { level: 'warn' }\n );\n }\n};\n\nexport const checkCMSAuth = async (\n configuration: IntlayerConfig,\n shouldCheckConfigConsistency: boolean = true,\n shouldAutoLogin: boolean = true\n): Promise<boolean> => {\n const appLogger = getAppLogger(configuration);\n\n // Try CLI session token first (short-lived 2h token stored in tempDir)\n const sessionData = await readCliSessionToken(configuration);\n if (sessionData) {\n const intlayerAPI = getIntlayerAPIProxy(\n undefined,\n configuration,\n sessionData.token\n );\n\n try {\n const result = await intlayerAPI.oAuth.getCliSessionMe();\n const project = result.data?.project;\n\n if (project && shouldCheckConfigConsistency) {\n checkProjectConfigConsistency(project, configuration, appLogger);\n }\n\n return true;\n } catch (error) {\n const message = extractErrorMessage(error);\n appLogger(message, { level: 'error' });\n appLogger(\n [\n 'Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to set up a new session.',\n ],\n { level: 'error' }\n );\n return false;\n }\n }\n\n // Fall back to client_credentials (access key)\n const hasCMSAuth =\n configuration.editor.clientId && configuration.editor.clientSecret;\n if (!hasCMSAuth) {\n if (shouldAutoLogin) {\n appLogger(\n [\n 'No credentials found. Starting login...',\n colorize('( Set', ANSIColors.GREY_LIGHT),\n colorize('INTLAYER_CLIENT_ID', ANSIColors.BLUE),\n colorize('and', ANSIColors.GREY_LIGHT),\n colorize('INTLAYER_CLIENT_SECRET', ANSIColors.BLUE),\n colorize('in your .env to skip this step)', ANSIColors.GREY_LIGHT),\n ],\n { level: 'warn' }\n );\n await login({ exitAfter: false });\n return checkCMSAuth(configuration, shouldCheckConfigConsistency, false);\n }\n\n appLogger(\n [\n 'CMS auth not provided. Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to authenticate, or set',\n colorize('INTLAYER_CLIENT_ID', ANSIColors.BLUE),\n 'and',\n colorize('INTLAYER_CLIENT_SECRET', ANSIColors.BLUE),\n 'in your .env file',\n colorize('(see doc:', ANSIColors.GREY_LIGHT),\n colorize('https://intlayer.org/doc/concept/cms', ANSIColors.GREY_DARK),\n colorize(')', ANSIColors.GREY_LIGHT),\n '.',\n ],\n { level: 'error' }\n );\n\n return false;\n }\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n try {\n const result = await intlayerAPI.oAuth.getOAuth2AccessToken();\n\n const project = result.data?.project;\n\n if (!project) {\n appLogger('Project not found');\n return true;\n }\n\n if (shouldCheckConfigConsistency) {\n checkProjectConfigConsistency(project, configuration, appLogger);\n }\n } catch (error) {\n const message = extractErrorMessage(error);\n appLogger(message, { level: 'error' });\n appLogger(\n [\n 'Your access keys appear to be invalid. Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to set up new access keys.',\n ],\n { level: 'error' }\n );\n return false;\n }\n\n return true;\n};\n\nexport const checkAIAccess = async (\n configuration: IntlayerConfig,\n aiOptions?: AIOptions,\n shouldCheckConfigConsistency: boolean = true\n): Promise<boolean> => {\n const isOllama =\n configuration.ai?.provider === 'ollama' || aiOptions?.provider === 'ollama';\n const hasHisOwnAIAPIKey = Boolean(\n configuration.ai?.apiKey || aiOptions?.apiKey\n );\n\n if (hasHisOwnAIAPIKey || isOllama) {\n return true;\n }\n\n // No local AI key — delegate to CMS auth check (which will auto-login if needed)\n return await checkCMSAuth(configuration, shouldCheckConfigConsistency);\n};\n"],"mappings":"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"}
|