@aigne/doc-smith 0.9.10 → 0.9.11-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +189 -219
- package/README.zh.md +270 -0
- package/agents/bash-executor/index.mjs +347 -0
- package/agents/clear/ai/intent.md +142 -0
- package/agents/clear/choose-contents.mjs +13 -65
- package/agents/clear/clear-auth-tokens.mjs +17 -21
- package/agents/clear/clear-deployment-config.mjs +33 -24
- package/agents/clear/index.yaml +1 -9
- package/agents/content-checker/ai/intent.md +209 -0
- package/agents/content-checker/clean-invalid-docs.mjs +254 -0
- package/agents/content-checker/index.mjs +191 -0
- package/agents/content-checker/validate-content.mjs +983 -0
- package/agents/generate-images/generate-image.yaml +75 -0
- package/agents/generate-images/generate-summary.mjs +213 -0
- package/agents/generate-images/index.yaml +39 -0
- package/agents/generate-images/prepare-generation.mjs +286 -0
- package/agents/generate-images/prepare-image-generation.mjs +130 -0
- package/{prompts/detail/diagram/generate-image-system.md → agents/generate-images/prompts/system.md} +22 -56
- package/agents/generate-images/prompts/user.md +85 -0
- package/agents/generate-images/save-image-result.mjs +247 -0
- package/agents/generate-images/scan-image-slots.mjs +247 -0
- package/agents/localize/index.yaml +19 -42
- package/{prompts/translate → agents/localize/prompts}/translate-document.md +0 -139
- package/agents/localize/translate-documents/generate-summary.mjs +163 -0
- package/agents/localize/translate-documents/load-glossary.mjs +52 -0
- package/agents/localize/translate-documents/prepare-translation.mjs +249 -0
- package/agents/localize/translate-documents/save-translation.mjs +171 -0
- package/agents/localize/translate-documents/translate-document-to-language.mjs +209 -0
- package/agents/localize/translate-documents/translate-document.yaml +23 -0
- package/agents/localize/translate-documents/translate-to-languages.yaml +10 -0
- package/agents/localize/translate-images/check-image-translation.mjs +225 -0
- package/agents/localize/translate-images/detect-text/detect-and-update-shared.mjs +148 -0
- package/agents/localize/translate-images/detect-text/detect-image-text.yaml +44 -0
- package/agents/localize/translate-images/detect-text/detect-images-text.yaml +21 -0
- package/agents/localize/translate-images/detect-text/prompts/detect-image-text-system.md +43 -0
- package/agents/localize/translate-images/detect-text/prompts/detect-image-text-user.md +14 -0
- package/agents/localize/translate-images/detect-text/save-text-detection.mjs +105 -0
- package/agents/localize/translate-images/prepare-image-input.mjs +124 -0
- package/agents/localize/translate-images/save-image-translation.mjs +172 -0
- package/agents/localize/translate-images/scan-doc-images.mjs +165 -0
- package/agents/localize/translate-images/translate-doc-images.yaml +24 -0
- package/agents/localize/{translate-diagram.yaml → translate-images/translate-image.yaml} +25 -14
- package/agents/publish/ai/intent.md +182 -0
- package/agents/publish/check.mjs +107 -0
- package/agents/publish/index.yaml +9 -14
- package/agents/publish/publish-docs.mjs +81 -61
- package/agents/publish/translate-meta.mjs +79 -58
- package/agents/save-document/index.mjs +260 -0
- package/agents/structure-checker/index.mjs +307 -0
- package/agents/structure-checker/validate-structure.mjs +477 -0
- package/agents/update-image/analyze-feedback.yaml +37 -0
- package/agents/update-image/index.yaml +78 -0
- package/agents/update-image/load-existing-image.mjs +211 -0
- package/agents/update-image/prompts/analyze-feedback-system.md +43 -0
- package/agents/update-image/prompts/analyze-feedback-user.md +15 -0
- package/aigne.yaml +26 -139
- package/package.json +16 -48
- package/scripts/README.md +90 -0
- package/scripts/install.sh +86 -0
- package/scripts/uninstall.sh +52 -0
- package/skills/doc-smith/SKILL.md +285 -0
- package/skills/doc-smith/ai/intent/sources-improve.md +290 -0
- package/skills/doc-smith/references/changeset-guide.md +171 -0
- package/skills/doc-smith/references/document-content-guide.md +214 -0
- package/skills/doc-smith/references/document-structure-schema.md +138 -0
- package/skills/doc-smith/references/patch-guide.md +96 -0
- package/skills/doc-smith/references/structure-confirmation-guide.md +133 -0
- package/skills/doc-smith/references/structure-planning-guide.md +149 -0
- package/skills/doc-smith/references/update-workflow.md +108 -0
- package/skills/doc-smith/references/user-intent-guide.md +175 -0
- package/skills/doc-smith/references/workspace-initialization.md +376 -0
- package/skills/doc-smith-docs-detail/SKILL.md +356 -0
- package/skills/doc-smith-docs-detail/ai/intent.md +271 -0
- package/skills-entry/doc-smith/ai/intent.md +260 -0
- package/skills-entry/doc-smith/index.mjs +66 -0
- package/skills-entry/doc-smith/prompt.md +57 -0
- package/skills-entry/doc-smith/utils.mjs +27 -0
- package/skills-entry/doc-smith-docs-detail/batch.yaml +56 -0
- package/skills-entry/doc-smith-docs-detail/index.mjs +95 -0
- package/skills-entry/doc-smith-docs-detail/prompt.md +64 -0
- package/utils/afs-factory.mjs +183 -0
- package/utils/agent-constants.mjs +97 -0
- package/utils/{auth-utils.mjs → auth.mjs} +6 -9
- package/{agents/utils/update-branding.mjs → utils/branding.mjs} +3 -4
- package/utils/config.mjs +261 -0
- package/utils/constants.mjs +32 -0
- package/utils/deploy.mjs +3 -3
- package/utils/docs-converter.mjs +454 -0
- package/utils/docs.mjs +212 -0
- package/utils/document-paths.mjs +172 -0
- package/utils/files.mjs +74 -0
- package/utils/git.mjs +65 -0
- package/utils/{blocklet.mjs → http.mjs} +18 -0
- package/utils/image-slots.mjs +57 -0
- package/utils/image-utils.mjs +114 -0
- package/utils/project.mjs +95 -0
- package/utils/sources-path-resolver.mjs +76 -0
- package/utils/{upload-files.mjs → upload.mjs} +3 -3
- package/utils/workspace.mjs +371 -0
- package/agents/chat/chat-system.md +0 -38
- package/agents/chat/index.mjs +0 -59
- package/agents/chat/skills/generate-document.yaml +0 -15
- package/agents/chat/skills/list-documents.mjs +0 -15
- package/agents/chat/skills/update-document.yaml +0 -24
- package/agents/clear/clear-document-config.mjs +0 -36
- package/agents/clear/clear-document-structure.mjs +0 -102
- package/agents/clear/clear-generated-docs.mjs +0 -142
- package/agents/clear/clear-media-description.mjs +0 -129
- package/agents/create/aggregate-document-structure.mjs +0 -21
- package/agents/create/analyze-diagram-type-llm.yaml +0 -159
- package/agents/create/analyze-diagram-type.mjs +0 -455
- package/agents/create/check-document-structure.yaml +0 -30
- package/agents/create/check-need-generate-structure.mjs +0 -138
- package/agents/create/document-structure-tools/add-document.mjs +0 -85
- package/agents/create/document-structure-tools/delete-document.mjs +0 -116
- package/agents/create/document-structure-tools/move-document.mjs +0 -109
- package/agents/create/document-structure-tools/update-document.mjs +0 -84
- package/agents/create/generate-diagram-image.yaml +0 -91
- package/agents/create/generate-structure.yaml +0 -106
- package/agents/create/index.yaml +0 -45
- package/agents/create/refine-document-structure.yaml +0 -12
- package/agents/create/replace-d2-with-image.mjs +0 -610
- package/agents/create/update-document-structure.yaml +0 -54
- package/agents/create/user-add-document/add-documents-to-structure.mjs +0 -90
- package/agents/create/user-add-document/find-documents-to-add-links.yaml +0 -47
- package/agents/create/user-add-document/index.yaml +0 -46
- package/agents/create/user-add-document/prepare-documents-to-translate.mjs +0 -22
- package/agents/create/user-add-document/print-add-document-summary.mjs +0 -63
- package/agents/create/user-add-document/review-documents-with-new-links.mjs +0 -110
- package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +0 -78
- package/agents/create/user-remove-document/index.yaml +0 -40
- package/agents/create/user-remove-document/prepare-documents-to-translate.mjs +0 -22
- package/agents/create/user-remove-document/print-remove-document-summary.mjs +0 -53
- package/agents/create/user-remove-document/remove-documents-from-structure.mjs +0 -99
- package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +0 -115
- package/agents/create/user-review-document-structure.mjs +0 -139
- package/agents/create/utils/init-current-content.mjs +0 -34
- package/agents/create/utils/merge-document-structures.mjs +0 -36
- package/agents/evaluate/code-snippet.mjs +0 -97
- package/agents/evaluate/document-structure.yaml +0 -67
- package/agents/evaluate/document.yaml +0 -82
- package/agents/evaluate/generate-report.mjs +0 -85
- package/agents/evaluate/index.yaml +0 -46
- package/agents/history/index.yaml +0 -6
- package/agents/history/view.mjs +0 -78
- package/agents/init/check.mjs +0 -16
- package/agents/init/index.mjs +0 -643
- package/agents/init/validate.mjs +0 -16
- package/agents/localize/choose-language.mjs +0 -107
- package/agents/localize/record-translation-history.mjs +0 -23
- package/agents/localize/save-doc-translation-or-skip.mjs +0 -18
- package/agents/localize/set-review-content.mjs +0 -58
- package/agents/localize/translate-document-wrapper.mjs +0 -34
- package/agents/localize/translate-document.yaml +0 -24
- package/agents/localize/translate-multilingual.yaml +0 -57
- package/agents/localize/translate-or-skip-diagram.mjs +0 -52
- package/agents/media/batch-generate-media-description.yaml +0 -46
- package/agents/media/generate-media-description.yaml +0 -50
- package/agents/media/load-media-description.mjs +0 -454
- package/agents/prefs/index.mjs +0 -203
- package/agents/schema/document-structure-item.yaml +0 -26
- package/agents/schema/document-structure-refine-item.yaml +0 -23
- package/agents/schema/document-structure.yaml +0 -29
- package/agents/update/batch-generate-document.yaml +0 -27
- package/agents/update/batch-update-document.yaml +0 -7
- package/agents/update/check-diagram-flag.mjs +0 -116
- package/agents/update/check-document.mjs +0 -162
- package/agents/update/check-generate-diagram.mjs +0 -106
- package/agents/update/check-update-is-single.mjs +0 -53
- package/agents/update/document-tools/update-document-content.mjs +0 -303
- package/agents/update/generate-diagram.yaml +0 -80
- package/agents/update/generate-document.yaml +0 -70
- package/agents/update/handle-document-update.yaml +0 -103
- package/agents/update/index.yaml +0 -69
- package/agents/update/pre-check-generate-diagram.yaml +0 -44
- package/agents/update/save-and-translate-document.mjs +0 -80
- package/agents/update/update-document-detail.yaml +0 -71
- package/agents/update/update-single/update-single-document-detail.mjs +0 -322
- package/agents/update/update-single-document.yaml +0 -7
- package/agents/update/user-review-document.mjs +0 -272
- package/agents/utils/action-success.mjs +0 -16
- package/agents/utils/analyze-document-feedback-intent.yaml +0 -32
- package/agents/utils/analyze-feedback-intent.mjs +0 -253
- package/agents/utils/analyze-structure-feedback-intent.yaml +0 -29
- package/agents/utils/check-detail-result.mjs +0 -51
- package/agents/utils/check-feedback-refiner.mjs +0 -81
- package/agents/utils/choose-docs.mjs +0 -251
- package/agents/utils/document-icon-generate.yaml +0 -52
- package/agents/utils/document-title-streamline.yaml +0 -48
- package/agents/utils/ensure-document-icons.mjs +0 -129
- package/agents/utils/exit.mjs +0 -6
- package/agents/utils/feedback-refiner.yaml +0 -50
- package/agents/utils/find-item-by-path.mjs +0 -114
- package/agents/utils/find-user-preferences-by-path.mjs +0 -37
- package/agents/utils/format-document-structure.mjs +0 -35
- package/agents/utils/generate-document-or-skip.mjs +0 -41
- package/agents/utils/handle-diagram-operations.mjs +0 -263
- package/agents/utils/load-all-document-content.mjs +0 -30
- package/agents/utils/load-document-all-content.mjs +0 -96
- package/agents/utils/load-sources.mjs +0 -405
- package/agents/utils/map-reasoning-effort-level.mjs +0 -15
- package/agents/utils/post-generate.mjs +0 -133
- package/agents/utils/read-current-document-content.mjs +0 -46
- package/agents/utils/save-doc-translation.mjs +0 -30
- package/agents/utils/save-doc.mjs +0 -54
- package/agents/utils/save-output.mjs +0 -26
- package/agents/utils/save-sidebar.mjs +0 -38
- package/agents/utils/skip-if-content-exists.mjs +0 -27
- package/agents/utils/streamline-document-titles-if-needed.mjs +0 -88
- package/agents/utils/transform-detail-data-sources.mjs +0 -45
- package/assets/report-template/report.html +0 -198
- package/docs-mcp/analyze-content-relevance.yaml +0 -50
- package/docs-mcp/analyze-docs-relevance.yaml +0 -59
- package/docs-mcp/docs-search.yaml +0 -42
- package/docs-mcp/get-docs-detail.mjs +0 -41
- package/docs-mcp/get-docs-structure.mjs +0 -16
- package/docs-mcp/read-doc-content.mjs +0 -119
- package/prompts/common/document/content-rules-core.md +0 -20
- package/prompts/common/document/markdown-syntax-rules.md +0 -65
- package/prompts/common/document/media-file-list-usage-rules.md +0 -18
- package/prompts/common/document/openapi-usage-rules.md +0 -189
- package/prompts/common/document/role-and-personality.md +0 -16
- package/prompts/common/document/user-preferences.md +0 -9
- package/prompts/common/document-structure/conflict-resolution-guidance.md +0 -16
- package/prompts/common/document-structure/document-icon-generate.md +0 -116
- package/prompts/common/document-structure/document-structure-rules.md +0 -43
- package/prompts/common/document-structure/document-title-streamline.md +0 -86
- package/prompts/common/document-structure/glossary.md +0 -7
- package/prompts/common/document-structure/intj-traits.md +0 -5
- package/prompts/common/document-structure/openapi-usage-rules.md +0 -28
- package/prompts/common/document-structure/output-constraints.md +0 -18
- package/prompts/common/document-structure/user-locale-rules.md +0 -10
- package/prompts/common/document-structure/user-preferences.md +0 -9
- package/prompts/detail/custom/admonition-usage-rules.md +0 -94
- package/prompts/detail/custom/code-block-usage-rules.md +0 -163
- package/prompts/detail/custom/custom-components/x-card-usage-rules.md +0 -63
- package/prompts/detail/custom/custom-components/x-cards-usage-rules.md +0 -83
- package/prompts/detail/custom/custom-components/x-field-desc-usage-rules.md +0 -120
- package/prompts/detail/custom/custom-components/x-field-group-usage-rules.md +0 -80
- package/prompts/detail/custom/custom-components/x-field-usage-rules.md +0 -189
- package/prompts/detail/custom/custom-components-usage-rules.md +0 -18
- package/prompts/detail/diagram/generate-image-user.md +0 -81
- package/prompts/detail/diagram/guide.md +0 -29
- package/prompts/detail/diagram/official-examples.md +0 -712
- package/prompts/detail/diagram/pre-check.md +0 -23
- package/prompts/detail/diagram/role-and-personality.md +0 -2
- package/prompts/detail/diagram/rules.md +0 -46
- package/prompts/detail/diagram/system-prompt.md +0 -1139
- package/prompts/detail/diagram/user-prompt.md +0 -43
- package/prompts/detail/generate/detail-example.md +0 -457
- package/prompts/detail/generate/document-rules.md +0 -45
- package/prompts/detail/generate/system-prompt.md +0 -61
- package/prompts/detail/generate/user-prompt.md +0 -99
- package/prompts/detail/jsx/rules.md +0 -6
- package/prompts/detail/update/system-prompt.md +0 -121
- package/prompts/detail/update/user-prompt.md +0 -41
- package/prompts/evaluate/document-structure.md +0 -93
- package/prompts/evaluate/document.md +0 -149
- package/prompts/media/media-description/system-prompt.md +0 -43
- package/prompts/media/media-description/user-prompt.md +0 -17
- package/prompts/structure/check-document-structure.md +0 -93
- package/prompts/structure/document-rules.md +0 -21
- package/prompts/structure/find-documents-to-add-links.md +0 -52
- package/prompts/structure/generate/system-prompt.md +0 -13
- package/prompts/structure/generate/user-prompt.md +0 -137
- package/prompts/structure/review/structure-review-system.md +0 -81
- package/prompts/structure/structure-example.md +0 -89
- package/prompts/structure/structure-getting-started.md +0 -10
- package/prompts/structure/update/system-prompt.md +0 -93
- package/prompts/structure/update/user-prompt.md +0 -43
- package/prompts/translate/admonition.md +0 -20
- package/prompts/translate/code-block.md +0 -33
- package/prompts/utils/analyze-document-feedback-intent.md +0 -54
- package/prompts/utils/analyze-structure-feedback-intent.md +0 -43
- package/prompts/utils/feedback-refiner.md +0 -105
- package/types/document-schema.mjs +0 -55
- package/types/document-structure-schema.mjs +0 -261
- package/utils/check-document-has-diagram.mjs +0 -95
- package/utils/conflict-detector.mjs +0 -149
- package/utils/constants/index.mjs +0 -620
- package/utils/constants/linter.mjs +0 -102
- package/utils/d2-utils.mjs +0 -205
- package/utils/debug.mjs +0 -3
- package/utils/delete-diagram-images.mjs +0 -99
- package/utils/diagram-version-utils.mjs +0 -14
- package/utils/docs-finder-utils.mjs +0 -548
- package/utils/evaluate/report-utils.mjs +0 -132
- package/utils/extract-api.mjs +0 -32
- package/utils/file-utils.mjs +0 -960
- package/utils/history-utils.mjs +0 -203
- package/utils/icon-map.mjs +0 -26
- package/utils/image-compress.mjs +0 -154
- package/utils/kroki-utils.mjs +0 -173
- package/utils/linter/index.mjs +0 -50
- package/utils/load-config.mjs +0 -78
- package/utils/markdown/index.mjs +0 -26
- package/utils/markdown-checker.mjs +0 -694
- package/utils/mermaid-validator.mjs +0 -140
- package/utils/mermaid-worker-pool.mjs +0 -250
- package/utils/mermaid-worker.mjs +0 -233
- package/utils/openapi/index.mjs +0 -28
- package/utils/preferences-utils.mjs +0 -175
- package/utils/request.mjs +0 -10
- package/utils/sync-diagram-to-translations.mjs +0 -272
- package/utils/translate-diagram-images.mjs +0 -807
- package/utils/utils.mjs +0 -1354
- /package/{prompts/translate → agents/localize/prompts}/glossary.md +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { readFile, access } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { parse as yamlParse } from "yaml";
|
|
4
|
+
import { PATHS, ERROR_CODES } from "./agent-constants.mjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalize path
|
|
8
|
+
* @param {string} rawPath - Raw path (may or may not have leading slash)
|
|
9
|
+
* @returns {Object} - { filePath: path without slash, displayPath: path with slash }
|
|
10
|
+
*/
|
|
11
|
+
export function normalizePath(rawPath) {
|
|
12
|
+
if (!rawPath || typeof rawPath !== "string") {
|
|
13
|
+
throw new Error("Path must be a non-empty string");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let normalized = rawPath.trim();
|
|
17
|
+
|
|
18
|
+
// Remove leading slash
|
|
19
|
+
if (normalized.startsWith("/")) {
|
|
20
|
+
normalized = normalized.slice(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Remove trailing slash
|
|
24
|
+
if (normalized.endsWith("/")) {
|
|
25
|
+
normalized = normalized.slice(0, -1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
filePath: normalized, // "overview" or "api/authentication"
|
|
30
|
+
displayPath: `/${normalized}`, // "/overview" or "/api/authentication"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Recursively collect document paths
|
|
36
|
+
* @param {Array} docs - Document array
|
|
37
|
+
* @param {Object} options - Collection options
|
|
38
|
+
* @param {boolean} options.includeBothFormats - Whether to include both formats with and without leading slash
|
|
39
|
+
* @param {boolean} options.collectMetadata - Whether to collect additional metadata
|
|
40
|
+
* @returns {Set|Array} - Path set or path object array
|
|
41
|
+
*/
|
|
42
|
+
export function collectDocumentPaths(docs, options = {}) {
|
|
43
|
+
const { includeBothFormats = false, collectMetadata = false } = options;
|
|
44
|
+
|
|
45
|
+
const paths = collectMetadata ? [] : new Set();
|
|
46
|
+
|
|
47
|
+
function collect(documents) {
|
|
48
|
+
for (const doc of documents) {
|
|
49
|
+
if (doc.path) {
|
|
50
|
+
// Normalize path
|
|
51
|
+
const normalized = doc.path.startsWith("/") ? doc.path.slice(1) : doc.path;
|
|
52
|
+
|
|
53
|
+
if (collectMetadata) {
|
|
54
|
+
// Collect path and metadata
|
|
55
|
+
paths.push({
|
|
56
|
+
path: normalized,
|
|
57
|
+
displayPath: `/${normalized}`,
|
|
58
|
+
title: doc.title || "",
|
|
59
|
+
description: doc.description || "",
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
// Only collect paths
|
|
63
|
+
paths.add(normalized);
|
|
64
|
+
if (includeBothFormats) {
|
|
65
|
+
paths.add(`/${normalized}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Recursively process child documents
|
|
71
|
+
if (doc.children && Array.isArray(doc.children)) {
|
|
72
|
+
collect(doc.children);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
collect(docs);
|
|
78
|
+
return paths;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load all paths from document structure
|
|
83
|
+
* @param {Object} options - Loading options
|
|
84
|
+
* @param {string} options.yamlPath - YAML file path
|
|
85
|
+
* @param {boolean} options.includeBothFormats - Whether to include both formats with and without leading slash
|
|
86
|
+
* @param {boolean} options.collectMetadata - Whether to collect additional metadata
|
|
87
|
+
* @param {boolean} options.throwOnInvalid - Whether to throw error when document format is invalid
|
|
88
|
+
* @returns {Promise<Set|Array>} - Set of all valid paths or path object array
|
|
89
|
+
*/
|
|
90
|
+
export async function loadDocumentPaths(options = {}) {
|
|
91
|
+
const {
|
|
92
|
+
yamlPath = PATHS.DOCUMENT_STRUCTURE,
|
|
93
|
+
includeBothFormats = false,
|
|
94
|
+
collectMetadata = false,
|
|
95
|
+
throwOnInvalid = true,
|
|
96
|
+
} = options;
|
|
97
|
+
|
|
98
|
+
// Check if file exists
|
|
99
|
+
try {
|
|
100
|
+
await access(yamlPath, constants.F_OK | constants.R_OK);
|
|
101
|
+
} catch (_error) {
|
|
102
|
+
throw new Error(ERROR_CODES.MISSING_STRUCTURE_FILE);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Read and parse YAML
|
|
106
|
+
const content = await readFile(yamlPath, "utf8");
|
|
107
|
+
const data = yamlParse(content);
|
|
108
|
+
|
|
109
|
+
if (!data.documents || !Array.isArray(data.documents)) {
|
|
110
|
+
if (throwOnInvalid) {
|
|
111
|
+
throw new Error(ERROR_CODES.INVALID_STRUCTURE_FILE);
|
|
112
|
+
} else {
|
|
113
|
+
throw new Error(ERROR_CODES.MISSING_STRUCTURE_FILE);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Recursively collect all paths
|
|
118
|
+
return collectDocumentPaths(data.documents, {
|
|
119
|
+
includeBothFormats,
|
|
120
|
+
collectMetadata,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Validate whether a path exists in the document structure
|
|
126
|
+
* @param {string} path - The path to validate
|
|
127
|
+
* @param {Set|Array} validPaths - Set of valid paths
|
|
128
|
+
* @returns {boolean} - Whether the path is valid
|
|
129
|
+
*/
|
|
130
|
+
export function isValidDocumentPath(path, validPaths) {
|
|
131
|
+
if (!path) return false;
|
|
132
|
+
|
|
133
|
+
const { filePath, displayPath } = normalizePath(path);
|
|
134
|
+
|
|
135
|
+
if (validPaths instanceof Set) {
|
|
136
|
+
return validPaths.has(filePath) || validPaths.has(displayPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (Array.isArray(validPaths)) {
|
|
140
|
+
return validPaths.some((p) => {
|
|
141
|
+
const pathValue = typeof p === "string" ? p : p.path;
|
|
142
|
+
return pathValue === filePath || pathValue === displayPath;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Filter valid paths from a path array
|
|
151
|
+
* @param {string[]} paths - Array of paths to validate
|
|
152
|
+
* @param {Set|Array} validPaths - Set of valid paths
|
|
153
|
+
* @returns {Object} - { validPaths: [], invalidPaths: [] }
|
|
154
|
+
*/
|
|
155
|
+
export function filterValidPaths(paths, validPaths) {
|
|
156
|
+
const result = {
|
|
157
|
+
validPaths: [],
|
|
158
|
+
invalidPaths: [],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
for (const path of paths) {
|
|
162
|
+
const { filePath } = normalizePath(path);
|
|
163
|
+
|
|
164
|
+
if (isValidDocumentPath(path, validPaths)) {
|
|
165
|
+
result.validPaths.push(filePath);
|
|
166
|
+
} else {
|
|
167
|
+
result.invalidPaths.push(path);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
package/utils/files.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PATHS } from "./agent-constants.mjs";
|
|
4
|
+
|
|
5
|
+
// Shared extension → MIME type mapping table
|
|
6
|
+
const EXT_TO_MIME = {
|
|
7
|
+
".jpg": "image/jpeg",
|
|
8
|
+
".jpeg": "image/jpeg",
|
|
9
|
+
".png": "image/png",
|
|
10
|
+
".gif": "image/gif",
|
|
11
|
+
".bmp": "image/bmp",
|
|
12
|
+
".webp": "image/webp",
|
|
13
|
+
".svg": "image/svg+xml",
|
|
14
|
+
".heic": "image/heic",
|
|
15
|
+
".heif": "image/heif",
|
|
16
|
+
".mp4": "video/mp4",
|
|
17
|
+
".mpeg": "video/mpeg",
|
|
18
|
+
".mpg": "video/mpg",
|
|
19
|
+
".mov": "video/mov",
|
|
20
|
+
".avi": "video/avi",
|
|
21
|
+
".flv": "video/x-flv",
|
|
22
|
+
".mkv": "video/x-matroska",
|
|
23
|
+
".webm": "video/webm",
|
|
24
|
+
".wmv": "video/wmv",
|
|
25
|
+
".m4v": "video/x-m4v",
|
|
26
|
+
".3gpp": "video/3gpp",
|
|
27
|
+
".mp3": "audio/mpeg",
|
|
28
|
+
".wav": "audio/wav",
|
|
29
|
+
".pdf": "application/pdf",
|
|
30
|
+
".doc": "application/msword",
|
|
31
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
32
|
+
".xls": "application/vnd.ms-excel",
|
|
33
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
34
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
35
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
36
|
+
".txt": "text/plain",
|
|
37
|
+
".json": "application/json",
|
|
38
|
+
".xml": "application/xml",
|
|
39
|
+
".html": "text/html",
|
|
40
|
+
".css": "text/css",
|
|
41
|
+
".js": "application/javascript",
|
|
42
|
+
".zip": "application/zip",
|
|
43
|
+
".rar": "application/x-rar-compressed",
|
|
44
|
+
".7z": "application/x-7z-compressed",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get MIME type from file path based on extension
|
|
49
|
+
* @param {string} filePath - File path
|
|
50
|
+
* @returns {string} MIME type
|
|
51
|
+
*/
|
|
52
|
+
export function getMimeType(filePath) {
|
|
53
|
+
const ext = path.extname(filePath || "").toLowerCase();
|
|
54
|
+
return EXT_TO_MIME[ext] || "application/octet-stream";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Ensure temporary directory exists
|
|
59
|
+
* @returns {Promise<void>}
|
|
60
|
+
*/
|
|
61
|
+
export async function ensureTmpDir() {
|
|
62
|
+
if (!existsSync(PATHS.TMP_DIR)) {
|
|
63
|
+
mkdirSync(PATHS.TMP_DIR, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if a file is a remote URL
|
|
69
|
+
* @param {string} file - File path or URL
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
export function isRemoteFile(file) {
|
|
73
|
+
return file && (file.startsWith("http://") || file.startsWith("https://"));
|
|
74
|
+
}
|
package/utils/git.mjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate if a URL is a valid GitHub repository URL
|
|
5
|
+
* @param {string} url - The URL to validate
|
|
6
|
+
* @returns {boolean} - True if valid GitHub URL
|
|
7
|
+
*/
|
|
8
|
+
export function isValidGithubUrl(url) {
|
|
9
|
+
if (!url || typeof url !== "string") return false;
|
|
10
|
+
// Match both HTTPS and SSH GitHub URLs
|
|
11
|
+
return /^(https:\/\/github\.com\/|git@github\.com:)[^/]+\/[^/]+/.test(url);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get GitHub repository URL
|
|
16
|
+
* @returns {string} - GitHub repository URL or empty string
|
|
17
|
+
*/
|
|
18
|
+
export function getGithubRepoUrl() {
|
|
19
|
+
try {
|
|
20
|
+
const gitRemote = execSync("git remote get-url origin", {
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
23
|
+
}).trim();
|
|
24
|
+
|
|
25
|
+
if (isValidGithubUrl(gitRemote)) {
|
|
26
|
+
return gitRemote;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return "";
|
|
30
|
+
} catch {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get GitHub repository information
|
|
37
|
+
* @param {string} repoUrl - The repository URL
|
|
38
|
+
* @returns {Promise<Object>} - Repository information
|
|
39
|
+
*/
|
|
40
|
+
export async function getGitHubRepoInfo(repoUrl) {
|
|
41
|
+
try {
|
|
42
|
+
const match = repoUrl.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
43
|
+
if (!match) return null;
|
|
44
|
+
|
|
45
|
+
const [, owner, repo] = match;
|
|
46
|
+
const apiUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
|
47
|
+
|
|
48
|
+
const response = await fetch(apiUrl);
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
console.warn("Failed to fetch GitHub repository info:", repoUrl, response.statusText);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return {
|
|
57
|
+
name: data.name,
|
|
58
|
+
description: data.description || "",
|
|
59
|
+
icon: data.owner?.avatar_url || "",
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.warn("Failed to fetch GitHub repository info:", error.message);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -102,3 +102,21 @@ export async function getComponentInfoWithMountPoint(appUrl, did) {
|
|
|
102
102
|
mountPoint: component.mountPoint,
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Perform HTTP request with authentication token
|
|
108
|
+
* @param {string} url - Request URL
|
|
109
|
+
* @param {Object} options - Fetch options
|
|
110
|
+
* @param {string} authToken - Authentication token
|
|
111
|
+
* @returns {Promise<Object>} Response JSON
|
|
112
|
+
*/
|
|
113
|
+
export async function requestWithAuthToken(url, options, authToken) {
|
|
114
|
+
if (!authToken) {
|
|
115
|
+
console.error("No authentication token provided");
|
|
116
|
+
}
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
...options,
|
|
119
|
+
headers: { ...options.headers, Authorization: `Bearer ${authToken}` },
|
|
120
|
+
});
|
|
121
|
+
return response.json();
|
|
122
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AFS Image Slot utility functions
|
|
3
|
+
* For parsing and processing image slots in documents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Slot regex (for replacement operations)
|
|
8
|
+
* Supports the following formats:
|
|
9
|
+
* - <!-- afs:image id="..." key="..." desc="..." -->
|
|
10
|
+
* - <!-- afs:image id="..." desc="..." -->
|
|
11
|
+
* - <!-- afs:image id=\"...\" key=\"...\" desc=\"...\" -->
|
|
12
|
+
* - <!-- afs:image id=\"...\" desc=\"...\" -->
|
|
13
|
+
*/
|
|
14
|
+
export const SLOT_REGEX =
|
|
15
|
+
/<!--\s*afs:image\s+id=\\?"([^\\"]+)\\?"(?:\s+key=\\?"([^\\"]+)\\?")?\s+desc=\\?"([^\\"]+)\\?"\s*-->/g;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate key (if slot doesn't provide one)
|
|
19
|
+
* @param {string} docPath - Document path (e.g., "/overview")
|
|
20
|
+
* @param {string} id - slot id
|
|
21
|
+
* @returns {string} - Generated key
|
|
22
|
+
*/
|
|
23
|
+
export function generateKey(docPath, id) {
|
|
24
|
+
// Remove leading /
|
|
25
|
+
const normalizedPath = docPath.startsWith("/") ? docPath.slice(1) : docPath;
|
|
26
|
+
// Replace / with -
|
|
27
|
+
const pathPart = normalizedPath.replace(/\//g, "-");
|
|
28
|
+
return `${pathPart}-${id}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse AFS image slots from document
|
|
33
|
+
* @param {string} content - Document content
|
|
34
|
+
* @param {string} docPath - Document path (used for generating key)
|
|
35
|
+
* @returns {Array<{id: string, key: string, desc: string, raw: string}>} - Slot array
|
|
36
|
+
*/
|
|
37
|
+
export function parseSlots(content, docPath) {
|
|
38
|
+
// Slot format: <!-- afs:image id="..." key="..." desc="..." -->
|
|
39
|
+
// key is optional
|
|
40
|
+
const slotRegex = SLOT_REGEX;
|
|
41
|
+
|
|
42
|
+
const slots = [];
|
|
43
|
+
|
|
44
|
+
for (const match of content.matchAll(slotRegex)) {
|
|
45
|
+
const id = match[1];
|
|
46
|
+
const userKey = match[2]; // May be undefined
|
|
47
|
+
const desc = match[3];
|
|
48
|
+
const raw = match[0]; // Complete slot string
|
|
49
|
+
|
|
50
|
+
// Auto-generate key if user didn't provide one
|
|
51
|
+
const key = userKey || generateKey(docPath, id);
|
|
52
|
+
|
|
53
|
+
slots.push({ id, key, desc, raw });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return slots;
|
|
57
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utility functions for image processing
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import fs from "fs-extra";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calculate SHA256 hash of a file
|
|
12
|
+
* @param {string} filePath - File path
|
|
13
|
+
* @returns {Promise<string>} - SHA256 hash (hex)
|
|
14
|
+
*/
|
|
15
|
+
export async function calculateFileHash(filePath) {
|
|
16
|
+
const content = await readFile(filePath);
|
|
17
|
+
return createHash("sha256").update(content).digest("hex");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calculate SHA256 hash of string content
|
|
22
|
+
* @param {string} content - String content
|
|
23
|
+
* @returns {string} - SHA256 hash (hex)
|
|
24
|
+
*/
|
|
25
|
+
export function calculateContentHash(content) {
|
|
26
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Supported image extensions
|
|
31
|
+
*/
|
|
32
|
+
export const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find image file (supports multiple extensions)
|
|
36
|
+
* @param {string} imagesDir - Image directory path
|
|
37
|
+
* @param {string} locale - Language code
|
|
38
|
+
* @param {string[]} extensions - Supported extension list (optional, defaults to IMAGE_EXTENSIONS)
|
|
39
|
+
* @returns {Promise<string|null>} - Image file path, returns null if not found
|
|
40
|
+
*/
|
|
41
|
+
export async function findImageFile(imagesDir, locale, extensions = IMAGE_EXTENSIONS) {
|
|
42
|
+
for (const ext of extensions) {
|
|
43
|
+
const imagePath = join(imagesDir, `${locale}${ext}`);
|
|
44
|
+
if (await fs.pathExists(imagePath)) {
|
|
45
|
+
return imagePath;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Find image file (with language fallback)
|
|
53
|
+
* @param {string} key - Image key
|
|
54
|
+
* @param {string} locale - Current language code
|
|
55
|
+
* @param {string} mainLocale - Primary language code (for fallback)
|
|
56
|
+
* @param {string} assetsDir - Assets directory path
|
|
57
|
+
* @returns {Promise<string|null>} - Image relative path (relative to assets), returns null if not found
|
|
58
|
+
*/
|
|
59
|
+
export async function findImageWithFallback(key, locale, mainLocale, assetsDir = "./assets") {
|
|
60
|
+
const keyDir = join(assetsDir, key, "images");
|
|
61
|
+
|
|
62
|
+
// 1. Try to find image for current language
|
|
63
|
+
const currentLocaleImage = await findImageFile(keyDir, locale);
|
|
64
|
+
if (currentLocaleImage) {
|
|
65
|
+
// Return path relative to assets
|
|
66
|
+
const filename = currentLocaleImage.split("/").pop();
|
|
67
|
+
return join(key, "images", filename);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. If current language doesn't exist, fall back to primary language
|
|
71
|
+
if (mainLocale && locale !== mainLocale) {
|
|
72
|
+
const mainLocaleImage = await findImageFile(keyDir, mainLocale);
|
|
73
|
+
if (mainLocaleImage) {
|
|
74
|
+
const filename = mainLocaleImage.split("/").pop();
|
|
75
|
+
return join(key, "images", filename);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. Image doesn't exist
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get image MIME type
|
|
85
|
+
* @param {string} filePath - Image file path
|
|
86
|
+
* @returns {string} - MIME type
|
|
87
|
+
*/
|
|
88
|
+
export function getImageMimeType(filePath) {
|
|
89
|
+
const ext = filePath.toLowerCase().split(".").pop();
|
|
90
|
+
const mimeTypes = {
|
|
91
|
+
jpg: "image/jpeg",
|
|
92
|
+
jpeg: "image/jpeg",
|
|
93
|
+
png: "image/png",
|
|
94
|
+
gif: "image/gif",
|
|
95
|
+
webp: "image/webp",
|
|
96
|
+
};
|
|
97
|
+
return mimeTypes[ext] || "image/jpeg";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get file extension from MIME type
|
|
102
|
+
* @param {string} mimeType - MIME type
|
|
103
|
+
* @returns {string} - File extension
|
|
104
|
+
*/
|
|
105
|
+
export function getExtensionFromMimeType(mimeType) {
|
|
106
|
+
const mimeToExt = {
|
|
107
|
+
"image/jpeg": "jpg",
|
|
108
|
+
"image/jpg": "jpg",
|
|
109
|
+
"image/png": "png",
|
|
110
|
+
"image/gif": "gif",
|
|
111
|
+
"image/webp": "webp",
|
|
112
|
+
};
|
|
113
|
+
return mimeToExt[mimeType] || "png";
|
|
114
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { SUPPORTED_LANGUAGES } from "./constants.mjs";
|
|
5
|
+
import { getGitHubRepoInfo, isValidGithubUrl } from "./git.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get project information from Git or directory
|
|
9
|
+
* @returns {Promise<Object>} - Project information
|
|
10
|
+
*/
|
|
11
|
+
export async function getProjectInfo() {
|
|
12
|
+
let repoInfo = null;
|
|
13
|
+
let defaultName = path.basename(process.cwd());
|
|
14
|
+
let defaultDescription = "";
|
|
15
|
+
let defaultIcon = "";
|
|
16
|
+
let fromGitHub = false;
|
|
17
|
+
let sourceDir = null;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Get the first folder in sources directory
|
|
21
|
+
const sourcesPath = path.join(process.cwd(), "sources");
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(sourcesPath)) {
|
|
24
|
+
const entries = fs.readdirSync(sourcesPath, { withFileTypes: true });
|
|
25
|
+
const firstFolder = entries.find((entry) => entry.isDirectory());
|
|
26
|
+
|
|
27
|
+
if (firstFolder) {
|
|
28
|
+
sourceDir = path.join(sourcesPath, firstFolder.name);
|
|
29
|
+
defaultName = firstFolder.name;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If no source directory found, fall back to current directory
|
|
34
|
+
const targetDir = sourceDir || process.cwd();
|
|
35
|
+
|
|
36
|
+
const gitRemote = execSync("git remote get-url origin", {
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
cwd: targetDir,
|
|
39
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
40
|
+
}).trim();
|
|
41
|
+
|
|
42
|
+
const repoName = gitRemote.split("/").pop().replace(".git", "");
|
|
43
|
+
defaultName = repoName;
|
|
44
|
+
|
|
45
|
+
if (isValidGithubUrl(gitRemote)) {
|
|
46
|
+
repoInfo = await getGitHubRepoInfo(gitRemote);
|
|
47
|
+
if (repoInfo) {
|
|
48
|
+
defaultDescription = repoInfo.description;
|
|
49
|
+
defaultIcon = repoInfo.icon;
|
|
50
|
+
fromGitHub = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (_error) {
|
|
54
|
+
console.warn("No git repository found, using current directory name");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: defaultName,
|
|
59
|
+
description: defaultDescription,
|
|
60
|
+
icon: defaultIcon,
|
|
61
|
+
fromGitHub,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Detect system language
|
|
67
|
+
* @returns {string} - Language code (e.g., 'en', 'zh')
|
|
68
|
+
*/
|
|
69
|
+
export function detectSystemLanguage() {
|
|
70
|
+
try {
|
|
71
|
+
let systemLocale = null;
|
|
72
|
+
|
|
73
|
+
systemLocale = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL;
|
|
74
|
+
|
|
75
|
+
if (!systemLocale) {
|
|
76
|
+
try {
|
|
77
|
+
systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
78
|
+
} catch (_error) {
|
|
79
|
+
// Intl API failed
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!systemLocale) {
|
|
84
|
+
return "en";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const languageCode = systemLocale.split(/[_.-]/)[0].toLowerCase();
|
|
88
|
+
const supportedCode = SUPPORTED_LANGUAGES.find((lang) => lang.code === languageCode);
|
|
89
|
+
|
|
90
|
+
return supportedCode ? supportedCode.code : "en";
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.warn("Failed to detect system language:", error.message);
|
|
93
|
+
return "en";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check if path is a /sources/... absolute path
|
|
6
|
+
* @param {string} imagePath - Image path
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function isSourcesAbsolutePath(imagePath) {
|
|
10
|
+
return imagePath.startsWith("/sources/");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse /sources/... absolute path and extract relative path portion
|
|
15
|
+
* @param {string} absolutePath - Absolute path, format: /sources/<relativePath>
|
|
16
|
+
* @returns {string | null} - Relative path, returns null if parsing fails or path is unsafe
|
|
17
|
+
*/
|
|
18
|
+
export function parseSourcesPath(absolutePath) {
|
|
19
|
+
// /sources/assets/screenshot.png → assets/screenshot.png
|
|
20
|
+
const match = absolutePath.match(/^\/sources\/(.+)$/);
|
|
21
|
+
if (!match) return null;
|
|
22
|
+
|
|
23
|
+
const relativePath = match[1];
|
|
24
|
+
|
|
25
|
+
// Security check: reject paths containing path traversal sequences
|
|
26
|
+
// Prevent accessing files outside sources directory via paths like /sources/../../../etc/passwd
|
|
27
|
+
if (relativePath.includes("..")) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return relativePath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve virtual absolute path to physical path based on config.yaml sources configuration
|
|
36
|
+
* Searches each source in order and returns the first existing path
|
|
37
|
+
*
|
|
38
|
+
* Execution layer perspective (after AFS mount):
|
|
39
|
+
* modules/workspace/ and modules/sources/ are at same level
|
|
40
|
+
* Documents reference using /sources/<path> format
|
|
41
|
+
*
|
|
42
|
+
* Physical disk perspective:
|
|
43
|
+
* - local-path: path relative to workspace
|
|
44
|
+
* - git-clone: workspace/sources/<name>/ directory
|
|
45
|
+
*
|
|
46
|
+
* @param {string} absolutePath - Virtual absolute path, format: /sources/<relativePath>
|
|
47
|
+
* @param {Array} sourcesConfig - sources configuration array from config.yaml
|
|
48
|
+
* @param {string} workspaceBase - Workspace physical root directory
|
|
49
|
+
* @returns {Promise<{physicalPath: string, sourceName: string} | null>} - Physical path and source name, returns null if resolution fails
|
|
50
|
+
*/
|
|
51
|
+
export async function resolveSourcesPath(absolutePath, sourcesConfig, workspaceBase) {
|
|
52
|
+
const relativePath = parseSourcesPath(absolutePath);
|
|
53
|
+
if (!relativePath) return null;
|
|
54
|
+
|
|
55
|
+
// Search in each source in order
|
|
56
|
+
for (const source of sourcesConfig) {
|
|
57
|
+
let physicalPath;
|
|
58
|
+
|
|
59
|
+
if (source.type === "local-path") {
|
|
60
|
+
// local-path: path relative to workspace
|
|
61
|
+
physicalPath = resolve(workspaceBase, source.path, relativePath);
|
|
62
|
+
} else if (source.type === "git-clone") {
|
|
63
|
+
// git-clone: cloned to workspace/sources/<name>/ directory
|
|
64
|
+
physicalPath = resolve(workspaceBase, "sources", source.name, relativePath);
|
|
65
|
+
} else {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if file exists
|
|
70
|
+
if (await fs.pathExists(physicalPath)) {
|
|
71
|
+
return { physicalPath, sourceName: source.name };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
@@ -5,9 +5,9 @@ import path from "node:path";
|
|
|
5
5
|
import pLimit from "p-limit";
|
|
6
6
|
import pRetry from "p-retry";
|
|
7
7
|
|
|
8
|
-
import { getComponentMountPoint } from "./
|
|
9
|
-
import { DISCUSS_KIT_DID, MEDIA_KIT_DID } from "./constants
|
|
10
|
-
import { getMimeType } from "./
|
|
8
|
+
import { getComponentMountPoint } from "./http.mjs";
|
|
9
|
+
import { DISCUSS_KIT_DID, MEDIA_KIT_DID } from "./constants.mjs";
|
|
10
|
+
import { getMimeType } from "./files.mjs";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Perform single file upload
|