@gitpagedocs/cli 1.1.67 → 1.1.69

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.
Files changed (38) hide show
  1. package/ai/application/ai-command.ts +1 -1
  2. package/ai/application/docs-pattern.ts +9 -5
  3. package/ai/application/resolve-chat-credentials.ts +13 -6
  4. package/ai/core/models/parse-ai-cli-config.ts +6 -8
  5. package/application/home/handler.mjs +3 -1
  6. package/application/ports/cli-runtime-ports.ts +2 -0
  7. package/application/report/config-only-reporter.mjs +9 -5
  8. package/builders/config-orchestrator.mjs +7 -6
  9. package/builders/language-bundles-builder.mjs +24 -0
  10. package/builders/root-config-builder.mjs +1 -6
  11. package/content/docs-en.mjs +11 -4
  12. package/content/docs-es.mjs +11 -4
  13. package/content/docs-pt.mjs +11 -4
  14. package/contracts/doc-versions.mjs +1 -1
  15. package/contracts/langs-paths.mjs +28 -0
  16. package/contracts/languages.mjs +6 -0
  17. package/contracts/layouts-paths.mjs +4 -4
  18. package/contracts/path-segments.d.mts +2 -0
  19. package/contracts/path-segments.mjs +15 -0
  20. package/data/default-site-config.mjs +9 -2
  21. package/domain/services/layouts-migration.mjs +15 -1
  22. package/home/home-file-writer.mjs +2 -3
  23. package/home/templates.mjs +5 -2
  24. package/infrastructure/home/runtime.mjs +2 -2
  25. package/package.json +3 -3
  26. package/presentation/commands/config-info.ts +54 -6
  27. package/presentation/commands/diagnostics.ts +30 -20
  28. package/presentation/commands/docs.ts +7 -2
  29. package/presentation/commands/pages.ts +4 -2
  30. package/presentation/options/parser.ts +46 -45
  31. package/presentation/ui/prompts.ts +6 -5
  32. package/runtime/doc-path-resolver.mjs +8 -6
  33. package/runtime/exec.mjs +168 -0
  34. package/runtime/git-ops.mjs +22 -20
  35. package/runtime/layouts-migrator.mjs +15 -1
  36. package/runtime/output.mjs +99 -57
  37. package/runtime/workflow.mjs +3 -1
  38. package/start.mjs +8 -6
@@ -5,7 +5,7 @@
5
5
  import { ILLMProvider } from '../core/ports/illm-provider';
6
6
 
