@gitpagedocs/cli 1.1.61 → 1.1.62
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/README.md +3 -1
- package/application/config-only/handler.mjs +2 -0
- package/application/home/handler.mjs +2 -0
- package/application/ports/cli-runtime-ports.ts +4 -0
- package/application/report/config-only-reporter.mjs +4 -3
- package/builders/project-links.mjs +24 -0
- package/builders/root-config-builder.d.mts +16 -0
- package/builders/root-config-builder.mjs +9 -1
- package/content/docs-en.mjs +13 -9
- package/content/docs-es.mjs +11 -9
- package/content/docs-pt.mjs +11 -9
- package/contracts/doc-versions.mjs +1 -1
- package/contracts/index.mjs +10 -0
- package/contracts/layouts-paths.d.mts +17 -0
- package/contracts/layouts-paths.mjs +68 -0
- package/domain/models/cli-options.ts +15 -0
- package/domain/services/layouts-migration.d.mts +15 -0
- package/domain/services/layouts-migration.mjs +60 -0
- package/package.json +3 -3
- package/presentation/index.ts +8 -0
- package/presentation/options/parser.ts +15 -0
- package/presentation/options/resolver.ts +6 -8
- package/presentation/options/schema.ts +4 -0
- package/presentation/ui/layouts-prompts.ts +124 -0
- package/presentation/ui/prompts.ts +37 -29
- package/presentation/ui/tty.ts +37 -0
- package/runtime/layouts-migrator.d.mts +11 -0
- package/runtime/layouts-migrator.mjs +103 -0
- package/runtime/output.mjs +33 -20
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ npx @gitpagedocs/cli
|
|
|
17
17
|
| Command | Description |
|
|
18
18
|
|---|---|
|
|
19
19
|
| `gitpagedocs` | Generate `gitpagedocs/` config + versioned docs (config-only) |
|
|
20
|
-
| `gitpagedocs --layoutconfig` | Also emit local layout templates in `
|
|
20
|
+
| `gitpagedocs --layoutconfig` | Also emit local layout templates in `gitpagelayouts/` |
|
|
21
|
+
| `gitpagedocs --layouts-dir <dir>` | Folder for local layouts (default: `gitpagelayouts`) |
|
|
22
|
+
| `gitpagedocs --no-interactive` / `-y` | Never prompt; use flags and defaults |
|
|
21
23
|
| `gitpagedocs --push --owner <o> --repo <r> [--path <sub>]` | Generate, configure Pages, create workflow, commit + push |
|
|
22
24
|
| `gitpagedocs --home` | Standalone distribution (`gitpagedocshome/`: static site + .env + Dockerfile) |
|
|
23
25
|
| `gitpagedocs -i` / `--interactive` | Interactive prompts |
|
|
@@ -24,6 +24,7 @@ export async function executeConfigOnly(params, runtime) {
|
|
|
24
24
|
|
|
25
25
|
const artifacts = buildConfigArtifacts({
|
|
26
26
|
useLocalLayoutConfig: options.useLocalLayoutConfig,
|
|
27
|
+
layoutsDir: options.layoutsDir,
|
|
27
28
|
githubOwner: sanitizeSegment(options.githubOwner),
|
|
28
29
|
githubRepo: sanitizeSegment(options.githubRepo),
|
|
29
30
|
root,
|
|
@@ -33,6 +34,7 @@ export async function executeConfigOnly(params, runtime) {
|
|
|
33
34
|
root,
|
|
34
35
|
pkgRoot,
|
|
35
36
|
outputDir: options.outputDir,
|
|
37
|
+
layoutsDir: options.layoutsDir,
|
|
36
38
|
artifacts,
|
|
37
39
|
useLocalLayoutConfig: options.useLocalLayoutConfig,
|
|
38
40
|
layouts,
|
|
@@ -41,6 +41,7 @@ export async function executeHome(params, runtime) {
|
|
|
41
41
|
|
|
42
42
|
const artifacts = buildConfigArtifacts({
|
|
43
43
|
useLocalLayoutConfig: false,
|
|
44
|
+
layoutsDir: options.layoutsDir,
|
|
44
45
|
githubOwner: "",
|
|
45
46
|
githubRepo: "",
|
|
46
47
|
root,
|
|
@@ -50,6 +51,7 @@ export async function executeHome(params, runtime) {
|
|
|
50
51
|
root,
|
|
51
52
|
pkgRoot,
|
|
52
53
|
outputDir: ARTIFACTS_DIR,
|
|
54
|
+
layoutsDir: options.layoutsDir,
|
|
53
55
|
artifacts,
|
|
54
56
|
useLocalLayoutConfig: false,
|
|
55
57
|
layouts,
|
|
@@ -2,6 +2,8 @@ import type { CliOptions } from "../../domain/models/cli-options";
|
|
|
2
2
|
|
|
3
3
|
export interface BuildArtifactsInput {
|
|
4
4
|
useLocalLayoutConfig: boolean;
|
|
5
|
+
/** Repo-relative layouts home the generated config should reference. */
|
|
6
|
+
layoutsDir: string;
|
|
5
7
|
githubOwner: string;
|
|
6
8
|
githubRepo: string;
|
|
7
9
|
root: string;
|
|
@@ -44,6 +46,8 @@ export interface ConfigOnlyOutputInput {
|
|
|
44
46
|
root: string;
|
|
45
47
|
pkgRoot: string;
|
|
46
48
|
outputDir: string;
|
|
49
|
+
/** Repo-relative layouts home local layouts are written to. */
|
|
50
|
+
layoutsDir: string;
|
|
47
51
|
artifacts: BuiltConfigArtifacts;
|
|
48
52
|
useLocalLayoutConfig: boolean;
|
|
49
53
|
layouts: LayoutDefinition[];
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/** Config-only command output messages - SRP: single place for all report messages */
|
|
2
2
|
import { resolveRenderingUrl } from "../../builders/project-links.mjs";
|
|
3
|
+
import { normalizeLayoutsDir } from "../../contracts/layouts-paths.mjs";
|
|
3
4
|
|
|
4
5
|
export function reportConfigOnlySuccess(options) {
|
|
5
6
|
return [`Generated: ${options.outputDir}/ (config-only)`, "No index.html/index.js generated."];
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export function reportLayoutConfig(options) {
|
|
10
|
+
const layoutsDir = normalizeLayoutsDir(options.layoutsDir);
|
|
9
11
|
if (options.useLocalLayoutConfig) {
|
|
10
|
-
return [
|
|
11
|
-
} else {
|
|
12
|
-
return ["Using official remote layouts config by default (no local gitpagedocs/layouts generated)."];
|
|
12
|
+
return [`Local layouts generated in ${layoutsDir}/ (--layoutconfig).`];
|
|
13
13
|
}
|
|
14
|
+
return [`Using official remote layouts config by default (no local ${layoutsDir}/ generated).`];
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function reportRenderingUrl(options) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Shared derivation of project URLs from CLI options (single source of truth
|
|
2
2
|
* for root and version config builders). */
|
|
3
3
|
|
|
4
|
+
import { layoutsArtifactPaths } from "../contracts/layouts-paths.mjs";
|
|
5
|
+
|
|
4
6
|
export const DEFAULT_PROJECT_LINK = "https://github.com/Vidigal-code/git-page-docs";
|
|
5
7
|
export const DEFAULT_RENDERING_URL = "https://vidigal-code.github.io/git-page-docs/";
|
|
6
8
|
|
|
@@ -16,6 +18,28 @@ export function resolveRenderingUrl(githubOwner, githubRepo) {
|
|
|
16
18
|
: DEFAULT_RENDERING_URL;
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Where the viewer should look for locally generated layouts.
|
|
23
|
+
*
|
|
24
|
+
* Mirrors resolveSourceViewerPath: user repositories use the symbolic HEAD ref
|
|
25
|
+
* so any default branch resolves. Without an owner/repo pair there is no URL to
|
|
26
|
+
* build, so the repo-relative path is returned - the viewer reads it directly in
|
|
27
|
+
* local mode and still discovers it by probing the repository otherwise.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} githubOwner
|
|
30
|
+
* @param {string} githubRepo
|
|
31
|
+
* @param {string} layoutsDir Repo-relative layouts home.
|
|
32
|
+
* @returns {{ config: string, templates: string }}
|
|
33
|
+
*/
|
|
34
|
+
export function resolveLayoutsLinks(githubOwner, githubRepo, layoutsDir) {
|
|
35
|
+
const paths = layoutsArtifactPaths(layoutsDir);
|
|
36
|
+
if (!githubOwner || !githubRepo) {
|
|
37
|
+
return { config: paths.config, templates: paths.templates };
|
|
38
|
+
}
|
|
39
|
+
const base = `${resolveProjectLink(githubOwner, githubRepo)}/blob/HEAD`;
|
|
40
|
+
return { config: `${base}/${paths.config}`, templates: `${base}/${paths.templates}` };
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
export function resolveSourceViewerPath(githubOwner, githubRepo) {
|
|
20
44
|
// User repositories use the symbolic HEAD ref so any default branch works
|
|
21
45
|
// (github.com, the trees API and raw.githubusercontent all accept it);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Options that shape the generated root config. */
|
|
2
|
+
export interface RootConfigOptions {
|
|
3
|
+
useLocalLayoutConfig?: boolean;
|
|
4
|
+
githubOwner?: string;
|
|
5
|
+
githubRepo?: string;
|
|
6
|
+
/** Repo-relative layouts home referenced by the generated config. */
|
|
7
|
+
layoutsDir?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RootConfig {
|
|
11
|
+
site: Record<string, unknown>;
|
|
12
|
+
VersionControl: { versions: Array<Record<string, unknown>> };
|
|
13
|
+
translations: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare function buildRootConfig(options?: RootConfigOptions): RootConfig;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/** Build root config for gitpagedocs */
|
|
2
2
|
import { OFFICIAL_LAYOUTS_CONFIG_URL, OFFICIAL_LAYOUTS_TEMPLATES_URL } from "../data/urls.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
resolveProjectLink,
|
|
5
|
+
resolveRenderingUrl,
|
|
6
|
+
resolveSourceViewerPath,
|
|
7
|
+
resolveLayoutsLinks,
|
|
8
|
+
} from "./project-links.mjs";
|
|
4
9
|
import { DOCS } from "../content/docs.mjs";
|
|
5
10
|
import { DOC_VERSIONS } from "../data/version-constants.mjs";
|
|
6
11
|
import { defaultLangMenu } from "../data/i18n-langmenu.mjs";
|
|
@@ -16,6 +21,7 @@ export function buildRootConfig(options = {}) {
|
|
|
16
21
|
const renderingUrl = resolveRenderingUrl(githubOwner, githubRepo);
|
|
17
22
|
const projectLink = resolveProjectLink(githubOwner, githubRepo);
|
|
18
23
|
const sourceViewerPath = resolveSourceViewerPath(githubOwner, githubRepo);
|
|
24
|
+
const layoutsLinks = resolveLayoutsLinks(githubOwner, githubRepo, options.layoutsDir);
|
|
19
25
|
|
|
20
26
|
const versionEntries = DOC_VERSIONS.map((id) => ({
|
|
21
27
|
id,
|
|
@@ -39,6 +45,8 @@ export function buildRootConfig(options = {}) {
|
|
|
39
45
|
layoutsConfigPathOficial: useOfficialLayouts,
|
|
40
46
|
layoutsConfigPathTemplatesOficial: useOfficialLayouts ? OFFICIAL_LAYOUTS_TEMPLATES_URL : "",
|
|
41
47
|
layoutsConfigPathOficialUrl: useOfficialLayouts ? OFFICIAL_LAYOUTS_CONFIG_URL : "",
|
|
48
|
+
layoutsConfigPath: useLocalLayoutConfig ? layoutsLinks.config : "",
|
|
49
|
+
layoutsConfigPathTemplates: useLocalLayoutConfig ? layoutsLinks.templates : "",
|
|
42
50
|
repositorySearchHome,
|
|
43
51
|
rendering: renderingUrl,
|
|
44
52
|
AiChatEnabled: true,
|
package/content/docs-en.mjs
CHANGED
|
@@ -21,8 +21,8 @@ The runtime expects this structure:
|
|
|
21
21
|
- \`gitpagedocs/docs/<lang>/*.md\`
|
|
22
22
|
- \`gitpagedocs/docs/versions/<version>/config.json\`
|
|
23
23
|
- \`gitpagedocs/docs/versions/<version>/<lang>/*.md\`
|
|
24
|
-
- \`
|
|
25
|
-
- \`
|
|
24
|
+
- \`gitpagelayouts/layoutsConfig.json\` (only with \`--layoutconfig\`)
|
|
25
|
+
- \`gitpagelayouts/templates/*.json\` (only with \`--layoutconfig\`)
|
|
26
26
|
|
|
27
27
|
## Quick navigation
|
|
28
28
|
|
|
@@ -113,10 +113,11 @@ Complete reference of CLI options, configuration keys, and runtime features.
|
|
|
113
113
|
| Command | Description |
|
|
114
114
|
|---------|--------------|
|
|
115
115
|
| \`gitpagedocs\` | Generate config and docs in \`gitpagedocs/\` |
|
|
116
|
-
| \`gitpagedocs --layoutconfig\` | Also generate local layouts/templates |
|
|
116
|
+
| \`gitpagedocs --layoutconfig\` | Also generate local layouts/templates in \`gitpagelayouts/\` |
|
|
117
117
|
| \`gitpagedocs --home\` | Standalone distribution (\`gitpagedocshome/\`) |
|
|
118
118
|
| \`gitpagedocs --push --owner X --repo Y\` | Setup workflow, commit, push |
|
|
119
|
-
| \`gitpagedocs --interactive\` / \`-i\` | Interactive mode with prompts |
|
|
119
|
+
| \`gitpagedocs --interactive\` / \`-i\` | Interactive mode with prompts (the default in a terminal) |
|
|
120
|
+
| \`gitpagedocs --no-interactive\` / \`--yes\` / \`-y\` | Never prompt; use flags and defaults |
|
|
120
121
|
| \`gitpagedocs ai\` | Interactive AI documentation generator |
|
|
121
122
|
| \`gitpagedocs provider [id]\` / \`models [provider]\` | List AI providers / catalog models |
|
|
122
123
|
| \`gitpagedocs document[:repo\\|:file\\|:folder]\` | Generate documentation with AI |
|
|
@@ -136,7 +137,8 @@ Install globally with \`npm install -g @gitpagedocs/cli\`, or run one-off with \
|
|
|
136
137
|
| \`--path <subpath>\` | Docs subpath (e.g. \`docs\`); without it, base path = repo name for correct CSS/JS on project sites |
|
|
137
138
|
| \`--output <dir>\` | Output directory (default: \`gitpagedocs\`) |
|
|
138
139
|
| \`--search true|false\` | Enable/disable repository search (\`--home\`) |
|
|
139
|
-
| \`--layoutconfig\` | Generate \`
|
|
140
|
+
| \`--layoutconfig\` | Generate local layouts in \`gitpagelayouts/\` |
|
|
141
|
+
| \`--layouts-dir <dir>\` | Folder for local layouts (default: \`gitpagelayouts\`) |
|
|
140
142
|
| \`--push\` | Create workflow, commit artifacts, push |
|
|
141
143
|
| \`--home\` | Generate \`gitpagedocshome/\` (static + .env + Dockerfile) |
|
|
142
144
|
|
|
@@ -146,7 +148,7 @@ Install globally with \`npm install -g @gitpagedocs/cli\`, or run one-off with \
|
|
|
146
148
|
- \`gitpagedocs/icon.svg\` – default icon
|
|
147
149
|
- \`gitpagedocs/docs/versions/<ver>/config.json\` – per-version routes
|
|
148
150
|
- \`gitpagedocs/docs/versions/<ver>/{en,pt,es}/*.md\` – markdown docs
|
|
149
|
-
- \`
|
|
151
|
+
- \`gitpagelayouts/\` – only with \`--layoutconfig\` (folder configurable with \`--layouts-dir\`)
|
|
150
152
|
|
|
151
153
|
## Content types
|
|
152
154
|
|
|
@@ -490,9 +492,11 @@ Themes are JSON templates mapped by \`layoutsConfig.json\`.
|
|
|
490
492
|
|
|
491
493
|
## Local layout files
|
|
492
494
|
|
|
493
|
-
- \`
|
|
494
|
-
- \`
|
|
495
|
-
- \`
|
|
495
|
+
- \`gitpagelayouts/layoutsConfig.json\`
|
|
496
|
+
- \`gitpagelayouts/layoutsFallbackConfig.json\`
|
|
497
|
+
- \`gitpagelayouts/templates/*.json\`
|
|
498
|
+
|
|
499
|
+
The legacy \`gitpagedocs/layouts/\` location is no longer generated. It is still read by the viewer, and the CLI offers to move it into the layouts home - only after you confirm.
|
|
496
500
|
|
|
497
501
|
## Template model
|
|
498
502
|
|
package/content/docs-es.mjs
CHANGED
|
@@ -19,8 +19,8 @@ El runtime espera esta estructura:
|
|
|
19
19
|
- \`gitpagedocs/docs/<lang>/*.md\`
|
|
20
20
|
- \`gitpagedocs/docs/versions/<version>/config.json\`
|
|
21
21
|
- \`gitpagedocs/docs/versions/<version>/<lang>/*.md\`
|
|
22
|
-
- \`
|
|
23
|
-
- \`
|
|
22
|
+
- \`gitpagelayouts/layoutsConfig.json\`
|
|
23
|
+
- \`gitpagelayouts/templates/*.json\`
|
|
24
24
|
|
|
25
25
|
## Navegacion rapida
|
|
26
26
|
|
|
@@ -106,10 +106,11 @@ Referencia completa de opciones CLI, claves de configuracion y funciones del run
|
|
|
106
106
|
| Comando | Descripcion |
|
|
107
107
|
|---------|-------------|
|
|
108
108
|
| \`npx @gitpagedocs/cli\` | Genera config y docs en \`gitpagedocs/\` |
|
|
109
|
-
| \`npx @gitpagedocs/cli --layoutconfig\` | Tambien genera layouts/templates locales |
|
|
109
|
+
| \`npx @gitpagedocs/cli --layoutconfig\` | Tambien genera layouts/templates locales en \`gitpagelayouts/\` |
|
|
110
110
|
| \`npx @gitpagedocs/cli --home\` | Distribucion standalone (\`gitpagedocshome/\`) |
|
|
111
111
|
| \`npx @gitpagedocs/cli --push --owner X --repo Y\` | Configura workflow, commit, push |
|
|
112
|
-
| \`npx @gitpagedocs/cli --interactive\` / \`-i\` | Modo interactivo con prompts |
|
|
112
|
+
| \`npx @gitpagedocs/cli --interactive\` / \`-i\` | Modo interactivo con prompts (por defecto en una terminal) |
|
|
113
|
+
| \`npx @gitpagedocs/cli --no-interactive\` / \`-y\` | Nunca pregunta; usa flags y valores por defecto |
|
|
113
114
|
|
|
114
115
|
## Opciones CLI
|
|
115
116
|
|
|
@@ -120,7 +121,8 @@ Referencia completa de opciones CLI, claves de configuracion y funciones del run
|
|
|
120
121
|
| \`--path <subpath>\` | Subruta de docs (ej: \`docs\`); sin ella, base path = nombre del repo para CSS/JS en project sites |
|
|
121
122
|
| \`--output <dir>\` | Directorio de salida (default: \`gitpagedocs\`) |
|
|
122
123
|
| \`--search true|false\` | Habilita/deshabilita busqueda de repositorio (\`--home\`) |
|
|
123
|
-
| \`--layoutconfig\` | Genera \`
|
|
124
|
+
| \`--layoutconfig\` | Genera layouts locales en \`gitpagelayouts/\` |
|
|
125
|
+
| \`--layouts-dir <dir>\` | Carpeta de layouts locales (por defecto: \`gitpagelayouts\`) |
|
|
124
126
|
| \`--push\` | Crea workflow, commit de artefactos, push |
|
|
125
127
|
| \`--home\` | Genera \`gitpagedocshome/\` (estatico + .env + Dockerfile) |
|
|
126
128
|
|
|
@@ -130,7 +132,7 @@ Referencia completa de opciones CLI, claves de configuracion y funciones del run
|
|
|
130
132
|
- \`gitpagedocs/icon.svg\` – icono por defecto
|
|
131
133
|
- \`gitpagedocs/docs/versions/<ver>/config.json\` – rutas por version
|
|
132
134
|
- \`gitpagedocs/docs/versions/<ver>/{en,pt,es}/*.md\` – docs en markdown
|
|
133
|
-
- \`
|
|
135
|
+
- \`gitpagelayouts/\` – solo con \`--layoutconfig\` (carpeta configurable con \`--layouts-dir\`)
|
|
134
136
|
|
|
135
137
|
## Tipos de contenido
|
|
136
138
|
|
|
@@ -400,9 +402,9 @@ Los temas son templates JSON mapeados por \`layoutsConfig.json\`.
|
|
|
400
402
|
|
|
401
403
|
## Archivos
|
|
402
404
|
|
|
403
|
-
- \`
|
|
404
|
-
- \`
|
|
405
|
-
- \`
|
|
405
|
+
- \`gitpagelayouts/layoutsConfig.json\`
|
|
406
|
+
- \`gitpagelayouts/layoutsFallbackConfig.json\`
|
|
407
|
+
- \`gitpagelayouts/templates/*.json\`
|
|
406
408
|
|
|
407
409
|
## Modelo de template
|
|
408
410
|
|
package/content/docs-pt.mjs
CHANGED
|
@@ -19,8 +19,8 @@ O runtime espera esta estrutura:
|
|
|
19
19
|
- \`gitpagedocs/docs/<lang>/*.md\`
|
|
20
20
|
- \`gitpagedocs/docs/versions/<versao>/config.json\`
|
|
21
21
|
- \`gitpagedocs/docs/versions/<versao>/<lang>/*.md\`
|
|
22
|
-
- \`
|
|
23
|
-
- \`
|
|
22
|
+
- \`gitpagelayouts/layoutsConfig.json\`
|
|
23
|
+
- \`gitpagelayouts/templates/*.json\`
|
|
24
24
|
|
|
25
25
|
## Navegacao rapida
|
|
26
26
|
|
|
@@ -106,10 +106,11 @@ Referencia completa de opcoes da CLI, chaves de configuracao e recursos do runti
|
|
|
106
106
|
| Comando | Descricao |
|
|
107
107
|
|---------|------------|
|
|
108
108
|
| \`npx @gitpagedocs/cli\` | Gera config e docs em \`gitpagedocs/\` |
|
|
109
|
-
| \`npx @gitpagedocs/cli --layoutconfig\` | Tambem gera layouts/templates locais |
|
|
109
|
+
| \`npx @gitpagedocs/cli --layoutconfig\` | Tambem gera layouts/templates locais em \`gitpagelayouts/\` |
|
|
110
110
|
| \`npx @gitpagedocs/cli --home\` | Distribuicao standalone (\`gitpagedocshome/\`) |
|
|
111
111
|
| \`npx @gitpagedocs/cli --push --owner X --repo Y\` | Configura workflow, commit, push |
|
|
112
|
-
| \`npx @gitpagedocs/cli --interactive\` / \`-i\` | Modo interativo com prompts |
|
|
112
|
+
| \`npx @gitpagedocs/cli --interactive\` / \`-i\` | Modo interativo com prompts (padrao em um terminal) |
|
|
113
|
+
| \`npx @gitpagedocs/cli --no-interactive\` / \`-y\` | Nunca pergunta; usa flags e padroes |
|
|
113
114
|
|
|
114
115
|
## Opcoes da CLI
|
|
115
116
|
|
|
@@ -120,7 +121,8 @@ Referencia completa de opcoes da CLI, chaves de configuracao e recursos do runti
|
|
|
120
121
|
| \`--path <subpath>\` | Subcaminho dos docs (ex: \`docs\`); sem ele, base path = nome do repo para CSS/JS em project sites |
|
|
121
122
|
| \`--output <dir>\` | Diretorio de saida (padrao: \`gitpagedocs\`) |
|
|
122
123
|
| \`--search true|false\` | Habilita/desabilita busca de repositorio (\`--home\`) |
|
|
123
|
-
| \`--layoutconfig\` | Gera \`
|
|
124
|
+
| \`--layoutconfig\` | Gera layouts locais em \`gitpagelayouts/\` |
|
|
125
|
+
| \`--layouts-dir <dir>\` | Pasta dos layouts locais (padrao: \`gitpagelayouts\`) |
|
|
124
126
|
| \`--push\` | Cria workflow, commit de artefatos, push |
|
|
125
127
|
| \`--home\` | Gera \`gitpagedocshome/\` (estatico + .env + Dockerfile) |
|
|
126
128
|
|
|
@@ -130,7 +132,7 @@ Referencia completa de opcoes da CLI, chaves de configuracao e recursos do runti
|
|
|
130
132
|
- \`gitpagedocs/icon.svg\` – icone padrao
|
|
131
133
|
- \`gitpagedocs/docs/versions/<ver>/config.json\` – rotas por versao
|
|
132
134
|
- \`gitpagedocs/docs/versions/<ver>/{en,pt,es}/*.md\` – docs em markdown
|
|
133
|
-
- \`
|
|
135
|
+
- \`gitpagelayouts/\` – apenas com \`--layoutconfig\` (pasta configuravel com \`--layouts-dir\`)
|
|
134
136
|
|
|
135
137
|
## Tipos de conteudo
|
|
136
138
|
|
|
@@ -400,9 +402,9 @@ Temas sao templates JSON mapeados por \`layoutsConfig.json\`.
|
|
|
400
402
|
|
|
401
403
|
## Arquivos
|
|
402
404
|
|
|
403
|
-
- \`
|
|
404
|
-
- \`
|
|
405
|
-
- \`
|
|
405
|
+
- \`gitpagelayouts/layoutsConfig.json\`
|
|
406
|
+
- \`gitpagelayouts/layoutsFallbackConfig.json\`
|
|
407
|
+
- \`gitpagelayouts/templates/*.json\`
|
|
406
408
|
|
|
407
409
|
## Modelo de template
|
|
408
410
|
|
package/contracts/index.mjs
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
1
|
export { DOC_VERSIONS } from "./doc-versions.mjs";
|
|
2
|
+
export {
|
|
3
|
+
DEFAULT_LAYOUTS_DIR,
|
|
4
|
+
LAYOUTS_CONFIG_FILENAME,
|
|
5
|
+
LAYOUTS_FALLBACK_CONFIG_FILENAME,
|
|
6
|
+
LAYOUTS_TEMPLATES_DIRNAME,
|
|
7
|
+
LEGACY_LAYOUTS_SUBDIR,
|
|
8
|
+
normalizeLayoutsDir,
|
|
9
|
+
legacyLayoutsDir,
|
|
10
|
+
layoutsArtifactPaths,
|
|
11
|
+
} from "./layouts-paths.mjs";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Repo-relative paths owned by a layouts folder. */
|
|
2
|
+
export interface LayoutsArtifactPaths {
|
|
3
|
+
root: string;
|
|
4
|
+
config: string;
|
|
5
|
+
fallbackConfig: string;
|
|
6
|
+
templates: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export declare const DEFAULT_LAYOUTS_DIR: string;
|
|
10
|
+
export declare const LAYOUTS_CONFIG_FILENAME: string;
|
|
11
|
+
export declare const LAYOUTS_FALLBACK_CONFIG_FILENAME: string;
|
|
12
|
+
export declare const LAYOUTS_TEMPLATES_DIRNAME: string;
|
|
13
|
+
export declare const LEGACY_LAYOUTS_SUBDIR: string;
|
|
14
|
+
|
|
15
|
+
export declare function normalizeLayoutsDir(dir: string): string;
|
|
16
|
+
export declare function legacyLayoutsDir(outputDir: string): string;
|
|
17
|
+
export declare function layoutsArtifactPaths(layoutsDir: string): LayoutsArtifactPaths;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical locations for layout artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Local layouts are generated into the standalone `gitpagelayouts/` home at the
|
|
5
|
+
* project root. `<outputDir>/layouts/` is the legacy location that earlier
|
|
6
|
+
* versions wrote: the viewer still reads it, and the CLI offers to migrate it,
|
|
7
|
+
* but nothing is generated there any more.
|
|
8
|
+
*
|
|
9
|
+
* Every consumer derives its paths from here so the folder name exists in one
|
|
10
|
+
* place only.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Standalone layouts home, relative to the project root. */
|
|
14
|
+
export const DEFAULT_LAYOUTS_DIR = "gitpagelayouts";
|
|
15
|
+
|
|
16
|
+
/** Index file listing every layout, inside a layouts folder. */
|
|
17
|
+
export const LAYOUTS_CONFIG_FILENAME = "layoutsConfig.json";
|
|
18
|
+
|
|
19
|
+
/** Fallback index used when a layout id cannot be resolved. */
|
|
20
|
+
export const LAYOUTS_FALLBACK_CONFIG_FILENAME = "layoutsFallbackConfig.json";
|
|
21
|
+
|
|
22
|
+
/** Folder holding one JSON template per layout, inside a layouts folder. */
|
|
23
|
+
export const LAYOUTS_TEMPLATES_DIRNAME = "templates";
|
|
24
|
+
|
|
25
|
+
/** Sub-folder of the docs output dir that older versions generated into. */
|
|
26
|
+
export const LEGACY_LAYOUTS_SUBDIR = "layouts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a user-supplied folder name to a repo-relative POSIX path with no
|
|
30
|
+
* leading/trailing separators, so it can be joined and stored in config.json
|
|
31
|
+
* identically on every platform.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} dir Raw folder name (may use either separator).
|
|
34
|
+
* @returns {string} Normalized path, or `DEFAULT_LAYOUTS_DIR` when empty.
|
|
35
|
+
*/
|
|
36
|
+
export function normalizeLayoutsDir(dir) {
|
|
37
|
+
const normalized = String(dir ?? "")
|
|
38
|
+
.replace(/[\\/]+/g, "/")
|
|
39
|
+
.replace(/^\/+|\/+$/g, "")
|
|
40
|
+
.trim();
|
|
41
|
+
return normalized || DEFAULT_LAYOUTS_DIR;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Legacy layouts folder for a given docs output dir.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} outputDir Docs output dir (e.g. `gitpagedocs`).
|
|
48
|
+
* @returns {string} Repo-relative POSIX path (e.g. `gitpagedocs/layouts`).
|
|
49
|
+
*/
|
|
50
|
+
export function legacyLayoutsDir(outputDir) {
|
|
51
|
+
return `${normalizeLayoutsDir(outputDir)}/${LEGACY_LAYOUTS_SUBDIR}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Repo-relative paths of every artifact a layouts folder owns.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} layoutsDir Layouts folder, repo-relative.
|
|
58
|
+
* @returns {{ root: string, config: string, fallbackConfig: string, templates: string }}
|
|
59
|
+
*/
|
|
60
|
+
export function layoutsArtifactPaths(layoutsDir) {
|
|
61
|
+
const root = normalizeLayoutsDir(layoutsDir);
|
|
62
|
+
return {
|
|
63
|
+
root,
|
|
64
|
+
config: `${root}/${LAYOUTS_CONFIG_FILENAME}`,
|
|
65
|
+
fallbackConfig: `${root}/${LAYOUTS_FALLBACK_CONFIG_FILENAME}`,
|
|
66
|
+
templates: `${root}/${LAYOUTS_TEMPLATES_DIRNAME}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
export type CliMode = "home" | "full" | "config-only" | "ai";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Values the user supplied explicitly on the command line. Interactive mode
|
|
5
|
+
* only asks for what is missing, so an explicit flag is never re-asked.
|
|
6
|
+
*/
|
|
7
|
+
export interface CliExplicitFlags {
|
|
8
|
+
useLocalLayoutConfig: boolean;
|
|
9
|
+
layoutsDir: boolean;
|
|
10
|
+
githubOwner: boolean;
|
|
11
|
+
githubRepo: boolean;
|
|
12
|
+
outputDir: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
export interface CliOptions {
|
|
4
16
|
isBuild: boolean;
|
|
5
17
|
isServe: boolean;
|
|
6
18
|
mode: CliMode;
|
|
7
19
|
aiCommand?: string;
|
|
8
20
|
outputDir: string;
|
|
21
|
+
/** Standalone layouts home (repo-relative); local layouts are generated here. */
|
|
22
|
+
layoutsDir: string;
|
|
9
23
|
useLocalLayoutConfig: boolean;
|
|
10
24
|
shouldPush: boolean;
|
|
11
25
|
githubOwner: string;
|
|
@@ -15,4 +29,5 @@ export interface CliOptions {
|
|
|
15
29
|
repositorySearch?: boolean;
|
|
16
30
|
isInteractive: boolean;
|
|
17
31
|
hasArgs: boolean;
|
|
32
|
+
explicit: CliExplicitFlags;
|
|
18
33
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** A planned move from the legacy layouts folder to the standalone home. */
|
|
2
|
+
export interface LayoutsMigrationPlan {
|
|
3
|
+
required: boolean;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
files: string[];
|
|
7
|
+
overwrites: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare function planLayoutsMigration(options: {
|
|
11
|
+
outputDir: string;
|
|
12
|
+
layoutsDir: string;
|
|
13
|
+
legacyFiles?: string[];
|
|
14
|
+
targetFiles?: string[];
|
|
15
|
+
}): LayoutsMigrationPlan;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy layouts migration — planning only.
|
|
3
|
+
*
|
|
4
|
+
* Deciding *what* should move is domain logic, so it stays free of prompts and
|
|
5
|
+
* filesystem writes: callers list the two folders, ask the user, and perform
|
|
6
|
+
* the move. That keeps the rule testable with plain arrays.
|
|
7
|
+
*/
|
|
8
|
+
import { legacyLayoutsDir, normalizeLayoutsDir } from "../../contracts/layouts-paths.mjs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} LayoutsMigrationPlan
|
|
12
|
+
* @property {boolean} required True when the legacy folder holds files to move.
|
|
13
|
+
* @property {string} from Repo-relative legacy folder.
|
|
14
|
+
* @property {string} to Repo-relative destination folder.
|
|
15
|
+
* @property {string[]} files Paths relative to `from`, sorted.
|
|
16
|
+
* @property {string[]} overwrites Subset of `files` already present at `to`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Plan the move from the legacy layouts folder to the standalone home.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
* @param {string} options.outputDir Docs output dir (owns the legacy folder).
|
|
24
|
+
* @param {string} options.layoutsDir Destination layouts folder.
|
|
25
|
+
* @param {string[]} [options.legacyFiles] Paths relative to the legacy folder.
|
|
26
|
+
* @param {string[]} [options.targetFiles] Paths relative to the destination folder.
|
|
27
|
+
* @returns {LayoutsMigrationPlan}
|
|
28
|
+
*/
|
|
29
|
+
export function planLayoutsMigration(options = {}) {
|
|
30
|
+
const { outputDir, layoutsDir, legacyFiles, targetFiles } = options;
|
|
31
|
+
|
|
32
|
+
const from = legacyLayoutsDir(outputDir);
|
|
33
|
+
const to = normalizeLayoutsDir(layoutsDir);
|
|
34
|
+
|
|
35
|
+
const files = toSortedPaths(legacyFiles);
|
|
36
|
+
const present = new Set(toSortedPaths(targetFiles));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
required: files.length > 0 && from !== to,
|
|
40
|
+
from,
|
|
41
|
+
to,
|
|
42
|
+
files,
|
|
43
|
+
overwrites: files.filter((file) => present.has(file)),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize, de-duplicate and sort a list of relative paths so a plan is
|
|
49
|
+
* deterministic regardless of directory-read order.
|
|
50
|
+
*
|
|
51
|
+
* @param {unknown} paths
|
|
52
|
+
* @returns {string[]}
|
|
53
|
+
*/
|
|
54
|
+
function toSortedPaths(paths) {
|
|
55
|
+
if (!Array.isArray(paths)) return [];
|
|
56
|
+
const normalized = paths
|
|
57
|
+
.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
58
|
+
.map((entry) => entry.replace(/[\\/]+/g, "/").replace(/^\/+/, ""));
|
|
59
|
+
return [...new Set(normalized)].sort();
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitpagedocs/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.62",
|
|
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.
|
|
33
|
-
"@gitpagedocs/tools": "^1.1.
|
|
32
|
+
"@gitpagedocs/mcp": "^1.1.62",
|
|
33
|
+
"@gitpagedocs/tools": "^1.1.62",
|
|
34
34
|
"tsx": "^4.21.0"
|
|
35
35
|
},
|
|
36
36
|
"repository": {
|
package/presentation/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { printBanner, printCredits } from "./ui/banner";
|
|
6
6
|
import { resolveOptions } from "./options/resolver";
|
|
7
|
+
import { migrateLegacyLayoutsInteractive } from "./ui/layouts-prompts";
|
|
7
8
|
import { runNewCommand } from "./commands/run-command";
|
|
8
9
|
import { dispatchMode } from "../application/use-cases/dispatch-mode";
|
|
9
10
|
import type {
|
|
@@ -57,6 +58,13 @@ async function main(): Promise<void> {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
const options = await resolveOptions(process.argv, process.env);
|
|
61
|
+
|
|
62
|
+
// Layouts moved out of <outputDir>/layouts/. Offer to relocate anything left
|
|
63
|
+
// behind before generating, so the config written next points at one folder.
|
|
64
|
+
if (options.mode === "config-only") {
|
|
65
|
+
await migrateLegacyLayoutsInteractive(root, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
const configOnlyRuntime = createConfigOnlyRuntime() as ConfigOnlyRuntimePort;
|
|
61
69
|
const homeRuntime = createHomeRuntime() as HomeRuntimePort;
|
|
62
70
|
await dispatchMode(options, params, {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CliOptions } from "../../domain/models/cli-options";
|
|
2
2
|
import { DEFAULTS } from "./schema";
|
|
3
|
+
import { normalizeLayoutsDir } from "../../contracts/layouts-paths.mjs";
|
|
3
4
|
|
|
4
5
|
const KNOWN_FLAGS = new Set([
|
|
5
6
|
"--build",
|
|
@@ -11,8 +12,12 @@ const KNOWN_FLAGS = new Set([
|
|
|
11
12
|
"--search",
|
|
12
13
|
"--path",
|
|
13
14
|
"--output",
|
|
15
|
+
"--layouts-dir",
|
|
14
16
|
"--interactive",
|
|
15
17
|
"-i",
|
|
18
|
+
"--no-interactive",
|
|
19
|
+
"--yes",
|
|
20
|
+
"-y",
|
|
16
21
|
"--ai",
|
|
17
22
|
"--pages-actions",
|
|
18
23
|
]);
|
|
@@ -35,6 +40,7 @@ export function parseCliOptions(argv: string[], env: NodeJS.ProcessEnv): CliOpti
|
|
|
35
40
|
let githubRepo = readOptionValue(args, "--repo");
|
|
36
41
|
const docsPath = readOptionValue(args, "--path");
|
|
37
42
|
const outputDir = readOptionValue(args, "--output");
|
|
43
|
+
const layoutsDir = readOptionValue(args, "--layouts-dir");
|
|
38
44
|
const searchRaw = readOptionValue(args, "--search");
|
|
39
45
|
|
|
40
46
|
const fallbackDashedArgs = args
|
|
@@ -45,6 +51,7 @@ export function parseCliOptions(argv: string[], env: NodeJS.ProcessEnv): CliOpti
|
|
|
45
51
|
.filter((a) => !a.startsWith("--path"))
|
|
46
52
|
.filter((a) => !a.startsWith("--home"))
|
|
47
53
|
.filter((a) => !a.startsWith("--output"))
|
|
54
|
+
.filter((a) => !a.startsWith("--layouts-dir"))
|
|
48
55
|
.filter((a) => !a.startsWith("--search"))
|
|
49
56
|
.map((a) => a.replace(/^--/, "").trim())
|
|
50
57
|
.filter(Boolean);
|
|
@@ -91,6 +98,7 @@ export function parseCliOptions(argv: string[], env: NodeJS.ProcessEnv): CliOpti
|
|
|
91
98
|
mode,
|
|
92
99
|
aiCommand,
|
|
93
100
|
outputDir: outputDir || (isHome ? DEFAULTS.home.outputDir : DEFAULTS.outputDir),
|
|
101
|
+
layoutsDir: normalizeLayoutsDir(layoutsDir || DEFAULTS.layoutsDir),
|
|
94
102
|
useLocalLayoutConfig,
|
|
95
103
|
shouldPush,
|
|
96
104
|
githubOwner,
|
|
@@ -100,5 +108,12 @@ export function parseCliOptions(argv: string[], env: NodeJS.ProcessEnv): CliOpti
|
|
|
100
108
|
repositorySearch,
|
|
101
109
|
isInteractive,
|
|
102
110
|
hasArgs: args.length > 0,
|
|
111
|
+
explicit: {
|
|
112
|
+
useLocalLayoutConfig,
|
|
113
|
+
layoutsDir: Boolean(layoutsDir),
|
|
114
|
+
githubOwner: Boolean(githubOwner),
|
|
115
|
+
githubRepo: Boolean(githubRepo),
|
|
116
|
+
outputDir: Boolean(outputDir),
|
|
117
|
+
},
|
|
103
118
|
};
|
|
104
119
|
}
|
|
@@ -34,15 +34,13 @@ export async function resolveOptions(argv: string[], env: NodeJS.ProcessEnv): Pr
|
|
|
34
34
|
return resolved;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
// Every command prompts in a real terminal; shouldRunInteractive owns the
|
|
38
|
+
// CI / piped-stdin / --no-interactive opt-outs, and each prompt asks only for
|
|
39
|
+
// what the command line left unanswered.
|
|
40
|
+
if (!shouldRunInteractive(argv)) return parsed;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
return promptConfigOnlyOptions(parsed);
|
|
45
|
-
}
|
|
42
|
+
if (parsed.mode === "home") return promptHomeOptions(parsed);
|
|
43
|
+
if (parsed.mode === "config-only") return promptConfigOnlyOptions(parsed);
|
|
46
44
|
|
|
47
45
|
return parsed;
|
|
48
46
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { DEFAULT_LAYOUTS_DIR } from "../../contracts/layouts-paths.mjs";
|
|
2
|
+
|
|
1
3
|
export const DEFAULTS = {
|
|
2
4
|
outputDir: "gitpagedocs",
|
|
5
|
+
/** Standalone layouts home; local layouts are generated here, never in outputDir. */
|
|
6
|
+
layoutsDir: DEFAULT_LAYOUTS_DIR as string,
|
|
3
7
|
repositorySearch: false,
|
|
4
8
|
basePath: "",
|
|
5
9
|
home: {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout-related prompts: where layouts come from, where they are written, and
|
|
3
|
+
* the opt-in migration away from the legacy `<outputDir>/layouts/` folder.
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { askConfirm, askSelect, askText, note } from "./clack";
|
|
7
|
+
import { interactivePromptsAvailable } from "./tty";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_LAYOUTS_DIR,
|
|
10
|
+
normalizeLayoutsDir,
|
|
11
|
+
legacyLayoutsDir,
|
|
12
|
+
} from "../../contracts/layouts-paths.mjs";
|
|
13
|
+
import { planLayoutsMigration } from "../../domain/services/layouts-migration.mjs";
|
|
14
|
+
import { listFilesRecursively, executeLayoutsMigration } from "../../runtime/layouts-migrator.mjs";
|
|
15
|
+
|
|
16
|
+
type LayoutsSource = "official" | "local";
|
|
17
|
+
|
|
18
|
+
/** Outcome of a completed migration, for the caller to report. */
|
|
19
|
+
export interface LayoutsMigrationOutcome {
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
movedCount: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Ask whether layouts come from the official remote source or a local folder. */
|
|
26
|
+
export async function askLayoutsSource(useLocal: boolean): Promise<boolean> {
|
|
27
|
+
const choice = await askSelect<LayoutsSource>(
|
|
28
|
+
"Layout source",
|
|
29
|
+
[
|
|
30
|
+
{
|
|
31
|
+
value: "official",
|
|
32
|
+
label: "Official layouts (remote)",
|
|
33
|
+
hint: "nothing is written to your repository",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
value: "local",
|
|
37
|
+
label: "Local layouts",
|
|
38
|
+
hint: `generated into ${DEFAULT_LAYOUTS_DIR}/`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
useLocal ? "local" : "official",
|
|
42
|
+
);
|
|
43
|
+
return choice === "local";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Ask which folder should hold the generated layouts. */
|
|
47
|
+
export async function askLayoutsDir(current: string): Promise<string> {
|
|
48
|
+
const answer = await askText({
|
|
49
|
+
message: "Layouts folder:",
|
|
50
|
+
defaultValue: normalizeLayoutsDir(current),
|
|
51
|
+
validate: (value) => (value.trim() ? undefined : "A folder name is required."),
|
|
52
|
+
});
|
|
53
|
+
return normalizeLayoutsDir(answer);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toAbsolute(root: string, repoRelative: string): string {
|
|
57
|
+
return path.join(root, ...repoRelative.split("/"));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Offer to move layouts left behind in the legacy folder.
|
|
62
|
+
*
|
|
63
|
+
* Nothing is moved without an explicit confirmation. Outside a TTY the legacy
|
|
64
|
+
* folder is reported and left alone — the viewer still reads it, so declining
|
|
65
|
+
* (or automating) never breaks an existing site.
|
|
66
|
+
*
|
|
67
|
+
* @returns The outcome when files were moved, otherwise `null`.
|
|
68
|
+
*/
|
|
69
|
+
export async function migrateLegacyLayoutsInteractive(
|
|
70
|
+
root: string,
|
|
71
|
+
options: { outputDir: string; layoutsDir: string },
|
|
72
|
+
): Promise<LayoutsMigrationOutcome | null> {
|
|
73
|
+
const from = legacyLayoutsDir(options.outputDir);
|
|
74
|
+
const to = normalizeLayoutsDir(options.layoutsDir);
|
|
75
|
+
|
|
76
|
+
const plan = planLayoutsMigration({
|
|
77
|
+
outputDir: options.outputDir,
|
|
78
|
+
layoutsDir: to,
|
|
79
|
+
legacyFiles: listFilesRecursively(toAbsolute(root, from)),
|
|
80
|
+
targetFiles: listFilesRecursively(toAbsolute(root, to)),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!plan.required) return null;
|
|
84
|
+
|
|
85
|
+
if (!interactivePromptsAvailable()) {
|
|
86
|
+
note(
|
|
87
|
+
`${plan.files.length} layout file(s) still live in ${plan.from}/.\n` +
|
|
88
|
+
`The viewer still reads them. Run this in a terminal to move them to ${plan.to}/.`,
|
|
89
|
+
"Legacy layouts",
|
|
90
|
+
);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const overwriteWarning =
|
|
95
|
+
plan.overwrites.length > 0
|
|
96
|
+
? `\n${plan.overwrites.length} file(s) already in ${plan.to}/ would be replaced.`
|
|
97
|
+
: "";
|
|
98
|
+
note(
|
|
99
|
+
`Found ${plan.files.length} layout file(s) in ${plan.from}/.\n` +
|
|
100
|
+
`Layouts are now generated in ${plan.to}/.${overwriteWarning}`,
|
|
101
|
+
"Legacy layouts",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const confirmed = await askConfirm(
|
|
105
|
+
`Move them to ${plan.to}/ now?`,
|
|
106
|
+
plan.overwrites.length === 0,
|
|
107
|
+
);
|
|
108
|
+
if (!confirmed) {
|
|
109
|
+
note(`Left untouched — the viewer still reads ${plan.from}/.`, "Legacy layouts");
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = executeLayoutsMigration(root, plan);
|
|
114
|
+
|
|
115
|
+
if (result.failed.length > 0) {
|
|
116
|
+
note(
|
|
117
|
+
`${result.failed.length} file(s) could not be moved:\n` +
|
|
118
|
+
result.failed.map((entry) => `- ${entry.file}: ${entry.reason}`).join("\n"),
|
|
119
|
+
"Migration incomplete",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { from: plan.from, to: plan.to, movedCount: result.moved.length };
|
|
124
|
+
}
|
|
@@ -4,26 +4,19 @@ import { execSync } from "node:child_process";
|
|
|
4
4
|
import type { CliOptions } from "../../domain/models/cli-options";
|
|
5
5
|
import { DEFAULTS } from "../options/schema";
|
|
6
6
|
import { askText, askConfirm, note } from "./clack";
|
|
7
|
+
import { interactivePromptsAvailable, promptingEnabled } from "./tty";
|
|
8
|
+
import { askLayoutsSource, askLayoutsDir } from "./layouts-prompts";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
if (process.env.CI === "true") return true;
|
|
10
|
-
if (process.env.GITHUB_ACTIONS === "true") return true;
|
|
11
|
-
if (process.stdin && !process.stdin.isTTY) return true;
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** True when clack prompts can run (interactive TTY, not CI). */
|
|
16
|
-
export function interactivePromptsAvailable(): boolean {
|
|
17
|
-
return !isCiOrNonTty();
|
|
18
|
-
}
|
|
10
|
+
export { interactivePromptsAvailable };
|
|
19
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Every command prompts when it runs in a real terminal, so a bare invocation
|
|
14
|
+
* reflects what the user actually needs instead of silently taking defaults.
|
|
15
|
+
* CI, a piped stdin, and an explicit `--no-interactive` / `--yes` / `-y` opt out;
|
|
16
|
+
* `--interactive` / `-i` remains accepted and reads the same either way.
|
|
17
|
+
*/
|
|
20
18
|
export function shouldRunInteractive(argv: string[]): boolean {
|
|
21
|
-
|
|
22
|
-
const args = argv.slice(2);
|
|
23
|
-
if (args.includes("--interactive") || args.includes("-i")) return true;
|
|
24
|
-
if (args.length === 0) return true;
|
|
25
|
-
if (args.length === 1 && args[0] === "--home") return true;
|
|
26
|
-
return false;
|
|
19
|
+
return promptingEnabled(argv.slice(2));
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
export async function promptHomeOptions(parsed: CliOptions): Promise<CliOptions> {
|
|
@@ -115,23 +108,38 @@ export async function ensureGitRepoInteractive(root: string): Promise<void> {
|
|
|
115
108
|
}
|
|
116
109
|
}
|
|
117
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Ask only for what the command line did not already answer, so an explicit
|
|
113
|
+
* flag is never second-guessed by a prompt.
|
|
114
|
+
*/
|
|
118
115
|
export async function promptConfigOnlyOptions(parsed: CliOptions): Promise<CliOptions> {
|
|
119
|
-
const useLocalLayoutConfig =
|
|
120
|
-
|
|
121
|
-
parsed.useLocalLayoutConfig ?? false
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
116
|
+
const useLocalLayoutConfig = parsed.explicit.useLocalLayoutConfig
|
|
117
|
+
? parsed.useLocalLayoutConfig
|
|
118
|
+
: await askLayoutsSource(parsed.useLocalLayoutConfig ?? false);
|
|
119
|
+
|
|
120
|
+
const layoutsDir =
|
|
121
|
+
useLocalLayoutConfig && !parsed.explicit.layoutsDir
|
|
122
|
+
? await askLayoutsDir(parsed.layoutsDir)
|
|
123
|
+
: parsed.layoutsDir;
|
|
124
|
+
|
|
125
|
+
const githubOwner = parsed.explicit.githubOwner
|
|
126
|
+
? parsed.githubOwner
|
|
127
|
+
: await askText({
|
|
128
|
+
message: "GitHub owner (optional):",
|
|
129
|
+
defaultValue: parsed.githubOwner ?? "",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const githubRepo = parsed.explicit.githubRepo
|
|
133
|
+
? parsed.githubRepo
|
|
134
|
+
: await askText({
|
|
135
|
+
message: "GitHub repo (optional):",
|
|
136
|
+
defaultValue: parsed.githubRepo ?? "",
|
|
137
|
+
});
|
|
131
138
|
|
|
132
139
|
return {
|
|
133
140
|
...parsed,
|
|
134
141
|
useLocalLayoutConfig,
|
|
142
|
+
layoutsDir,
|
|
135
143
|
githubOwner: githubOwner?.trim() ?? "",
|
|
136
144
|
githubRepo: githubRepo?.trim() ?? "",
|
|
137
145
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal capability detection.
|
|
3
|
+
*
|
|
4
|
+
* Kept in its own module so every prompt module can ask "may I prompt?" without
|
|
5
|
+
* importing the prompt modules themselves (which would be circular).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Environment variables that mark a non-interactive automated run. */
|
|
9
|
+
const CI_ENV_FLAGS = ["CI", "GITHUB_ACTIONS"] as const;
|
|
10
|
+
|
|
11
|
+
/** Flags that explicitly opt out of prompting. */
|
|
12
|
+
export const NON_INTERACTIVE_FLAGS = ["--no-interactive", "--yes", "-y"] as const;
|
|
13
|
+
|
|
14
|
+
function isCiOrNonTty(): boolean {
|
|
15
|
+
for (const flag of CI_ENV_FLAGS) {
|
|
16
|
+
if (process.env[flag] === "true") return true;
|
|
17
|
+
}
|
|
18
|
+
return Boolean(process.stdin && !process.stdin.isTTY);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** True when clack prompts can run (interactive TTY, not CI). */
|
|
22
|
+
export function interactivePromptsAvailable(): boolean {
|
|
23
|
+
return !isCiOrNonTty();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** True when the user explicitly asked for a non-interactive run. */
|
|
27
|
+
export function promptingOptedOut(args: readonly string[]): boolean {
|
|
28
|
+
return NON_INTERACTIVE_FLAGS.some((flag) => args.includes(flag));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* True when prompts should run for this invocation: a real terminal, and no
|
|
33
|
+
* explicit opt-out.
|
|
34
|
+
*/
|
|
35
|
+
export function promptingEnabled(args: readonly string[]): boolean {
|
|
36
|
+
return interactivePromptsAvailable() && !promptingOptedOut(args);
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface LayoutsMigrationResult {
|
|
2
|
+
moved: string[];
|
|
3
|
+
failed: Array<{ file: string; reason: string }>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export declare function listFilesRecursively(absoluteDir: string): string[];
|
|
7
|
+
|
|
8
|
+
export declare function executeLayoutsMigration(
|
|
9
|
+
root: string,
|
|
10
|
+
plan: { from: string; to: string; files: string[] },
|
|
11
|
+
): LayoutsMigrationResult;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem side of the legacy layouts migration.
|
|
3
|
+
*
|
|
4
|
+
* The decision of *what* moves lives in the domain planner; this module only
|
|
5
|
+
* reads directories and performs the move, so both halves stay testable on
|
|
6
|
+
* their own.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
statSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
copyFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* List every file under a directory, as POSIX paths relative to it.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} absoluteDir
|
|
23
|
+
* @returns {string[]} Empty when the directory is absent or unreadable.
|
|
24
|
+
*/
|
|
25
|
+
export function listFilesRecursively(absoluteDir) {
|
|
26
|
+
if (!absoluteDir || !existsSync(absoluteDir)) return [];
|
|
27
|
+
|
|
28
|
+
const found = [];
|
|
29
|
+
const walk = (currentDir, prefix) => {
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = readdirSync(currentDir);
|
|
33
|
+
} catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const absolute = path.join(currentDir, entry);
|
|
38
|
+
const relative = prefix ? `${prefix}/${entry}` : entry;
|
|
39
|
+
let isDirectory = false;
|
|
40
|
+
try {
|
|
41
|
+
isDirectory = statSync(absolute).isDirectory();
|
|
42
|
+
} catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (isDirectory) walk(absolute, relative);
|
|
46
|
+
else found.push(relative);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
walk(absoluteDir, "");
|
|
51
|
+
return found.sort();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Move one file, falling back to copy + delete when rename is not possible
|
|
56
|
+
* (different volumes, or a locked handle on Windows).
|
|
57
|
+
*
|
|
58
|
+
* @param {string} fromAbsolute
|
|
59
|
+
* @param {string} toAbsolute
|
|
60
|
+
*/
|
|
61
|
+
function moveFile(fromAbsolute, toAbsolute) {
|
|
62
|
+
mkdirSync(path.dirname(toAbsolute), { recursive: true });
|
|
63
|
+
try {
|
|
64
|
+
renameSync(fromAbsolute, toAbsolute);
|
|
65
|
+
} catch {
|
|
66
|
+
copyFileSync(fromAbsolute, toAbsolute);
|
|
67
|
+
rmSync(fromAbsolute, { force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execute a migration plan produced by `planLayoutsMigration`.
|
|
73
|
+
*
|
|
74
|
+
* Files are moved one by one so a single failure is reported without aborting
|
|
75
|
+
* the rest; the legacy folder is removed only once it is genuinely empty.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} root Project root the plan's paths are relative to.
|
|
78
|
+
* @param {{ from: string, to: string, files: string[] }} plan
|
|
79
|
+
* @returns {{ moved: string[], failed: Array<{ file: string, reason: string }> }}
|
|
80
|
+
*/
|
|
81
|
+
export function executeLayoutsMigration(root, plan) {
|
|
82
|
+
const moved = [];
|
|
83
|
+
const failed = [];
|
|
84
|
+
|
|
85
|
+
const fromRoot = path.join(root, ...plan.from.split("/"));
|
|
86
|
+
const toRoot = path.join(root, ...plan.to.split("/"));
|
|
87
|
+
|
|
88
|
+
for (const file of plan.files) {
|
|
89
|
+
const segments = file.split("/");
|
|
90
|
+
try {
|
|
91
|
+
moveFile(path.join(fromRoot, ...segments), path.join(toRoot, ...segments));
|
|
92
|
+
moved.push(file);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
failed.push({ file, reason: error instanceof Error ? error.message : String(error) });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (failed.length === 0 && listFilesRecursively(fromRoot).length === 0) {
|
|
99
|
+
rmSync(fromRoot, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { moved, failed };
|
|
103
|
+
}
|
package/runtime/output.mjs
CHANGED
|
@@ -8,12 +8,7 @@ import {
|
|
|
8
8
|
withVersionBadge,
|
|
9
9
|
normalizeToOutputPath,
|
|
10
10
|
} from "./doc-path-resolver.mjs";
|
|
11
|
-
|
|
12
|
-
/** True when `child` is the same as, or nested inside, `parent` (path-safe). */
|
|
13
|
-
function isWithin(parent, child) {
|
|
14
|
-
const rel = path.relative(parent, child);
|
|
15
|
-
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
16
|
-
}
|
|
11
|
+
import { layoutsArtifactPaths } from "../contracts/layouts-paths.mjs";
|
|
17
12
|
|
|
18
13
|
const DEFAULT_ICON_SVG = `<?xml version="1.0" encoding="utf-8"?>
|
|
19
14
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -57,6 +52,29 @@ export async function writeText(root, relativePath, data) {
|
|
|
57
52
|
await writeFile(absolutePath, data, "utf-8");
|
|
58
53
|
}
|
|
59
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Write the local layout artifacts into the standalone layouts home.
|
|
57
|
+
*
|
|
58
|
+
* No layouts folder is ever deleted here. The legacy `<outputDir>/layouts/`
|
|
59
|
+
* folder can hold hand-edited templates and the viewer still reads it, so
|
|
60
|
+
* relocating it is an explicit, confirmed migration rather than a side effect of
|
|
61
|
+
* generating. Switching back to official layouts likewise leaves an existing
|
|
62
|
+
* layouts home in place: `layoutsConfigPathOficial` already decides which source
|
|
63
|
+
* the viewer uses, so the files are inert rather than harmful.
|
|
64
|
+
*/
|
|
65
|
+
async function writeLayoutArtifacts(options) {
|
|
66
|
+
const { root, layoutsDir, useLocalLayoutConfig, layouts, artifacts, createThemeTemplate } = options;
|
|
67
|
+
|
|
68
|
+
if (!useLocalLayoutConfig) return;
|
|
69
|
+
|
|
70
|
+
const paths = layoutsArtifactPaths(layoutsDir);
|
|
71
|
+
await writeJson(root, paths.config, artifacts.layoutsConfig);
|
|
72
|
+
await writeJson(root, paths.fallbackConfig, artifacts.fallbackLayoutsConfig);
|
|
73
|
+
for (const layout of layouts) {
|
|
74
|
+
await writeJson(root, `${paths.root}/${layout.file}`, createThemeTemplate(layout));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
export async function writeConfigOnlyOutput(options) {
|
|
61
79
|
const {
|
|
62
80
|
root,
|
|
@@ -64,6 +82,7 @@ export async function writeConfigOnlyOutput(options) {
|
|
|
64
82
|
outputDir,
|
|
65
83
|
artifacts,
|
|
66
84
|
useLocalLayoutConfig,
|
|
85
|
+
layoutsDir,
|
|
67
86
|
layouts,
|
|
68
87
|
createThemeTemplate,
|
|
69
88
|
} = options;
|
|
@@ -78,20 +97,14 @@ export async function writeConfigOnlyOutput(options) {
|
|
|
78
97
|
|
|
79
98
|
await writeJson(root, `${outputDir}/config.json`, artifacts.rootConfig);
|
|
80
99
|
await writeText(root, `${outputDir}/icon.svg`, DEFAULT_ICON_SVG);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} else if (existsSync(layoutsPath) && !isWithin(root, pkgRoot)) {
|
|
90
|
-
// Only clean a stale local layouts/ when the CLI runs from OUTSIDE the
|
|
91
|
-
// project (a real install) — never when pkgRoot is inside the project being
|
|
92
|
-
// generated (the gitpagedocs source repo), so committed layouts survive.
|
|
93
|
-
rmSync(layoutsPath, { recursive: true, force: true });
|
|
94
|
-
}
|
|
100
|
+
await writeLayoutArtifacts({
|
|
101
|
+
root,
|
|
102
|
+
layoutsDir,
|
|
103
|
+
useLocalLayoutConfig,
|
|
104
|
+
layouts,
|
|
105
|
+
artifacts,
|
|
106
|
+
createThemeTemplate,
|
|
107
|
+
});
|
|
95
108
|
|
|
96
109
|
for (const [versionId, versionConfig] of Object.entries(artifacts.versionConfigs)) {
|
|
97
110
|
await writeJson(root, `${outputDir}/docs/versions/${versionId}/config.json`, versionConfig);
|