create-nx-workspace
Version:
213 lines (210 loc) • 8.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitHubPushSkippedError = exports.VcsPushStatus = void 0;
exports.checkGitVersion = checkGitVersion;
exports.initializeGitRepo = initializeGitRepo;
exports.pushToGitHub = pushToGitHub;
const default_base_1 = require("./default-base");
const output_1 = require("../output");
const child_process_utils_1 = require("../child-process-utils");
const enquirer = require("enquirer");
var VcsPushStatus;
(function (VcsPushStatus) {
VcsPushStatus["PushedToVcs"] = "PushedToVcs";
VcsPushStatus["OptedOutOfPushingToVcs"] = "OptedOutOfPushingToVcs";
VcsPushStatus["FailedToPushToVcs"] = "FailedToPushToVcs";
VcsPushStatus["SkippedGit"] = "SkippedGit";
})(VcsPushStatus || (exports.VcsPushStatus = VcsPushStatus = {}));
class GitHubPushSkippedError extends Error {
constructor(message) {
super(message);
this.title = 'Push your workspace';
this.name = 'GitHubPushSkippedError';
}
}
exports.GitHubPushSkippedError = GitHubPushSkippedError;
async function checkGitVersion() {
try {
const result = await (0, child_process_utils_1.execAndWait)('git --version', process.cwd());
const gitVersionOutput = result.stdout.trim();
return gitVersionOutput.match(/[0-9]+\.[0-9]+\.+[0-9]+/)?.[0];
}
catch {
return null;
}
}
async function getGitHubUsername(directory) {
const result = await (0, child_process_utils_1.execAndWait)('gh api user --jq .login', directory);
const username = result.stdout.trim();
if (!username) {
throw new GitHubPushSkippedError('GitHub CLI is not authenticated');
}
return username;
}
async function getUserRepositories(directory) {
try {
const allRepos = new Set();
// Get user's personal repos and organizations concurrently
const [userRepos, orgsResult] = await Promise.all([
(0, child_process_utils_1.execAndWait)('gh repo list --limit 1000 --json nameWithOwner --jq ".[].nameWithOwner"', directory),
(0, child_process_utils_1.execAndWait)('gh api user/orgs --jq ".[].login"', directory),
]);
// Add user's personal repos
userRepos.stdout
.trim()
.split('\n')
.filter((repo) => repo.length > 0)
.forEach((repo) => allRepos.add(repo));
// Parse organizations
const orgs = orgsResult.stdout
.trim()
.split('\n')
.filter((org) => org.length > 0);
// Get repos from all organizations concurrently
const orgRepoPromises = orgs.map(async (org) => {
try {
const orgRepos = await (0, child_process_utils_1.execAndWait)(`gh repo list ${org} --limit 1000 --json nameWithOwner --jq ".[].nameWithOwner"`, directory);
return orgRepos.stdout
.trim()
.split('\n')
.filter((repo) => repo.length > 0);
}
catch {
// Return empty array if we can't access org repos
return [];
}
});
const orgRepoResults = await Promise.all(orgRepoPromises);
// Add all org repos to the set
orgRepoResults.flat().forEach((repo) => allRepos.add(repo));
return allRepos;
}
catch {
// If we can't fetch repos, return empty set to skip validation
return new Set();
}
}
async function initializeGitRepo(directory, options) {
// Set git commit environment variables if provided
if (options.commit?.name) {
process.env.GIT_AUTHOR_NAME = options.commit.name;
process.env.GIT_COMMITTER_NAME = options.commit.name;
}
if (options.commit?.email) {
process.env.GIT_AUTHOR_EMAIL = options.commit.email;
process.env.GIT_COMMITTER_EMAIL = options.commit.email;
}
const gitVersion = await checkGitVersion();
if (!gitVersion) {
return;
}
const insideRepo = await (0, child_process_utils_1.execAndWait)('git rev-parse --is-inside-work-tree', directory).then(() => true, () => false);
if (insideRepo) {
output_1.output.log({
title: 'Directory is already under version control. Skipping initialization of git.',
});
return;
}
const defaultBase = options.defaultBase || (0, default_base_1.deduceDefaultBase)();
const [gitMajor, gitMinor] = gitVersion.split('.');
if (+gitMajor > 2 || (+gitMajor === 2 && +gitMinor >= 28)) {
await (0, child_process_utils_1.execAndWait)(`git init -b ${defaultBase}`, directory);
}
else {
await (0, child_process_utils_1.execAndWait)('git init', directory);
await (0, child_process_utils_1.execAndWait)(`git checkout -b ${defaultBase}`, directory); // Git < 2.28 doesn't support -b on git init.
}
await (0, child_process_utils_1.execAndWait)('git add .', directory);
if (options.commit) {
let message = `${options.commit.message}` || 'initial commit';
if (options.connectUrl) {
message = `${message}
To connect your workspace to Nx Cloud, push your repository
to your git hosting provider and go to the following URL:
${options.connectUrl}
`;
}
await (0, child_process_utils_1.execAndWait)(`git commit -m "${message}"`, directory);
}
}
async function pushToGitHub(directory, options) {
try {
if (process.env['NX_SKIP_GH_PUSH'] === 'true') {
throw new GitHubPushSkippedError('NX_SKIP_GH_PUSH is true so skipping GitHub push.');
}
const username = await getGitHubUsername(directory);
// First prompt: Ask if they want to push to GitHub
const { push } = await enquirer.prompt([
{
name: 'push',
message: 'Would you like to push this workspace to Github?',
type: 'autocomplete',
choices: [{ name: 'Yes' }, { name: 'No' }],
initial: 0,
},
]);
if (push !== 'Yes') {
return VcsPushStatus.OptedOutOfPushingToVcs;
}
// Preload existing repositories for validation
const existingRepos = await getUserRepositories(directory);
// Create default repository name using the username we already have
const defaultRepo = `${username}/${options.name}`;
// Second prompt: Ask where to create the repository with validation
const { repoName } = await enquirer.prompt([
{
name: 'repoName',
message: 'Repository name (format: username/repo-name):',
type: 'input',
initial: defaultRepo,
validate: async (value) => {
if (!value.includes('/')) {
return 'Repository name must be in format: username/repo-name';
}
if (existingRepos.has(value)) {
return `Repository '${value}' already exists. Please choose a different name.`;
}
return true;
},
},
]);
// Create GitHub repository using gh CLI from the workspace directory
// This will automatically add remote origin and push the current branch
await (0, child_process_utils_1.spawnAndWait)('gh', [
'repo',
'create',
repoName,
'--private',
'--push',
'--source',
directory,
], directory);
// Get the actual repository URL from GitHub CLI (it could be different from github.com)
const repoResult = await (0, child_process_utils_1.execAndWait)('gh repo view --json url -q .url', directory);
const repoUrl = repoResult.stdout.trim();
output_1.output.success({
title: `Successfully pushed to GitHub repository: ${repoUrl}`,
});
return VcsPushStatus.PushedToVcs;
}
catch (e) {
const isVerbose = options.verbose || process.env.NX_VERBOSE_LOGGING === 'true';
const errorMessage = e instanceof Error ? e.message : String(e);
// Error code 127 means gh wasn't installed
const title = e instanceof GitHubPushSkippedError || e?.code === 127
? 'Push your workspace to GitHub.'
: 'Failed to push workspace to GitHub.';
const createRepoUrl = `https://github.com/new?name=${encodeURIComponent(options.name)}`;
output_1.output.log({
title,
bodyLines: isVerbose
? [
`Please create a repo at ${createRepoUrl} and push this workspace.`,
'Error details:',
errorMessage,
]
: [`Please create a repo at ${createRepoUrl} and push this workspace.`],
});
return VcsPushStatus.FailedToPushToVcs;
}
}