7
7
  export class AiCommandService {
8
- constructor(private provider: ILLMProvider) { }
8
+ constructor(private readonly provider: ILLMProvider) { }
9
9
 
10
10
  /**
11
11
  * Executes the AI Documentation Generation for a specific content
@@ -14,21 +14,25 @@ export interface AiDocPage {
14
14
  body: string;
15
15
  }
16
16
 
17
- const PAGE_DELIMITER = /^===\s*PAGE:\s*(.+?)\s*\|\s*(.*?)\s*===\s*$/;
17
+ // Slug is everything up to the first `|`, title everything up to the closing
18
+ // `===`; both are trimmed by the caller. Kept free of overlapping quantifiers
19
+ // so a hostile line cannot make the match super-linear.
20
+ const PAGE_DELIMITER = /^===\s*PAGE:([^|]*)\|(.*)===\s*$/;
18
21
 
19
22
  /** Turn an arbitrary heading into a safe lowercase-kebab slug. */
20
23
  export function slugify(raw: string): string {
21
24
  const slug = (raw ?? "")
22
25
  .toLowerCase()
23
26
  .normalize("NFKD")
24
- .replace(/[^a-z0-9]+/g, "-")
25
- .replace(/^-+|-+$/g, "")
27
+ .split(/[^a-z0-9]+/)
28
+ .filter(Boolean)
29
+ .join("-")
26
30
  .slice(0, 60);
27
31
  return slug || "page";
28
32
  }
29
33
 
30
34
  export function parseAiPages(markdown: string): AiDocPage[] {
31
- const text = (markdown ?? "").replace(/\r\n/g, "\n").trim();
35
+ const text = (markdown ?? "").replaceAll("\r\n", "\n").trim();
32
36
  if (!text) return [];
33
37
 
34
38
  const pages: AiDocPage[] = [];
@@ -41,7 +45,7 @@ export function parseAiPages(markdown: string): AiDocPage[] {
41
45
  };
42
46
 
43
47
  for (const line of text.split("\n")) {
44
- const match = line.match(PAGE_DELIMITER);
48
+ const match = PAGE_DELIMITER.exec(line);
45
49
  if (match) {
46
50
  flush();
47
51
  const rawSlug = match[1].trim();
@@ -2,6 +2,7 @@ import { legacyProviderToCatalogId, resolveApiKeyFromEnv } from "@gitpagedocs/to
2
2
  import type { AiProviderId } from "@gitpagedocs/tools/ports";
3
3
  import { PROVIDER_CATALOG } from "@gitpagedocs/tools";
4
4
  import { AiConfigFileRepository } from "../infrastructure/ai-config-file";
5
+ import type { AiCliConfig } from "../core/models/ai-cli-config";
5
6
 
6
7
  export interface ResolvedChatCredentials {
7
8
  readonly providerId: AiProviderId;
@@ -35,12 +36,7 @@ export async function resolveChatCredentials(
35
36
  const configRepo = input.configRepo ?? new AiConfigFileRepository({ cwd: input.cwd });
36
37
  const stored = await configRepo.read().catch(() => null);
37
38
 
38
- const providerId = input.providerOverride
39
- ? legacyProviderToCatalogId(input.providerOverride)
40
- : stored
41
- ? legacyProviderToCatalogId(stored.ai.provider)
42
- : firstProviderFromEnv(env) ?? "openai";
43
-
39
+ const providerId = resolveProviderId(input.providerOverride, stored, env);
44
40
  const spec = PROVIDER_CATALOG[providerId];
45
41
  const model = input.modelOverride?.trim() || stored?.ai.model?.trim() || spec.defaultModel;
46
42
  const baseUrl = stored?.ai.baseUrl?.trim() || undefined;
@@ -52,6 +48,17 @@ export async function resolveChatCredentials(
52
48
  return { providerId, model, apiKey, baseUrl };
53
49
  }
54
50
 
51
+ /** Explicit override > stored config > first provider with an env key > openai. */
52
+ function resolveProviderId(
53
+ override: string | undefined,
54
+ stored: AiCliConfig | null,
55
+ env: Readonly<Record<string, string | undefined>>,
56
+ ): AiProviderId {
57
+ if (override) return legacyProviderToCatalogId(override);
58
+ if (stored) return legacyProviderToCatalogId(stored.ai.provider);
59
+ return firstProviderFromEnv(env) ?? "openai";
60
+ }
61
+
55
62
  function firstProviderFromEnv(
56
63
  env: Readonly<Record<string, string | undefined>>,
57
64
  ): AiProviderId | undefined {
@@ -1,8 +1,10 @@
1
1
  import { AI_MODEL_DEFAULTS, type AiProviderId } from "../../config";
2
2
  import type { AiCliConfig } from "./ai-cli-config";
3
3
 
4
- const SUPPORTED_PROVIDERS = Object.keys(AI_MODEL_DEFAULTS) as AiProviderId[];
5
- const SUPPORTED_LANGUAGES: ReadonlyArray<AiCliConfig["ai"]["languages"][number]> = ["pt", "en", "es"];
4
+ type SupportedLanguage = AiCliConfig["ai"]["languages"][number];
5
+
6
+ const SUPPORTED_PROVIDERS: ReadonlySet<string> = new Set(Object.keys(AI_MODEL_DEFAULTS));
7
+ const SUPPORTED_LANGUAGES: ReadonlySet<string> = new Set<SupportedLanguage>(["pt", "en", "es"]);
6
8
 
7
9
  /** Fallbacks for optional fields, mirroring the interactive prompt defaults. */
8
10
  const DEFAULT_LANGUAGES: AiCliConfig["ai"]["languages"] = ["en", "pt", "es"];
@@ -27,17 +29,13 @@ function asPaths(value: unknown): string[] {
27
29
 
28
30
  function asLanguages(value: unknown): AiCliConfig["ai"]["languages"] {
29
31
  const languages = Array.isArray(value)
30
- ? value.filter((language): language is AiCliConfig["ai"]["languages"][number] =>
31
- SUPPORTED_LANGUAGES.includes(language as AiCliConfig["ai"]["languages"][number]),
32
- )
32
+ ? value.filter((language): language is SupportedLanguage => isString(language) && SUPPORTED_LANGUAGES.has(language))
33
33
  : [];
34
34
  return languages.length > 0 ? languages : [...DEFAULT_LANGUAGES];
35
35
  }
36
36
 
37
37
  function asProviderId(value: unknown): AiProviderId | null {
38
- return isString(value) && SUPPORTED_PROVIDERS.includes(value as AiProviderId)
39
- ? (value as AiProviderId)
40
- : null;
38
+ return isString(value) && SUPPORTED_PROVIDERS.has(value) ? (value as AiProviderId) : null;
41
39
  }
42
40
 
43
41
  /**
@@ -1,6 +1,7 @@
1
1
  /** Home distribution use case - static site + auxiliary files */
2
2
 
3
3
  import { STATIC_OUTPUT_DIR, ARTIFACTS_DIR } from "../../home/constants.mjs";
4
+ import { trimSlashes } from "../../contracts/path-segments.mjs";
4
5
 
5
6
  /**
6
7
  * Execute home distribution use case
@@ -30,7 +31,7 @@ export async function executeHome(params, runtime) {
30
31
  runtime.ensureDirEmpty(root, outputDir);
31
32
  }
32
33
 
33
- const pathSegment = basePath ? basePath.replace(/^\/+|\/+$/g, "") : "";
34
+ const pathSegment = basePath ? trimSlashes(basePath) : "";
34
35
  const buildEnv = {
35
36
  ...process.env,
36
37
  GITHUB_ACTIONS: "true",
@@ -83,3 +84,4 @@ export async function executeHome(params, runtime) {
83
84
  runtime.logInfo(`Serve: cd ${cdDir} && npx serve .`);
84
85
  runtime.logInfo(`Docker: cd ${cdDir} && docker build -t gitpagedocshome . && docker run -p 3000:80 gitpagedocshome`);
85
86
  }
87
+
@@ -16,6 +16,8 @@ export interface LayoutDefinition {
16
16
 
17
17
  export interface BuiltConfigArtifacts {
18
18
  rootConfig: unknown;
19
+ /** UI-string bundle per language, written to `<outputDir>/langs/<lang>.json`. */
20
+ languageBundles: Record<string, unknown>;
19
21
  layoutsConfig: unknown;
20
22
  fallbackLayoutsConfig: unknown;
21
23
  docs?: Record<string, Record<string, string>>;
@@ -1,9 +1,16 @@
1
1
  /** Config-only command output messages - SRP: single place for all report messages */
2
2
  import { resolveRenderingUrl } from "../../builders/project-links.mjs";
3
3
  import { normalizeLayoutsDir } from "../../contracts/layouts-paths.mjs";
4
+ import { languageArtifactPaths } from "../../contracts/langs-paths.mjs";
5
+ import { SUPPORTED_LANGUAGES } from "../../contracts/languages.mjs";
4
6
 
5
7
  export function reportConfigOnlySuccess(options) {
6
- return [`Generated: ${options.outputDir}/ (config-only)`, "No index.html/index.js generated."];
8
+ const langs = languageArtifactPaths(options.outputDir);
9
+ return [
10
+ `Generated: ${options.outputDir}/ (config-only)`,
11
+ `UI strings: ${langs.dir}/{${SUPPORTED_LANGUAGES.join(",")}}.json (enable/disable each language in ${langs.config} -> site.languages)`,
12
+ "No index.html/index.js generated.",
13
+ ];
7
14
  }
8
15
 
9
16
  export function reportLayoutConfig(options) {
@@ -44,10 +51,7 @@ export function reportPrebuiltDetected() {
44
51
  }
45
52
 
46
53
  export function reportAll(options, prebuiltDetected) {
47
- const lines = [];
48
- lines.push(...reportConfigOnlySuccess(options));
49
- lines.push(...reportLayoutConfig(options));
50
- lines.push(...reportRenderingUrl(options));
54
+ const lines = [...reportConfigOnlySuccess(options), ...reportLayoutConfig(options), ...reportRenderingUrl(options)];
51
55
  if (options.shouldPush) {
52
56
  lines.push(...reportPushMode());
53
57
  }
@@ -5,16 +5,16 @@ import { DOCS } from "../content/docs.mjs";
5
5
  import { DOC_VERSIONS } from "../data/version-constants.mjs";
6
6
  import { buildRootConfig } from "./root-config-builder.mjs";
7
7
  import { buildVersionConfig } from "./version-config-builder.mjs";
8
+ import { buildLanguageArtifacts } from "./language-bundles-builder.mjs";
8
9
 
9
10
  /**
10
- * Build all config artifacts (root, layouts, versions, docs).
11
- * @param {object} options - { useLocalLayoutConfig, githubOwner, githubRepo, root }
12
- * @returns {object} { rootConfig, layoutsConfig, fallbackLayoutsConfig, docs, docsHtml, versionConfigs }
11
+ * Build all config artifacts (root, languages, layouts, versions, docs).
12
+ * @param {object} options - { useLocalLayoutConfig, githubOwner, githubRepo, layoutsDir }
13
+ * @returns {object} { rootConfig, languageBundles, layoutsConfig, fallbackLayoutsConfig, docs, docsHtml, versionConfigs }
13
14
  */
14
15
  export function buildConfigArtifacts(options = {}) {
15
- const root = options.root ?? process.cwd();
16
-
17
- const rootConfig = buildRootConfig(options);
16
+ const { languageToggles, languageBundles } = buildLanguageArtifacts();
17
+ const rootConfig = buildRootConfig({ ...options, languageToggles });
18
18
  const layoutsConfig = { layouts: LAYOUTS };
19
19
  const fallbackLayoutsConfig = { layouts: FALLBACK_LAYOUTS };
20
20
 
@@ -25,6 +25,7 @@ export function buildConfigArtifacts(options = {}) {
25
25
 
26
26
  return {
27
27
  rootConfig,
28
+ languageBundles,
28
29
  layoutsConfig,
29
30
  fallbackLayoutsConfig,
30
31
  docs: DOCS,
@@ -0,0 +1,24 @@
1
+ /** Build the `site.languages` toggles + per-language UI string bundles for gitpagedocs */
2
+ import { buildLanguageToggles, splitLanguageBundles } from "@gitpagedocs/tools/i18n";
3
+ import { SUPPORTED_LANGUAGES } from "../contracts/languages.mjs";
4
+ import { defaultLangMenu } from "../data/i18n-langmenu.mjs";
5
+ import { defaultTranslations } from "../data/i18n-translations.mjs";
6
+
7
+ function assertLanguageStrings(languages) {
8
+ const missing = languages.filter((language) => !defaultLangMenu[language]);
9
+ if (missing.length > 0) {
10
+ throw new Error(`No UI strings (i18n-langmenu) for supported language(s): ${missing.join(", ")}`);
11
+ }
12
+ }
13
+
14
+ /**
15
+ * @returns {{ languageToggles: Record<string, boolean>, languageBundles: Record<string, object> }}
16
+ */
17
+ export function buildLanguageArtifacts() {
18
+ const languages = [...SUPPORTED_LANGUAGES];
19
+ assertLanguageStrings(languages);
20
+ return {
21
+ languageToggles: buildLanguageToggles(languages),
22
+ languageBundles: splitLanguageBundles(languages, defaultLangMenu, defaultTranslations),
23
+ };
24
+ }
@@ -6,10 +6,7 @@ import {
6
6
  resolveSourceViewerPath,
7
7
  resolveLayoutsLinks,
8
8
  } from "./project-links.mjs";
9
- import { DOCS } from "../content/docs.mjs";
10
9
  import { DOC_VERSIONS } from "../data/version-constants.mjs";
11
- import { defaultLangMenu } from "../data/i18n-langmenu.mjs";
12
- import { defaultTranslations } from "../data/i18n-translations.mjs";
13
10
  import { getDefaultSiteConfig } from "../data/default-site-config.mjs";
14
11
 
15
12
  export function buildRootConfig(options = {}) {
@@ -37,7 +34,7 @@ export function buildRootConfig(options = {}) {
37
34
  "source-viewer-path": sourceViewerPath,
38
35
  }));
39
36
 
40
- const baseSiteConfig = getDefaultSiteConfig(DOCS, projectLink);
37
+ const baseSiteConfig = getDefaultSiteConfig(projectLink, options.languageToggles);
41
38
 
42
39
  return {
43
40
  site: {
@@ -51,11 +48,9 @@ export function buildRootConfig(options = {}) {
51
48
  rendering: renderingUrl,
52
49
  AiChatEnabled: true,
53
50
  docsAccess: { enabled: false, publicKey: "" },
54
- langmenu: defaultLangMenu,
55
51
  },
56
52
  VersionControl: {
57
53
  versions: versionEntries,
58
54
  },
59
- translations: defaultTranslations,
60
55
  };
61
56
  }
@@ -172,9 +172,10 @@ The version config can render a **Source code** container through \`routes-sourc
172
172
 
173
173
  ## Config keys (site)
174
174
 
175
- - \`name\`, \`defaultLanguage\`, \`supportedLanguages\`
175
+ - \`name\`, \`defaultLanguage\`
176
176
  - \`docsVersion\`, \`rendering\`, \`ThemeDefault\`, \`ThemeModeDefault\`
177
177
  - \`ProjectLink\`, \`layoutsConfigPathOficial\`, \`layoutsConfigPath\`
178
+ - Languages: \`site.languages\` (enable/disable each one); UI strings: \`gitpagedocs/langs/<lang>.json\`
178
179
 
179
180
  ## Environment variables
180
181
 
@@ -316,7 +317,14 @@ Supported adapters:
316
317
  `,
317
318
  configuration: `# Configuration
318
319
 
319
- Runtime configuration lives in \`gitpagedocs/config.json\`.
320
+ Runtime configuration lives in \`gitpagedocs/config.json\`. UI text lives next to it, one file per language.
321
+
322
+ ## Languages (\`site.languages\` + \`langs/\`)
323
+
324
+ - \`site.languages\` in \`gitpagedocs/config.json\` switches each language on (\`true\`) or off (\`false\`), in menu order: \`{ "languages": { "en": true, "pt": true, "es": false } }\`. A language set to \`false\` disappears from the language selector and its strings are not loaded, even when its docs exist.
325
+ - \`gitpagedocs/langs/<lang>.json\` holds that language's strings: \`langmenu\` (header, search, source viewer, audio player, AI chat and docs-access labels) and \`translations\` (\`notFound\`, \`navigation\`, \`footer\`).
326
+ - To add a language, create \`langs/<lang>.json\` and add \`"<lang>": true\` to \`site.languages\`. There is no \`langs.json\` any more: a manifest left by release 1.1.68 is still read by configs without \`site.languages\`, and the generator removes it.
327
+ - Older \`config.json\` files that still inline \`site.langmenu\` / \`translations\` keep working; when both exist, the \`langs/\` files win and any missing key is backfilled from the current release.
320
328
 
321
329
  ## \`site\` section
322
330
 
@@ -324,7 +332,6 @@ Important keys:
324
332
 
325
333
  - \`name\`
326
334
  - \`defaultLanguage\`
327
- - \`supportedLanguages\`
328
335
  - \`docsVersion\`
329
336
  - \`rendering\`
330
337
  - \`ThemeDefault\`
@@ -453,7 +460,7 @@ A pnpm + turborepo monorepo where all business logic lives in one shared core (\
453
460
 
454
461
  - **frontend/** — Next.js viewer (Feature-Sliced: \`app / widgets / features / entities / shared\`); static export.
455
462
  - **cli/** — the published \`gitpagedocs\` bin (hexagonal: \`presentation / application / domain / infrastructure\`) + \`cli/ai/\` (the AI documentation CLI).
456
- - **tools/** — \`@gitpagedocs/tools\`: \`ai/ security/ crypto/ cache/ config/ logger/ errors/ filesystem/ documentation/ ports/\`.
463
+ - **tools/** — \`@gitpagedocs/tools\`: \`ai/ security/ crypto/ cache/ config/ logger/ errors/ filesystem/ documentation/ i18n/ ports/\`.
457
464
  - **mcp/** — \`@gitpagedocs/mcp\`: MCP tools + resources delegating to \`tools/\`.
458
465
 
459
466
  ## Main runtime modules (frontend)
@@ -156,9 +156,10 @@ El config de version puede renderizar un contenedor **Codigo fuente** via \`rout
156
156
 
157
157
  ## Claves de config (site)
158
158
 
159
- - \`name\`, \`defaultLanguage\`, \`supportedLanguages\`
159
+ - \`name\`, \`defaultLanguage\`
160
160
  - \`docsVersion\`, \`rendering\`, \`ThemeDefault\`, \`ThemeModeDefault\`
161
161
  - \`ProjectLink\`, \`layoutsConfigPathOficial\`, \`layoutsConfigPath\`
162
+ - Idiomas: \`site.languages\` (activa/desactiva cada uno); textos de la UI: \`gitpagedocs/langs/<lang>.json\`
162
163
 
163
164
  ## Variables de entorno
164
165
 
@@ -167,7 +168,14 @@ El config de version puede renderizar un contenedor **Codigo fuente** via \`rout
167
168
  `,
168
169
  configuration: `# Configuracion
169
170
 
170
- La configuracion de runtime esta en \`gitpagedocs/config.json\`.
171
+ La configuracion de runtime esta en \`gitpagedocs/config.json\`. Los textos de la UI viven al lado, un archivo por idioma.
172
+
173
+ ## Idiomas (\`site.languages\` + \`langs/\`)
174
+
175
+ - \`site.languages\` en \`gitpagedocs/config.json\` activa (\`true\`) o desactiva (\`false\`) cada idioma, en el orden del menu: \`{ "languages": { "en": true, "pt": true, "es": false } }\`. Un idioma en \`false\` desaparece del selector de idiomas y sus textos no se cargan, aunque existan sus docs.
176
+ - \`gitpagedocs/langs/<lang>.json\` guarda los textos de ese idioma: \`langmenu\` (cabecera, busqueda, visor de codigo, reproductor de audio, chat de IA y acceso a los docs) y \`translations\` (\`notFound\`, \`navigation\`, \`footer\`).
177
+ - Para agregar un idioma, crea \`langs/<lang>.json\` y agrega \`"<lang>": true\` en \`site.languages\`. Ya no existe \`langs.json\`: un manifiesto dejado por la version 1.1.68 aun se lee en configs antiguos sin \`site.languages\`, y el generador lo elimina.
178
+ - Los \`config.json\` antiguos que aun traen \`site.langmenu\` / \`translations\` inline siguen funcionando; cuando existen ambos, los archivos de \`langs/\` prevalecen y cualquier clave ausente se completa desde la version actual.
171
179
 
172
180
  ## Seccion \`site\`
173
181
 
@@ -175,7 +183,6 @@ Claves principales:
175
183
 
176
184
  - \`name\`: titulo del proyecto en UI
177
185
  - \`defaultLanguage\`: idioma por defecto
178
- - \`supportedLanguages\`: lista de idiomas disponibles
179
186
  - \`HideThemeSelector\`: ocultar/mostrar selector de tema
180
187
  - \`ThemeDefault\`: id del tema inicial
181
188
  - \`ThemeModeDefault\`: modo inicial (\`light\` o \`dark\`)
@@ -293,7 +300,7 @@ El proyecto esta organizado por fronteras de feature y responsabilidades de runt
293
300
 
294
301
  - **frontend/** — visor Next.js (Feature-Sliced: app / widgets / features / entities / shared); export estatico.
295
302
  - **cli/** — el bin publicado \`gitpagedocs\` (hexagonal) + \`cli/ai/\` (CLI de documentacion con IA).
296
- - **tools/** — \`@gitpagedocs/tools\`: ai/ security/ crypto/ cache/ config/ logger/ errors/ filesystem/ documentation/ ports/.
303
+ - **tools/** — \`@gitpagedocs/tools\`: ai/ security/ crypto/ cache/ config/ logger/ errors/ filesystem/ documentation/ i18n/ ports/.
297
304
  - **mcp/** — \`@gitpagedocs/mcp\`: herramientas + recursos MCP que delegan a tools/.
298
305
 
299
306
  ## Modulos principales (frontend)
@@ -156,9 +156,10 @@ O config da versao pode renderizar um container **Codigo fonte** via \`routes-so
156
156
 
157
157
  ## Chaves de config (site)
158
158
 
159
- - \`name\`, \`defaultLanguage\`, \`supportedLanguages\`
159
+ - \`name\`, \`defaultLanguage\`
160
160
  - \`docsVersion\`, \`rendering\`, \`ThemeDefault\`, \`ThemeModeDefault\`
161
161
  - \`ProjectLink\`, \`layoutsConfigPathOficial\`, \`layoutsConfigPath\`
162
+ - Idiomas: \`site.languages\` (liga/desliga cada um); textos da UI: \`gitpagedocs/langs/<lang>.json\`
162
163
 
163
164
  ## Variaveis de ambiente
164
165
 
@@ -167,7 +168,14 @@ O config da versao pode renderizar um container **Codigo fonte** via \`routes-so
167
168
  `,
168
169
  configuration: `# Configuracao
169
170
 
170
- A configuracao de runtime fica em \`gitpagedocs/config.json\`.
171
+ A configuracao de runtime fica em \`gitpagedocs/config.json\`. Os textos da UI ficam ao lado, um arquivo por idioma.
172
+
173
+ ## Idiomas (\`site.languages\` + \`langs/\`)
174
+
175
+ - \`site.languages\` em \`gitpagedocs/config.json\` liga (\`true\`) ou desliga (\`false\`) cada idioma, na ordem do menu: \`{ "languages": { "en": true, "pt": true, "es": false } }\`. Um idioma em \`false\` some do seletor de idiomas e seus textos nao sao carregados, mesmo que os docs existam.
176
+ - \`gitpagedocs/langs/<lang>.json\` guarda os textos daquele idioma: \`langmenu\` (cabecalho, busca, visualizador de codigo, player de audio, chat de IA e acesso aos docs) e \`translations\` (\`notFound\`, \`navigation\`, \`footer\`).
177
+ - Para adicionar um idioma, crie \`langs/<lang>.json\` e inclua \`"<lang>": true\` em \`site.languages\`. Nao existe mais \`langs.json\`: um manifesto deixado pela versao 1.1.68 ainda e lido por configs antigos sem \`site.languages\`, e o gerador o remove.
178
+ - Arquivos \`config.json\` antigos que ainda trazem \`site.langmenu\` / \`translations\` inline continuam funcionando; quando os dois existem, os arquivos de \`langs/\` prevalecem e qualquer chave ausente e preenchida a partir da versao atual.
171
179
 
172
180
  ## Secao \`site\`
173
181
 
@@ -175,7 +183,6 @@ Principais chaves:
175
183
 
176
184
  - \`name\`: titulo do projeto no UI
177
185
  - \`defaultLanguage\`: idioma padrao
178
- - \`supportedLanguages\`: lista de idiomas disponiveis
179
186
  - \`HideThemeSelector\`: esconde/mostra seletor de tema
180
187
  - \`ThemeDefault\`: id do tema inicial
181
188
  - \`ThemeModeDefault\`: modo inicial (\`light\` ou \`dark\`)
@@ -293,7 +300,7 @@ O projeto e organizado por fronteiras de feature e responsabilidades do runtime.
293
300
 
294
301
  - **frontend/** — visualizador Next.js (Feature-Sliced: app / widgets / features / entities / shared); export estatico.
295
302
  - **cli/** — o bin publicado \`gitpagedocs\` (hexagonal) + \`cli/ai/\` (CLI de documentacao com IA).
296
- - **tools/** — \`@gitpagedocs/tools\`: ai/ security/ crypto/ cache/ config/ logger/ errors/ filesystem/ documentation/ ports/.
303
+ - **tools/** — \`@gitpagedocs/tools\`: ai/ security/ crypto/ cache/ config/ logger/ errors/ filesystem/ documentation/ i18n/ ports/.
297
304
  - **mcp/** — \`@gitpagedocs/mcp\`: ferramentas + recursos MCP que delegam ao tools/.
298
305
 
299
306
  ## Modulos principais (frontend)
@@ -1,3 +1,3 @@
1
- export const PACKAGE_VERSION = "1.1.67";
1
+ export const PACKAGE_VERSION = "1.1.69";
2
2
 
3
3
  export const DOC_VERSIONS = [PACKAGE_VERSION];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Canonical locations for the language artifacts inside the docs output dir:
3
+ *
4
+ * <outputDir>/config.json `site.languages` — enable (true) / disable (false) each language
5
+ * <outputDir>/langs/<lang>.json UI strings (langmenu + translations) of one language
6
+ *
7
+ * `<outputDir>/langs.json` was the 1.1.68 manifest; `site.languages` replaced
8
+ * it, so the generator deletes a stale copy. The file names come from
9
+ * @gitpagedocs/tools so the generator and the viewer never disagree about where
10
+ * the bundles live.
11
+ */
12
+ import { LANGS_DIRNAME, LANGS_MANIFEST_FILENAME } from "@gitpagedocs/tools/i18n";
13
+
14
+ /**
15
+ * Repo-relative POSIX paths of the language artifacts for a docs output dir.
16
+ *
17
+ * @param {string} outputDir Docs output dir (e.g. `gitpagedocs`).
18
+ * @returns {{ config: string, legacyManifest: string, dir: string, bundle: (language: string) => string }}
19
+ */
20
+ export function languageArtifactPaths(outputDir) {
21
+ const dir = `${outputDir}/${LANGS_DIRNAME}`;
22
+ return {
23
+ config: `${outputDir}/config.json`,
24
+ legacyManifest: `${outputDir}/${LANGS_MANIFEST_FILENAME}`,
25
+ dir,
26
+ bundle: (language) => `${dir}/${language}.json`,
27
+ };
28
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Languages the generator ships UI strings and docs for, in menu order. This
3
+ * is the list written to `gitpagedocs/langs.json`; every entry must have a
4
+ * `langs/<lang>.json` bundle and a docs content module.
5
+ */
6
+ export const SUPPORTED_LANGUAGES = Object.freeze(["en", "pt", "es"]);
@@ -1,3 +1,5 @@
1
+ import { trimSlashes } from "./path-segments.mjs";
2
+
1
3
  /**
2
4
  * Canonical locations for layout artifacts.
3
5
  *
@@ -34,10 +36,8 @@ export const LEGACY_LAYOUTS_SUBDIR = "layouts";
34
36
  * @returns {string} Normalized path, or `DEFAULT_LAYOUTS_DIR` when empty.
35
37
  */
36
38
  export function normalizeLayoutsDir(dir) {
37
- const normalized = String(dir ?? "")
38
- .replace(/[\\/]+/g, "/")
39
- .replace(/^\/+|\/+$/g, "")
40
- .trim();
39
+ const collapsed = String(dir ?? "").replace(/[\\/]+/g, "/");
40
+ const normalized = trimSlashes(collapsed).trim();
41
41
  return normalized || DEFAULT_LAYOUTS_DIR;
42
42
  }
43
43
 
@@ -0,0 +1,2 @@
1
+ /** Strip every leading and trailing `/` from a path segment. */
2
+ export function trimSlashes(value: string): string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Strip every leading and trailing `/` from a user-supplied path segment in a
3
+ * single forward/backward scan. Same result as `.replace(/^\/+|\/+$/g, "")`
4
+ * without the super-linear backtracking that regex incurs on long slash runs.
5
+ *
6
+ * @param {string} value Raw segment such as "/docs/" or "///".
7
+ * @returns {string} The segment without surrounding slashes ("" when only slashes).
8
+ */
9
+ export function trimSlashes(value) {
10
+ let start = 0;
11
+ let end = value.length;
12
+ while (start < end && value[start] === "/") start += 1;
13
+ while (end > start && value[end - 1] === "/") end -= 1;
14
+ return value.slice(start, end);
15
+ }
@@ -1,8 +1,15 @@
1
- export function getDefaultSiteConfig(DOCS, projectLink) {
1
+ /**
2
+ * Site chrome defaults. `languages` enables (true) / disables (false) each
3
+ * language; the UI strings themselves live in langs/<lang>.json.
4
+ */
5
+ import { buildLanguageToggles } from "@gitpagedocs/tools/i18n";
6
+ import { SUPPORTED_LANGUAGES } from "../contracts/languages.mjs";
7
+
8
+ export function getDefaultSiteConfig(projectLink, languageToggles = buildLanguageToggles(SUPPORTED_LANGUAGES)) {
2
9
  return {
3
10
  name: "Git Pages Docs",
4
11
  defaultLanguage: "en",
5
- supportedLanguages: Object.keys(DOCS || { en: 1, pt: 1, es: 1 }),
12
+ languages: { ...languageToggles },
6
13
  HideThemeSelector: false,
7
14
  ThemeDefault: "aurora-dark",
8
15
  ThemeModeDefault: "dark",
@@ -56,5 +56,19 @@ function toSortedPaths(paths) {
56
56
  const normalized = paths
57
57
  .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
58
58
  .map((entry) => entry.replace(/[\\/]+/g, "/").replace(/^\/+/, ""));
59
- return [...new Set(normalized)].sort();
59
+ return [...new Set(normalized)].sort(compareCodeUnits);
60
+ }
61
+
62
+ /**
63
+ * Plain UTF-16 code-unit order (what `sort()` does by default), spelled out so
64
+ * a plan never depends on the runtime locale.
65
+ *
66
+ * @param {string} a
67
+ * @param {string} b
68
+ * @returns {number}
69
+ */
70
+ function compareCodeUnits(a, b) {
71
+ if (a < b) return -1;
72
+ if (a > b) return 1;
73
+ return 0;
60
74
  }
@@ -1,8 +1,7 @@
1
1
  /** Write .env, Dockerfile, README to home output directory */
2
2
 
3
3
  import path from "node:path";
4
- import { mkdir } from "node:fs/promises";
5
- import { writeFile } from "node:fs/promises";
4
+ import { mkdir, writeFile } from "node:fs/promises";
6
5
  import { getEnvTemplate, getDockerfileTemplate, getReadmeTemplate } from "./templates.mjs";
7
6
 
8
7
  /**
@@ -16,5 +15,5 @@ export async function writeHomeFiles(root, outputDir, opts = {}) {
16
15
 
17
16
  await writeFile(path.join(basePath, ".env"), getEnvTemplate(opts), "utf-8");
18
17
  await writeFile(path.join(basePath, "Dockerfile"), getDockerfileTemplate(), "utf-8");
19
- await writeFile(path.join(basePath, "README.md"), getReadmeTemplate({ outputDir }), "utf-8");
18
+ await writeFile(path.join(basePath, "README.md"), getReadmeTemplate(), "utf-8");
20
19
  }
@@ -22,8 +22,11 @@ CMD ["nginx", "-g", "daemon off;"]
22
22
  `;
23
23
  }
24
24
 
25
- export function getReadmeTemplate({ outputDir = "gitpagedocshome" }) {
26
- const cdDir = outputDir === "." || outputDir === "./" ? "." : outputDir;
25
+ /**
26
+ * README shipped next to the static export. Every command runs from the
27
+ * folder the file lives in, so it never needs to know the output dir name.
28
+ */
29
+ export function getReadmeTemplate() {
27
30
  return `# GitPageDocs Home
28
31
 
29
32
  Pre-built GitPageDocs static site by Vidigal-code.
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
3
- import { execSync } from "node:child_process";
3
+ import { runExecutable } from "../../runtime/exec.mjs";
4
4
  import { writeConfigOnlyOutput, writeText } from "../../runtime/output.mjs";
5
5
  import { writeHomeFiles } from "../../home/home-file-writer.mjs";
6
6
  import { logInfo, logSuccess } from "../../ui/logger.mjs";
@@ -17,7 +17,7 @@ function copyRecursive(src, dest) {
17
17
  }
18
18
 
19
19
  function runNextBuild(root, env) {
20
- execSync("npx next build frontend", { cwd: root, env, stdio: "inherit" });
20
+ runExecutable("npx", ["next", "build", "frontend"], { cwd: root, env, stdio: "inherit" });
21
21
  }
22
22
 
23
23
  export function createHomeRuntime() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitpagedocs/cli",
3
- "version": "1.1.67",
3
+ "version": "1.1.69",
4
4
  "description": "CLI that scaffolds and maintains gitpagedocs documentation, generates docs with AI, configures GitHub Pages, and runs the MCP server.",
5
5
  "bin": {
6
6
  "gitpagedocs": "index.mjs"
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^1.5.1",
32
- "@gitpagedocs/mcp": "^1.1.67",
33
- "@gitpagedocs/tools": "^1.1.67",
32
+ "@gitpagedocs/mcp": "^1.1.69",
33
+ "@gitpagedocs/tools": "^1.1.69",
34
34
  "tsx": "^4.21.0"
35
35
  },
36
36
  "repository": {