@patternfly/patternfly-cli 1.0.2 ā 1.0.4
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/.github/workflows/build.yml +1 -1
- package/.github/workflows/lint.yml +1 -1
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/test.yml +1 -1
- package/LICENSE +1 -1
- package/dist/cli.js +34 -145
- package/dist/cli.js.map +1 -1
- package/dist/create.d.ts +10 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +151 -0
- package/dist/create.js.map +1 -0
- package/dist/gh-pages.d.ts +14 -0
- package/dist/gh-pages.d.ts.map +1 -0
- package/dist/gh-pages.js +139 -0
- package/dist/gh-pages.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/cli.test.ts +0 -23
- package/src/__tests__/create.test.ts +306 -0
- package/src/__tests__/gh-pages.test.ts +283 -0
- package/src/cli.ts +33 -176
- package/src/create.ts +187 -0
- package/src/gh-pages.ts +170 -0
package/src/cli.ts
CHANGED
|
@@ -2,26 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import { program } from 'commander';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
|
-
import inquirer from 'inquirer';
|
|
6
5
|
import fs from 'fs-extra';
|
|
7
6
|
import path from 'path';
|
|
8
7
|
import { defaultTemplates } from './templates.js';
|
|
9
8
|
import { mergeTemplates } from './template-loader.js';
|
|
10
9
|
import { offerAndCreateGitHubRepo } from './github.js';
|
|
10
|
+
import { runCreate } from './create.js';
|
|
11
11
|
import { runSave } from './save.js';
|
|
12
12
|
import { runLoad } from './load.js';
|
|
13
|
-
|
|
14
|
-
/** Project data provided by the user */
|
|
15
|
-
type ProjectData = {
|
|
16
|
-
/** Project name */
|
|
17
|
-
name: string,
|
|
18
|
-
/** Project version */
|
|
19
|
-
version: string,
|
|
20
|
-
/** Project description */
|
|
21
|
-
description: string,
|
|
22
|
-
/** Project author */
|
|
23
|
-
author: string
|
|
24
|
-
}
|
|
13
|
+
import { runDeployToGitHubPages } from './gh-pages.js';
|
|
25
14
|
|
|
26
15
|
/** Command to create a new project */
|
|
27
16
|
program
|
|
@@ -33,172 +22,14 @@ program
|
|
|
33
22
|
.option('-t, --template-file <path>', 'Path to a JSON file with custom templates (same format as built-in)')
|
|
34
23
|
.option('--ssh', 'Use SSH URL for cloning the template repository')
|
|
35
24
|
.action(async (projectDirectory, templateName, options) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const projectDirAnswer = await inquirer.prompt([
|
|
41
|
-
{
|
|
42
|
-
type: 'input',
|
|
43
|
-
name: 'projectDirectory',
|
|
44
|
-
message: 'Please provide the directory where you want to create the project?',
|
|
45
|
-
default: 'my-app',
|
|
46
|
-
},
|
|
47
|
-
]);
|
|
48
|
-
projectDirectory = projectDirAnswer.projectDirectory;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// If template name is not provided, show available templates and let user select
|
|
52
|
-
if (!templateName) {
|
|
53
|
-
console.log('\nš Available templates:\n');
|
|
54
|
-
templatesToUse.forEach(t => {
|
|
55
|
-
console.log(` ${t.name.padEnd(12)} - ${t.description}`);
|
|
56
|
-
});
|
|
57
|
-
console.log('');
|
|
58
|
-
|
|
59
|
-
const templateQuestion = [
|
|
60
|
-
{
|
|
61
|
-
type: 'list',
|
|
62
|
-
name: 'templateName',
|
|
63
|
-
message: 'Select a template:',
|
|
64
|
-
choices: templatesToUse.map(t => ({
|
|
65
|
-
name: `${t.name} - ${t.description}`,
|
|
66
|
-
value: t.name
|
|
67
|
-
}))
|
|
68
|
-
}
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
const templateAnswer = await inquirer.prompt(templateQuestion);
|
|
72
|
-
templateName = templateAnswer.templateName;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Look up the template by name
|
|
76
|
-
const template = templatesToUse.find(t => t.name === templateName);
|
|
77
|
-
if (!template) {
|
|
78
|
-
console.error(`ā Template "${templateName}" not found.\n`);
|
|
79
|
-
console.log('š Available templates:\n');
|
|
80
|
-
templatesToUse.forEach(t => {
|
|
81
|
-
console.log(` ${t.name.padEnd(12)} - ${t.description}`);
|
|
25
|
+
try {
|
|
26
|
+
await runCreate(projectDirectory, templateName, {
|
|
27
|
+
templateFile: options?.templateFile,
|
|
28
|
+
ssh: options?.ssh,
|
|
82
29
|
});
|
|
83
|
-
|
|
30
|
+
} catch {
|
|
84
31
|
process.exit(1);
|
|
85
32
|
}
|
|
86
|
-
|
|
87
|
-
// If --ssh was not passed, prompt whether to use SSH
|
|
88
|
-
let useSSH = options?.ssh;
|
|
89
|
-
if (useSSH === undefined && template.repoSSH) {
|
|
90
|
-
const sshAnswer = await inquirer.prompt([
|
|
91
|
-
{
|
|
92
|
-
type: 'confirm',
|
|
93
|
-
name: 'useSSH',
|
|
94
|
-
message: 'Use SSH URL for cloning?',
|
|
95
|
-
default: false,
|
|
96
|
-
},
|
|
97
|
-
]);
|
|
98
|
-
useSSH = sshAnswer.useSSH;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const templateRepoUrl = useSSH && template.repoSSH ? template.repoSSH : template.repo;
|
|
102
|
-
|
|
103
|
-
// Define the full path for the new project
|
|
104
|
-
const projectPath = path.resolve(projectDirectory);
|
|
105
|
-
console.log(`Cloning template "${templateName}" from ${templateRepoUrl} into ${projectPath}...`);
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
|
|
109
|
-
// Clone the repository
|
|
110
|
-
const cloneArgs = ['clone'];
|
|
111
|
-
if (template.options && Array.isArray(template.options)) {
|
|
112
|
-
cloneArgs.push(...template.options);
|
|
113
|
-
}
|
|
114
|
-
cloneArgs.push(templateRepoUrl, projectPath);
|
|
115
|
-
await execa('git', cloneArgs, { stdio: 'inherit' });
|
|
116
|
-
console.log('ā
Template cloned successfully.');
|
|
117
|
-
|
|
118
|
-
// Remove the .git folder from the *new* project
|
|
119
|
-
await fs.remove(path.join(projectPath, '.git'));
|
|
120
|
-
console.log('š§¹ Cleaned up template .git directory.');
|
|
121
|
-
|
|
122
|
-
// Ask user for customization details
|
|
123
|
-
const questions = [
|
|
124
|
-
{
|
|
125
|
-
type: 'input',
|
|
126
|
-
name: 'name',
|
|
127
|
-
message: 'What is the project name?',
|
|
128
|
-
default: path.basename(projectPath),
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
type: 'input',
|
|
132
|
-
name: 'version',
|
|
133
|
-
message: 'What version number would you like to use?',
|
|
134
|
-
default: '1.0.0',
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
type: 'input',
|
|
138
|
-
name: 'description',
|
|
139
|
-
message: 'What is the project description?',
|
|
140
|
-
default: '',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
type: 'input',
|
|
144
|
-
name: 'author',
|
|
145
|
-
message: 'Who is the author of the project?',
|
|
146
|
-
default: '',
|
|
147
|
-
},
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
const answers: ProjectData = await inquirer.prompt(questions);
|
|
151
|
-
|
|
152
|
-
// Update the package.json in the new project
|
|
153
|
-
const pkgJsonPath = path.join(projectPath, 'package.json');
|
|
154
|
-
|
|
155
|
-
if (await fs.pathExists(pkgJsonPath)) {
|
|
156
|
-
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
157
|
-
|
|
158
|
-
// Overwrite fields with user's answers
|
|
159
|
-
pkgJson.name = answers.name;
|
|
160
|
-
pkgJson.version = answers.version;
|
|
161
|
-
pkgJson.description = answers.description;
|
|
162
|
-
pkgJson.author = answers.author;
|
|
163
|
-
|
|
164
|
-
// Write the updated package.json back
|
|
165
|
-
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
166
|
-
console.log('š Customized package.json.');
|
|
167
|
-
} else {
|
|
168
|
-
console.log('ā¹ļø No package.json found in template, skipping customization.');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const packageManager = template.packageManager || "npm";
|
|
172
|
-
// Install dependencies
|
|
173
|
-
console.log('š¦ Installing dependencies... (This may take a moment)');
|
|
174
|
-
await execa(packageManager, ['install'], { cwd: projectPath, stdio: 'inherit' });
|
|
175
|
-
console.log('ā
Dependencies installed.');
|
|
176
|
-
|
|
177
|
-
// Optional: Create GitHub repository
|
|
178
|
-
await offerAndCreateGitHubRepo(projectPath);
|
|
179
|
-
|
|
180
|
-
// Let the user know the project was created successfully
|
|
181
|
-
console.log('\n⨠Project created successfully! āØ\n');
|
|
182
|
-
console.log(`To get started:`);
|
|
183
|
-
console.log(` cd ${projectDirectory}`);
|
|
184
|
-
console.log(' Happy coding! š');
|
|
185
|
-
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.error('ā An error occurred:');
|
|
188
|
-
if (error instanceof Error) {
|
|
189
|
-
console.error(error.message);
|
|
190
|
-
} else if (error && typeof error === 'object' && 'stderr' in error) {
|
|
191
|
-
console.error((error as { stderr?: string }).stderr || String(error));
|
|
192
|
-
} else {
|
|
193
|
-
console.error(String(error));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Clean up the created directory if an error occurred
|
|
197
|
-
if (await fs.pathExists(projectPath)) {
|
|
198
|
-
await fs.remove(projectPath);
|
|
199
|
-
console.log('š§¹ Cleaned up failed project directory.');
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
33
|
});
|
|
203
34
|
|
|
204
35
|
/** Command to initialize a project and optionally create a GitHub repository */
|
|
@@ -304,4 +135,30 @@ program
|
|
|
304
135
|
}
|
|
305
136
|
});
|
|
306
137
|
|
|
138
|
+
/** Command to deploy the React app to GitHub Pages */
|
|
139
|
+
program
|
|
140
|
+
.command('deploy')
|
|
141
|
+
.description('Build the app and deploy it to GitHub Pages (uses gh-pages branch)')
|
|
142
|
+
.argument('[path]', 'Path to the project (defaults to current directory)')
|
|
143
|
+
.option('-d, --dist-dir <dir>', 'Build output directory to deploy', 'dist')
|
|
144
|
+
.option('--no-build', 'Skip running the build step (deploy existing output only)')
|
|
145
|
+
.option('-b, --branch <branch>', 'Git branch to deploy to', 'gh-pages')
|
|
146
|
+
.action(async (projectPath, options) => {
|
|
147
|
+
const cwd = projectPath ? path.resolve(projectPath) : process.cwd();
|
|
148
|
+
try {
|
|
149
|
+
await runDeployToGitHubPages(cwd, {
|
|
150
|
+
distDir: options.distDir,
|
|
151
|
+
skipBuild: options.build === false,
|
|
152
|
+
branch: options.branch,
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof Error) {
|
|
156
|
+
console.error(`\nā ${error.message}\n`);
|
|
157
|
+
} else {
|
|
158
|
+
console.error(error);
|
|
159
|
+
}
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
307
164
|
program.parse(process.argv);
|
package/src/create.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { defaultTemplates } from './templates.js';
|
|
6
|
+
import { mergeTemplates } from './template-loader.js';
|
|
7
|
+
import { offerAndCreateGitHubRepo } from './github.js';
|
|
8
|
+
|
|
9
|
+
/** Project data provided by the user */
|
|
10
|
+
type ProjectData = {
|
|
11
|
+
/** Project name */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Project version */
|
|
14
|
+
version: string;
|
|
15
|
+
/** Project description */
|
|
16
|
+
description: string;
|
|
17
|
+
/** Project author */
|
|
18
|
+
author: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RunCreateOptions = {
|
|
22
|
+
templateFile?: string;
|
|
23
|
+
ssh?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Runs the create flow: clone template, customize package.json, install deps, optionally create GitHub repo.
|
|
28
|
+
* Throws on fatal errors. Caller should catch and process.exit(1).
|
|
29
|
+
*/
|
|
30
|
+
export async function runCreate(
|
|
31
|
+
projectDirectory: string | undefined,
|
|
32
|
+
templateName: string | undefined,
|
|
33
|
+
options?: RunCreateOptions
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
const templatesToUse = mergeTemplates(defaultTemplates, options?.templateFile);
|
|
36
|
+
|
|
37
|
+
// If project directory is not provided, prompt for it
|
|
38
|
+
if (!projectDirectory) {
|
|
39
|
+
const projectDirAnswer = await inquirer.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: 'input',
|
|
42
|
+
name: 'projectDirectory',
|
|
43
|
+
message: 'Please provide the directory where you want to create the project?',
|
|
44
|
+
default: 'my-app',
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
projectDirectory = projectDirAnswer.projectDirectory;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If template name is not provided, show available templates and let user select
|
|
51
|
+
if (!templateName) {
|
|
52
|
+
console.log('\nš Available templates:\n');
|
|
53
|
+
templatesToUse.forEach(t => {
|
|
54
|
+
console.log(` ${t.name.padEnd(12)} - ${t.description}`);
|
|
55
|
+
});
|
|
56
|
+
console.log('');
|
|
57
|
+
|
|
58
|
+
const templateQuestion = [
|
|
59
|
+
{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'templateName',
|
|
62
|
+
message: 'Select a template:',
|
|
63
|
+
choices: templatesToUse.map(t => ({
|
|
64
|
+
name: `${t.name} - ${t.description}`,
|
|
65
|
+
value: t.name,
|
|
66
|
+
})),
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const templateAnswer = await inquirer.prompt(templateQuestion);
|
|
71
|
+
templateName = templateAnswer.templateName;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Look up the template by name
|
|
75
|
+
const template = templatesToUse.find(t => t.name === templateName);
|
|
76
|
+
if (!template) {
|
|
77
|
+
console.error(`ā Template "${templateName}" not found.\n`);
|
|
78
|
+
console.log('š Available templates:\n');
|
|
79
|
+
templatesToUse.forEach(t => {
|
|
80
|
+
console.log(` ${t.name.padEnd(12)} - ${t.description}`);
|
|
81
|
+
});
|
|
82
|
+
console.log('');
|
|
83
|
+
throw new Error(`Template "${templateName}" not found`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const templateRepoUrl = options?.ssh && template.repoSSH ? template.repoSSH : template.repo;
|
|
87
|
+
|
|
88
|
+
// Define the full path for the new project (projectDirectory is set above via arg or prompt)
|
|
89
|
+
const dir = projectDirectory ?? 'my-app';
|
|
90
|
+
const projectPath = path.resolve(dir);
|
|
91
|
+
console.log(`Cloning template "${templateName}" from ${templateRepoUrl} into ${projectPath}...`);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Clone the repository
|
|
95
|
+
const cloneArgs = ['clone'];
|
|
96
|
+
if (template.options && Array.isArray(template.options)) {
|
|
97
|
+
cloneArgs.push(...template.options);
|
|
98
|
+
}
|
|
99
|
+
cloneArgs.push(templateRepoUrl, projectPath);
|
|
100
|
+
await execa('git', cloneArgs, { stdio: 'inherit' });
|
|
101
|
+
console.log('ā
Template cloned successfully.');
|
|
102
|
+
|
|
103
|
+
// Remove the .git folder from the *new* project
|
|
104
|
+
await fs.remove(path.join(projectPath, '.git'));
|
|
105
|
+
console.log('š§¹ Cleaned up template .git directory.');
|
|
106
|
+
|
|
107
|
+
// Ask user for customization details
|
|
108
|
+
const questions = [
|
|
109
|
+
{
|
|
110
|
+
type: 'input',
|
|
111
|
+
name: 'name',
|
|
112
|
+
message: 'What is the project name?',
|
|
113
|
+
default: path.basename(projectPath),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: 'input',
|
|
117
|
+
name: 'version',
|
|
118
|
+
message: 'What version number would you like to use?',
|
|
119
|
+
default: '1.0.0',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'input',
|
|
123
|
+
name: 'description',
|
|
124
|
+
message: 'What is the project description?',
|
|
125
|
+
default: '',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'input',
|
|
129
|
+
name: 'author',
|
|
130
|
+
message: 'Who is the author of the project?',
|
|
131
|
+
default: '',
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const answers: ProjectData = await inquirer.prompt(questions);
|
|
136
|
+
|
|
137
|
+
// Update the package.json in the new project
|
|
138
|
+
const pkgJsonPath = path.join(projectPath, 'package.json');
|
|
139
|
+
|
|
140
|
+
if (await fs.pathExists(pkgJsonPath)) {
|
|
141
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
142
|
+
|
|
143
|
+
// Overwrite fields with user's answers
|
|
144
|
+
pkgJson.name = answers.name;
|
|
145
|
+
pkgJson.version = answers.version;
|
|
146
|
+
pkgJson.description = answers.description;
|
|
147
|
+
pkgJson.author = answers.author;
|
|
148
|
+
|
|
149
|
+
// Write the updated package.json back
|
|
150
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
151
|
+
console.log('š Customized package.json.');
|
|
152
|
+
} else {
|
|
153
|
+
console.log('ā¹ļø No package.json found in template, skipping customization.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const packageManager = template.packageManager || 'npm';
|
|
157
|
+
// Install dependencies
|
|
158
|
+
console.log('š¦ Installing dependencies... (This may take a moment)');
|
|
159
|
+
await execa(packageManager, ['install'], { cwd: projectPath, stdio: 'inherit' });
|
|
160
|
+
console.log('ā
Dependencies installed.');
|
|
161
|
+
|
|
162
|
+
// Optional: Create GitHub repository
|
|
163
|
+
await offerAndCreateGitHubRepo(projectPath);
|
|
164
|
+
|
|
165
|
+
// Let the user know the project was created successfully
|
|
166
|
+
console.log('\n⨠Project created successfully! āØ\n');
|
|
167
|
+
console.log(`To get started:`);
|
|
168
|
+
console.log(` cd ${dir}`);
|
|
169
|
+
console.log(' Happy coding! š');
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('ā An error occurred:');
|
|
172
|
+
if (error instanceof Error) {
|
|
173
|
+
console.error(error.message);
|
|
174
|
+
} else if (error && typeof error === 'object' && 'stderr' in error) {
|
|
175
|
+
console.error((error as { stderr?: string }).stderr || String(error));
|
|
176
|
+
} else {
|
|
177
|
+
console.error(String(error));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Clean up the created directory if an error occurred
|
|
181
|
+
if (await fs.pathExists(projectPath)) {
|
|
182
|
+
await fs.remove(projectPath);
|
|
183
|
+
console.log('š§¹ Cleaned up failed project directory.');
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
package/src/gh-pages.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import ghPages from 'gh-pages';
|
|
5
|
+
import { checkGhAuth } from './github.js';
|
|
6
|
+
|
|
7
|
+
export type DeployOptions = {
|
|
8
|
+
/** Build output directory to deploy (e.g. dist, build) */
|
|
9
|
+
distDir: string;
|
|
10
|
+
/** Skip running the build step */
|
|
11
|
+
skipBuild: boolean;
|
|
12
|
+
/** Branch to push to (default gh-pages) */
|
|
13
|
+
branch: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_DIST_DIR = 'dist';
|
|
17
|
+
const DEFAULT_BRANCH = 'gh-pages';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse owner and repo name from a Git remote URL.
|
|
21
|
+
* Supports https://github.com/owner/repo, https://github.com/owner/repo.git, git@github.com:owner/repo.git
|
|
22
|
+
*/
|
|
23
|
+
function parseRepoFromUrl(repoUrl: string): { owner: string; repo: string } | null {
|
|
24
|
+
const trimmed = repoUrl.trim().replace(/\.git$/, '');
|
|
25
|
+
// git@github.com:owner/repo or https://github.com/owner/repo
|
|
26
|
+
const sshMatch = trimmed.match(/git@github\.com:([^/]+)\/([^/]+)/);
|
|
27
|
+
if (sshMatch?.[1] && sshMatch[2]) {
|
|
28
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
29
|
+
}
|
|
30
|
+
const httpsMatch = trimmed.match(/github\.com[/:]([^/]+)\/([^/#?]+)/);
|
|
31
|
+
if (httpsMatch?.[1] && httpsMatch[2]) {
|
|
32
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ensure GitHub Pages is enabled for the repository, configured to use the given branch.
|
|
39
|
+
* Uses the GitHub API via `gh` CLI. No-op if gh is not authenticated or on failure.
|
|
40
|
+
*/
|
|
41
|
+
async function ensurePagesEnabled(owner: string, repo: string, branch: string): Promise<void> {
|
|
42
|
+
const auth = await checkGhAuth();
|
|
43
|
+
if (!auth.ok) return;
|
|
44
|
+
|
|
45
|
+
const body = JSON.stringify({ source: { branch, path: '/' } });
|
|
46
|
+
try {
|
|
47
|
+
const getResult = await execa('gh', ['api', `repos/${owner}/${repo}/pages`, '--jq', '.source.branch'], {
|
|
48
|
+
reject: false,
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
});
|
|
51
|
+
if (getResult.exitCode === 0) {
|
|
52
|
+
const currentBranch = getResult.stdout?.trim();
|
|
53
|
+
if (currentBranch === branch) return;
|
|
54
|
+
await execa('gh', [
|
|
55
|
+
'api',
|
|
56
|
+
'-X',
|
|
57
|
+
'PUT',
|
|
58
|
+
`repos/${owner}/${repo}/pages`,
|
|
59
|
+
'--input',
|
|
60
|
+
'-',
|
|
61
|
+
], { input: body });
|
|
62
|
+
console.log(` GitHub Pages source updated to branch "${branch}".`);
|
|
63
|
+
} else {
|
|
64
|
+
await execa('gh', [
|
|
65
|
+
'api',
|
|
66
|
+
'-X',
|
|
67
|
+
'POST',
|
|
68
|
+
`repos/${owner}/${repo}/pages`,
|
|
69
|
+
'--input',
|
|
70
|
+
'-',
|
|
71
|
+
], { input: body });
|
|
72
|
+
console.log(` GitHub Pages enabled (source: branch "${branch}").`);
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Best-effort: continue without enabling; user can enable manually
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Detect package manager from lock files.
|
|
81
|
+
*/
|
|
82
|
+
async function getPackageManager(cwd: string): Promise<'yarn' | 'pnpm' | 'npm'> {
|
|
83
|
+
if (await fs.pathExists(path.join(cwd, 'yarn.lock'))) return 'yarn';
|
|
84
|
+
if (await fs.pathExists(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
85
|
+
return 'npm';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Run build script in the project (npm run build / yarn build / pnpm build).
|
|
90
|
+
*/
|
|
91
|
+
async function runBuild(cwd: string): Promise<void> {
|
|
92
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
93
|
+
const pkg = await fs.readJson(pkgPath);
|
|
94
|
+
const scripts = (pkg.scripts as Record<string, string>) || {};
|
|
95
|
+
if (!scripts['build']) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'No "build" script found in package.json. Add a build script or use --no-build and deploy an existing folder with -d/--dist-dir.'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const pm = await getPackageManager(cwd);
|
|
102
|
+
const runCmd = pm === 'npm' ? 'npm' : pm === 'yarn' ? 'yarn' : 'pnpm';
|
|
103
|
+
const args = pm === 'npm' ? ['run', 'build'] : ['build'];
|
|
104
|
+
console.log(`š¦ Running build (${runCmd} ${args.join(' ')})...`);
|
|
105
|
+
await execa(runCmd, args, { cwd, stdio: 'inherit' });
|
|
106
|
+
console.log('ā
Build completed.\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Deploy the built app to GitHub Pages using the gh-pages package.
|
|
111
|
+
* Builds the project first unless skipBuild is true, then publishes distDir to the gh-pages branch.
|
|
112
|
+
*/
|
|
113
|
+
export async function runDeployToGitHubPages(
|
|
114
|
+
projectPath: string,
|
|
115
|
+
options: Partial<DeployOptions> = {}
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
const distDir = options.distDir ?? DEFAULT_DIST_DIR;
|
|
118
|
+
const skipBuild = options.skipBuild ?? false;
|
|
119
|
+
const branch = options.branch ?? DEFAULT_BRANCH;
|
|
120
|
+
|
|
121
|
+
const cwd = path.resolve(projectPath);
|
|
122
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
123
|
+
|
|
124
|
+
if (!(await fs.pathExists(pkgPath))) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'No package.json found in this directory. Run this command from your project root (or pass the project path).'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let repoUrl: string;
|
|
131
|
+
try {
|
|
132
|
+
const { stdout } = await execa('git', ['remote', 'get-url', 'origin'], {
|
|
133
|
+
cwd,
|
|
134
|
+
reject: true,
|
|
135
|
+
});
|
|
136
|
+
repoUrl = stdout.trim();
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'Please save your changes first, before deploying to GitHub Pages.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!skipBuild) {
|
|
144
|
+
await runBuild(cwd);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const absoluteDist = path.join(cwd, distDir);
|
|
148
|
+
if (!(await fs.pathExists(absoluteDist))) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Build output directory "${distDir}" does not exist. Run a build first or specify the correct directory with -d/--dist-dir.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const parsed = parseRepoFromUrl(repoUrl);
|
|
155
|
+
if (parsed) {
|
|
156
|
+
await ensurePagesEnabled(parsed.owner, parsed.repo, branch);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(`š Deploying "${distDir}" to GitHub Pages (branch: ${branch})...`);
|
|
160
|
+
await new Promise<void>((resolve, reject) => {
|
|
161
|
+
ghPages.publish(
|
|
162
|
+
absoluteDist,
|
|
163
|
+
{ branch, repo: repoUrl },
|
|
164
|
+
(err) => (err ? reject(err) : resolve())
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
console.log('\nā
Deployed to GitHub Pages.');
|
|
168
|
+
console.log(' Enable GitHub Pages in your repo: Settings ā Pages ā Source: branch "' + branch + '".');
|
|
169
|
+
console.log(' If the site is at username.github.io/<repo-name>, set your app\'s base path (e.g. base: \'/<repo-name>/\' in Vite).\n');
|
|
170
|
+
}
|