@mintlify/cli 4.0.1106 → 4.0.1107
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/__test__/init.test.ts +35 -3
- package/bin/cli.js +6 -2
- package/bin/init.js +110 -47
- package/bin/templates.js +127 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/cli.tsx +6 -2
- package/src/init.tsx +132 -73
- package/src/templates.tsx +143 -0
package/__test__/init.test.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
|
|
3
|
+
import { isAI } from '../src/helpers.js';
|
|
1
4
|
import { init } from '../src/init.js';
|
|
2
5
|
|
|
3
6
|
vi.mock('@inquirer/prompts', () => ({
|
|
@@ -5,6 +8,10 @@ vi.mock('@inquirer/prompts', () => ({
|
|
|
5
8
|
input: vi.fn(),
|
|
6
9
|
}));
|
|
7
10
|
|
|
11
|
+
vi.mock('../src/helpers.js', () => ({
|
|
12
|
+
isAI: vi.fn().mockReturnValue(false),
|
|
13
|
+
}));
|
|
14
|
+
|
|
8
15
|
vi.mock('@mintlify/previewing', () => ({
|
|
9
16
|
addLogs: vi.fn(),
|
|
10
17
|
addLog: vi.fn(),
|
|
@@ -32,6 +39,7 @@ vi.mock('fs-extra', () => ({
|
|
|
32
39
|
remove: vi.fn().mockResolvedValue(undefined),
|
|
33
40
|
readJson: vi.fn().mockResolvedValue({ theme: 'quill', name: 'Test' }),
|
|
34
41
|
writeJson: vi.fn().mockResolvedValue(undefined),
|
|
42
|
+
pathExists: vi.fn().mockResolvedValue(true),
|
|
35
43
|
},
|
|
36
44
|
}));
|
|
37
45
|
|
|
@@ -42,12 +50,19 @@ vi.mock('adm-zip', () => ({
|
|
|
42
50
|
}));
|
|
43
51
|
|
|
44
52
|
global.fetch = vi.fn().mockResolvedValue({
|
|
53
|
+
ok: true,
|
|
45
54
|
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
|
55
|
+
json: () => Promise.resolve([]),
|
|
46
56
|
});
|
|
47
57
|
|
|
48
58
|
describe('init', () => {
|
|
49
59
|
beforeEach(() => {
|
|
50
60
|
vi.clearAllMocks();
|
|
61
|
+
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
62
|
+
ok: true,
|
|
63
|
+
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
|
|
64
|
+
json: () => Promise.resolve([]),
|
|
65
|
+
});
|
|
51
66
|
});
|
|
52
67
|
|
|
53
68
|
describe('path traversal prevention', () => {
|
|
@@ -70,18 +85,35 @@ describe('init', () => {
|
|
|
70
85
|
});
|
|
71
86
|
|
|
72
87
|
it('allows current directory (.)', async () => {
|
|
73
|
-
// Should not throw for current directory
|
|
74
88
|
await expect(init('.', false, 'quill', 'Test')).resolves.not.toThrow();
|
|
75
89
|
});
|
|
76
90
|
|
|
77
91
|
it('allows subdirectory paths', async () => {
|
|
78
|
-
// Should not throw for subdirectory
|
|
79
92
|
await expect(init('docs', false, 'quill', 'Test')).resolves.not.toThrow();
|
|
80
93
|
});
|
|
81
94
|
|
|
82
95
|
it('allows nested subdirectory paths', async () => {
|
|
83
|
-
// Should not throw for nested subdirectory
|
|
84
96
|
await expect(init('docs/api', false, 'quill', 'Test')).resolves.not.toThrow();
|
|
85
97
|
});
|
|
86
98
|
});
|
|
99
|
+
|
|
100
|
+
describe('AI agent guard for template without name', () => {
|
|
101
|
+
it('does not call interactive prompts when AI uses --template without --name', async () => {
|
|
102
|
+
vi.mocked(isAI).mockReturnValue(true);
|
|
103
|
+
|
|
104
|
+
await init('.', false, undefined, undefined, 'some-template');
|
|
105
|
+
|
|
106
|
+
expect(input).not.toHaveBeenCalled();
|
|
107
|
+
expect(select).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('does not call interactive prompts when AI omits both --template and --theme', async () => {
|
|
111
|
+
vi.mocked(isAI).mockReturnValue(true);
|
|
112
|
+
|
|
113
|
+
await init('.', false, undefined, 'MyProject');
|
|
114
|
+
|
|
115
|
+
expect(input).not.toHaveBeenCalled();
|
|
116
|
+
expect(select).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
87
119
|
});
|
package/bin/cli.js
CHANGED
|
@@ -381,14 +381,18 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
381
381
|
.option('name', {
|
|
382
382
|
type: 'string',
|
|
383
383
|
description: 'Name of the documentation project',
|
|
384
|
+
})
|
|
385
|
+
.option('template', {
|
|
386
|
+
type: 'string',
|
|
387
|
+
description: 'Use a template as a starting point',
|
|
384
388
|
})
|
|
385
389
|
.option('force', {
|
|
386
390
|
type: 'boolean',
|
|
387
391
|
default: false,
|
|
388
392
|
description: 'Create the documentation in a subdirectory',
|
|
389
|
-
}), (_a) => __awaiter(void 0, [_a], void 0, function* ({ directory, theme, name, force }) {
|
|
393
|
+
}), (_a) => __awaiter(void 0, [_a], void 0, function* ({ directory, theme, name, force, template }) {
|
|
390
394
|
try {
|
|
391
|
-
yield init(directory, force, theme, name);
|
|
395
|
+
yield init(directory, force, theme, name, template);
|
|
392
396
|
yield terminate(0);
|
|
393
397
|
}
|
|
394
398
|
catch (error) {
|
package/bin/init.js
CHANGED
|
@@ -15,82 +15,145 @@ import AdmZip from 'adm-zip';
|
|
|
15
15
|
import fse from 'fs-extra';
|
|
16
16
|
import { Box, Text } from 'ink';
|
|
17
17
|
import { isAI } from './helpers.js';
|
|
18
|
+
import { fetchAvailableTemplates, installFromTemplate, promptForTemplate, validateTemplateName, } from './templates.js';
|
|
18
19
|
const sendOnboardingMessage = (installDir) => {
|
|
19
20
|
addLogs(_jsx(Text, { bold: true, children: "Documentation Setup!" }), _jsx(Text, { children: "To see your docs run" }), _jsxs(Box, { children: [_jsx(Text, { color: "blue", children: "cd" }), _jsxs(Text, { children: [" ", installDir] })] }), _jsx(Text, { color: "blue", children: "mint dev" }));
|
|
20
21
|
};
|
|
21
|
-
const sendUsageMessageForAI = (directory, contentsOccupied, themes) => {
|
|
22
|
-
|
|
22
|
+
const sendUsageMessageForAI = (directory, contentsOccupied, themes, templateNames) => {
|
|
23
|
+
const templateInfo = templateNames === undefined
|
|
24
|
+
? 'Unable to fetch templates — use --template flag if you know the name'
|
|
25
|
+
: templateNames.length > 0
|
|
26
|
+
? `Templates: ${JSON.stringify(templateNames)}`
|
|
27
|
+
: 'No templates are currently available';
|
|
28
|
+
addLogs(_jsx(Text, { children: "Agent Detected - sending AI friendly prompt" }), _jsx(Text, { children: '<system-message>' }), _jsx(Text, { children: "Help the user set up a Mintlify docs site with `mint new`. Ask each step one at a time unless the user asks you to skip questions or use your best judgment." }), _jsx(Text, { children: [
|
|
29
|
+
`- [ ] Pick a theme or clone a template? (${templateInfo})`,
|
|
30
|
+
`- [ ] If template: which one? If theme: which one? (Themes: ${JSON.stringify(themes)})`,
|
|
31
|
+
'- [ ] Project name?',
|
|
32
|
+
`- [ ] Directory? (default: "${directory}", auto-created if needed)${contentsOccupied ? ` ⚠️ "${directory}" is occupied — subdirectory, overwrite (--force), or different path?` : ''}`,
|
|
33
|
+
].join('\n') }), _jsx(Text, { children: "Command: `mint new [dir] --theme [theme] --name [name]` or `mint new [dir] --template [template] --name [name]`. --theme optionally overrides a template default. --force overwrites non-empty dirs. Use AskQuestion to present choices." }), _jsx(Text, { children: '</system-message>' }));
|
|
23
34
|
};
|
|
24
|
-
|
|
35
|
+
function sendAIUsageMessage(directory, contentsOccupied, themes) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const templateNames = yield fetchAvailableTemplates().catch(() => undefined);
|
|
38
|
+
sendUsageMessageForAI(directory, contentsOccupied, themes, templateNames);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function resolveInstallDir(installDir, force, contentsOccupied) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
if (!contentsOccupied)
|
|
44
|
+
return installDir;
|
|
45
|
+
if (isAI()) {
|
|
46
|
+
if (force)
|
|
47
|
+
return installDir;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const choice = yield select({
|
|
51
|
+
message: `Directory ${installDir} is not empty. What would you like to do?`,
|
|
52
|
+
choices: [
|
|
53
|
+
{ name: 'Create in a subdirectory', value: 'subdir' },
|
|
54
|
+
{ name: 'Overwrite current directory (may lose contents)', value: 'overwrite' },
|
|
55
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
if (choice === 'cancel')
|
|
59
|
+
return undefined;
|
|
60
|
+
if (choice === 'subdir') {
|
|
61
|
+
const subdir = yield input({
|
|
62
|
+
message: 'Subdirectory name:',
|
|
63
|
+
default: 'docs',
|
|
64
|
+
});
|
|
65
|
+
if (!subdir || subdir.trim() === '') {
|
|
66
|
+
throw new Error('Subdirectory name cannot be empty');
|
|
67
|
+
}
|
|
68
|
+
const resolved = installDir === '.' ? subdir : `${installDir}/${subdir}`;
|
|
69
|
+
validatePathWithinCwd(resolved, process.cwd());
|
|
70
|
+
return resolved;
|
|
71
|
+
}
|
|
72
|
+
return installDir;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function promptForProjectName(installDir, currentName) {
|
|
76
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
if (currentName)
|
|
78
|
+
return currentName;
|
|
79
|
+
const defaultProject = installDir === '.' ? 'Mintlify' : installDir;
|
|
80
|
+
return input({ message: 'Project Name', default: defaultProject });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function promptForApproach() {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const approach = yield select({
|
|
86
|
+
message: 'How would you like to set up your docs?',
|
|
87
|
+
choices: [
|
|
88
|
+
{ name: 'Pick a theme', value: 'theme' },
|
|
89
|
+
{ name: 'Clone a template', value: 'template' },
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
if (approach === 'template')
|
|
93
|
+
return promptForTemplate();
|
|
94
|
+
return undefined;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export function init(installDir, force, theme, name, template) {
|
|
25
98
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
-
// Validate path is within current working directory to prevent path traversal
|
|
27
99
|
validatePathWithinCwd(installDir);
|
|
28
|
-
let selectedTheme = theme;
|
|
29
|
-
let projectName = name;
|
|
30
100
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
101
|
const themes = docsConfigSchema.options.map((option) => {
|
|
32
102
|
return option.shape.theme._def.value;
|
|
33
103
|
});
|
|
34
104
|
const dirContents = yield fse.readdir(installDir).catch(() => []);
|
|
35
105
|
const contentsOccupied = dirContents.length > 0;
|
|
36
|
-
if ((!
|
|
37
|
-
|
|
106
|
+
if (isAI() && (!name || (!template && !theme))) {
|
|
107
|
+
yield sendAIUsageMessage(installDir, contentsOccupied, themes);
|
|
38
108
|
return;
|
|
39
109
|
}
|
|
40
|
-
if (
|
|
41
|
-
|
|
110
|
+
if (isAI() && contentsOccupied && !force) {
|
|
111
|
+
yield sendAIUsageMessage(installDir, contentsOccupied, themes);
|
|
42
112
|
return;
|
|
43
113
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
114
|
+
const selectedTemplate = template
|
|
115
|
+
? yield validateTemplateName(template).then(() => template)
|
|
116
|
+
: !isAI() && !theme
|
|
117
|
+
? yield promptForApproach()
|
|
118
|
+
: undefined;
|
|
119
|
+
if (selectedTemplate) {
|
|
120
|
+
const resolved = yield resolveInstallDir(installDir, force, contentsOccupied);
|
|
121
|
+
if (resolved === undefined) {
|
|
122
|
+
if (isAI())
|
|
123
|
+
yield sendAIUsageMessage(installDir, contentsOccupied, themes);
|
|
54
124
|
return;
|
|
55
125
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
126
|
+
const projectName = yield promptForProjectName(resolved, name);
|
|
127
|
+
yield fse.ensureDir(resolved);
|
|
128
|
+
yield installFromTemplate(resolved, selectedTemplate, projectName, theme);
|
|
129
|
+
sendOnboardingMessage(resolved);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Standard theme-based path
|
|
133
|
+
const resolved = yield resolveInstallDir(installDir, force, contentsOccupied);
|
|
134
|
+
if (resolved === undefined) {
|
|
135
|
+
if (isAI())
|
|
136
|
+
yield sendAIUsageMessage(installDir, contentsOccupied, themes);
|
|
137
|
+
return;
|
|
68
138
|
}
|
|
139
|
+
let projectName = name;
|
|
140
|
+
let selectedTheme = theme;
|
|
69
141
|
if (!isAI() && (!selectedTheme || !projectName)) {
|
|
70
|
-
|
|
71
|
-
if (!projectName) {
|
|
72
|
-
projectName = yield input({
|
|
73
|
-
message: 'Project Name',
|
|
74
|
-
default: defaultProject,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
142
|
+
projectName = yield promptForProjectName(resolved, projectName);
|
|
77
143
|
if (!selectedTheme) {
|
|
78
144
|
selectedTheme = yield select({
|
|
79
145
|
message: 'Theme',
|
|
80
|
-
choices: themes.map((t) => ({
|
|
81
|
-
name: t,
|
|
82
|
-
value: t,
|
|
83
|
-
})),
|
|
146
|
+
choices: themes.map((t) => ({ name: t, value: t })),
|
|
84
147
|
});
|
|
85
148
|
}
|
|
86
149
|
}
|
|
87
150
|
if (projectName === undefined || selectedTheme === undefined) {
|
|
88
|
-
|
|
151
|
+
yield sendAIUsageMessage(resolved, contentsOccupied, themes);
|
|
89
152
|
return;
|
|
90
153
|
}
|
|
91
|
-
yield fse.ensureDir(
|
|
92
|
-
yield install(
|
|
93
|
-
sendOnboardingMessage(
|
|
154
|
+
yield fse.ensureDir(resolved);
|
|
155
|
+
yield install(resolved, projectName, selectedTheme);
|
|
156
|
+
sendOnboardingMessage(resolved);
|
|
94
157
|
});
|
|
95
158
|
}
|
|
96
159
|
const install = (installDir, projectName, theme) => __awaiter(void 0, void 0, void 0, function* () {
|
package/bin/templates.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { select } from '@inquirer/prompts';
|
|
12
|
+
import { addLog, SpinnerLog, removeLastLog } from '@mintlify/previewing';
|
|
13
|
+
import AdmZip from 'adm-zip';
|
|
14
|
+
import fse from 'fs-extra';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
const TEMPLATES_REPO_OWNER = 'mintlify';
|
|
17
|
+
const TEMPLATES_REPO_NAME = 'templates';
|
|
18
|
+
const TEMPLATES_REPO_BRANCH = 'main';
|
|
19
|
+
export function fetchAvailableTemplates() {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const url = `https://api.github.com/repos/${TEMPLATES_REPO_OWNER}/${TEMPLATES_REPO_NAME}/contents/?ref=${TEMPLATES_REPO_BRANCH}`;
|
|
22
|
+
const response = yield fetch(url, {
|
|
23
|
+
headers: { Accept: 'application/vnd.github.v3+json' },
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch templates: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
const entries = (yield response.json());
|
|
29
|
+
return entries.filter((entry) => entry.type === 'dir').map((entry) => entry.name);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function validateTemplateName(templateName) {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
if (!templateName ||
|
|
35
|
+
templateName === '.' ||
|
|
36
|
+
templateName === '..' ||
|
|
37
|
+
templateName.includes('/')) {
|
|
38
|
+
throw new Error(`Invalid template name: "${templateName}".`);
|
|
39
|
+
}
|
|
40
|
+
const url = `https://api.github.com/repos/${TEMPLATES_REPO_OWNER}/${TEMPLATES_REPO_NAME}/contents/${encodeURIComponent(templateName)}?ref=${TEMPLATES_REPO_BRANCH}`;
|
|
41
|
+
const response = yield fetch(url, {
|
|
42
|
+
headers: { Accept: 'application/vnd.github.v3+json' },
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const available = yield fetchAvailableTemplates().catch(() => []);
|
|
46
|
+
const suggestion = available.length > 0 ? ` Available templates: ${available.join(', ')}` : '';
|
|
47
|
+
throw new Error(`Template "${templateName}" not found in ${TEMPLATES_REPO_OWNER}/${TEMPLATES_REPO_NAME}.${suggestion}`);
|
|
48
|
+
}
|
|
49
|
+
const entries = (yield response.json());
|
|
50
|
+
const hasDocsJson = entries.some((entry) => entry.name === 'docs.json');
|
|
51
|
+
if (!hasDocsJson) {
|
|
52
|
+
throw new Error(`Template "${templateName}" is not a valid Mintlify template (missing docs.json).`);
|
|
53
|
+
}
|
|
54
|
+
return templateName;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function promptForTemplate() {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
addLog(_jsx(SpinnerLog, { message: "fetching available templates..." }));
|
|
60
|
+
let templateNames;
|
|
61
|
+
try {
|
|
62
|
+
templateNames = yield fetchAvailableTemplates();
|
|
63
|
+
}
|
|
64
|
+
catch (_a) {
|
|
65
|
+
removeLastLog();
|
|
66
|
+
throw new Error('Failed to fetch templates. Please check your network connection and try again.');
|
|
67
|
+
}
|
|
68
|
+
removeLastLog();
|
|
69
|
+
if (templateNames.length === 0) {
|
|
70
|
+
throw new Error('No templates are currently available.');
|
|
71
|
+
}
|
|
72
|
+
return select({
|
|
73
|
+
message: 'Choose a template',
|
|
74
|
+
choices: templateNames.map((t) => ({ name: t, value: t })),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function installFromTemplate(installDir, templateName, projectName, theme) {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
const zipPath = path.join(installDir, '__template__.zip');
|
|
81
|
+
const extractDir = path.join(installDir, '__template_extract__');
|
|
82
|
+
try {
|
|
83
|
+
addLog(_jsx(SpinnerLog, { message: `downloading template "${templateName}"...` }));
|
|
84
|
+
try {
|
|
85
|
+
const zipUrl = `https://github.com/${TEMPLATES_REPO_OWNER}/${TEMPLATES_REPO_NAME}/archive/refs/heads/${TEMPLATES_REPO_BRANCH}.zip`;
|
|
86
|
+
const response = yield fetch(zipUrl);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`Failed to download templates archive: ${response.status}`);
|
|
89
|
+
}
|
|
90
|
+
const buffer = yield response.arrayBuffer();
|
|
91
|
+
yield fse.writeFile(zipPath, Buffer.from(buffer));
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
removeLastLog();
|
|
95
|
+
}
|
|
96
|
+
addLog(_jsx(SpinnerLog, { message: "extracting template..." }));
|
|
97
|
+
try {
|
|
98
|
+
const zip = new AdmZip(zipPath);
|
|
99
|
+
zip.extractAllTo(extractDir, true);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
removeLastLog();
|
|
103
|
+
}
|
|
104
|
+
const repoRoot = path.join(extractDir, `${TEMPLATES_REPO_NAME}-${TEMPLATES_REPO_BRANCH}`);
|
|
105
|
+
const templateDir = path.join(repoRoot, templateName);
|
|
106
|
+
if (!(yield fse.pathExists(templateDir))) {
|
|
107
|
+
throw new Error(`Template directory "${templateName}" not found in the downloaded archive.`);
|
|
108
|
+
}
|
|
109
|
+
yield fse.copy(templateDir, installDir, { overwrite: true });
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
yield fse.remove(zipPath).catch(() => { });
|
|
113
|
+
yield fse.remove(extractDir).catch(() => { });
|
|
114
|
+
}
|
|
115
|
+
const docsJsonPath = path.join(installDir, 'docs.json');
|
|
116
|
+
if (yield fse.pathExists(docsJsonPath)) {
|
|
117
|
+
const docsConfig = yield fse.readJson(docsJsonPath);
|
|
118
|
+
if (projectName) {
|
|
119
|
+
docsConfig.name = projectName;
|
|
120
|
+
}
|
|
121
|
+
if (theme) {
|
|
122
|
+
docsConfig.theme = theme;
|
|
123
|
+
}
|
|
124
|
+
yield fse.writeJson(docsJsonPath, docsConfig, { spaces: 2 });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|