fabrica-e-commerce 0.1.8 → 0.1.9
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/package.json +1 -1
- package/src/github.js +72 -14
package/package.json
CHANGED
package/src/github.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { commandExists, runCommand, runCommandCapture } from './system.js';
|
|
2
2
|
import { STORE_REPO } from './config.js';
|
|
3
3
|
import { choose } from './prompt.js';
|
|
4
|
-
import { kv, section, spinner } from './ui.js';
|
|
4
|
+
import { dimOrange, kv, section, spinner } from './ui.js';
|
|
5
5
|
|
|
6
6
|
export async function isGithubCliInstalled() {
|
|
7
7
|
return commandExists('gh');
|
|
@@ -76,6 +76,31 @@ async function waitForGithubRepo(owner, repoName) {
|
|
|
76
76
|
if (await githubRepoExists(owner, repoName)) return;
|
|
77
77
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
78
78
|
}
|
|
79
|
+
throw new Error(`Timed out waiting for ${owner}/${repoName} to appear on GitHub. GitHub forks happen asynchronously and can occasionally lag — try again in a minute, or check ${owner} on GitHub directly.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sourceOwner() {
|
|
83
|
+
return sourceRepoFullName().split('/')[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function isMarkedAsTemplate(repoFullName) {
|
|
87
|
+
const result = await runCommandCapture('gh', ['api', `repos/${repoFullName}`, '-q', '.is_template']);
|
|
88
|
+
return result.code === 0 && result.stdout.trim() === 'true';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function createFromTemplate(spin, owner, repoName, source) {
|
|
92
|
+
if (!(await isMarkedAsTemplate(source))) {
|
|
93
|
+
spin.fail(`${source} is not marked as a "Template repository" on GitHub`);
|
|
94
|
+
throw new Error(`Creating ${owner}/${repoName} from ${source} requires ${source} to be a template repository. Go to https://github.com/${source}/settings, check "Template repository", then run "fabrica build" again.`);
|
|
95
|
+
}
|
|
96
|
+
const template = await runCommandCapture('gh', ['repo', 'create', `${owner}/${repoName}`, '--private', '--template', source, '--include-all-branches']);
|
|
97
|
+
if (template.code === 0) {
|
|
98
|
+
await waitForGithubRepo(owner, repoName);
|
|
99
|
+
spin.succeed(`Created ${owner}/${repoName} from ${source}`);
|
|
100
|
+
return repoName;
|
|
101
|
+
}
|
|
102
|
+
spin.fail('Could not create GitHub repo from source repo');
|
|
103
|
+
throw new Error(template.stderr || 'GitHub repo creation from source failed');
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
async function forkSourceRepo(owner, repoName) {
|
|
@@ -87,7 +112,18 @@ async function forkSourceRepo(owner, repoName) {
|
|
|
87
112
|
{ name: 'Stop so I can choose a different Vercel project/repo name', value: 'stop' }
|
|
88
113
|
]);
|
|
89
114
|
if (action === 'stop') throw new Error(`GitHub repo ${owner}/${repoName} already exists. Re-run build with a different project name.`);
|
|
90
|
-
return;
|
|
115
|
+
return repoName;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// GitHub will not let an account fork a repository that account itself owns
|
|
119
|
+
// — not even under a different name. The Fork API still returns success
|
|
120
|
+
// (202 Accepted, since forking is asynchronous) and only fails the actual
|
|
121
|
+
// creation in the background, which previously showed up as a false "✓
|
|
122
|
+
// Forked" message followed by a 404. Skip the Fork API entirely here and go
|
|
123
|
+
// straight to template-based creation, which has no such restriction.
|
|
124
|
+
if (owner.toLowerCase() === sourceOwner().toLowerCase()) {
|
|
125
|
+
const spin = spinner(`Creating GitHub repo ${owner}/${repoName} from template ${source} (can't fork into the same account that owns ${source})`);
|
|
126
|
+
return createFromTemplate(spin, owner, repoName, source);
|
|
91
127
|
}
|
|
92
128
|
|
|
93
129
|
const spin = spinner(`Creating GitHub repo ${owner}/${repoName} from ${source}`);
|
|
@@ -95,27 +131,49 @@ async function forkSourceRepo(owner, repoName) {
|
|
|
95
131
|
input: JSON.stringify({ name: repoName, default_branch_only: false })
|
|
96
132
|
});
|
|
97
133
|
if (fork.code === 0) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
// The fork API can also return success while pointing at a DIFFERENT repo
|
|
135
|
+
// than the one we asked for — e.g. if this account already has a fork of
|
|
136
|
+
// `source` under another name, GitHub returns that existing fork and
|
|
137
|
+
// silently ignores the requested "name". Read the actual name back from
|
|
138
|
+
// the API response instead of assuming it matched.
|
|
139
|
+
let actualName = repoName;
|
|
140
|
+
try {
|
|
141
|
+
const data = JSON.parse(fork.stdout);
|
|
142
|
+
if (data?.name) actualName = data.name;
|
|
143
|
+
} catch {
|
|
144
|
+
// Response wasn't parseable JSON; fall back to assuming the requested
|
|
145
|
+
// name was honored and let waitForGithubRepo confirm or throw below.
|
|
146
|
+
}
|
|
102
147
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
148
|
+
await waitForGithubRepo(owner, actualName);
|
|
149
|
+
|
|
150
|
+
if (actualName !== repoName) {
|
|
151
|
+
const renameSpin = spinner(`Renaming ${owner}/${actualName} to ${owner}/${repoName}`);
|
|
152
|
+
const rename = await runCommandCapture('gh', ['api', `repos/${owner}/${actualName}`, '--method', 'PATCH', '-f', `name=${repoName}`]);
|
|
153
|
+
if (rename.code === 0) {
|
|
154
|
+
await waitForGithubRepo(owner, repoName);
|
|
155
|
+
renameSpin.succeed(`Forked ${source} to ${owner}/${repoName} (GitHub had reused an existing fork named ${actualName})`);
|
|
156
|
+
return repoName;
|
|
157
|
+
}
|
|
158
|
+
renameSpin.fail(`Could not rename — continuing with the existing fork name`);
|
|
159
|
+
kv('GitHub repo', `https://github.com/${owner}/${actualName}`);
|
|
160
|
+
console.log(dimOrange(` Note: this is named "${actualName}", not "${repoName}", because your GitHub account already had a fork of ${source}.`));
|
|
161
|
+
return actualName;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
spin.succeed(`Forked ${source} to ${owner}/${repoName}`);
|
|
165
|
+
return repoName;
|
|
107
166
|
}
|
|
108
167
|
|
|
109
|
-
spin
|
|
110
|
-
throw new Error(fork.stderr || template.stderr || 'GitHub repo creation from source failed');
|
|
168
|
+
return createFromTemplate(spin, owner, repoName, source);
|
|
111
169
|
}
|
|
112
170
|
|
|
113
171
|
export async function createGithubRepoFromClone(project) {
|
|
114
172
|
section('GitHub repository');
|
|
115
173
|
await ensureGithubLogin();
|
|
116
174
|
const owner = await getGithubLogin();
|
|
117
|
-
const
|
|
118
|
-
await forkSourceRepo(owner,
|
|
175
|
+
const requestedName = project.projectName;
|
|
176
|
+
const repoName = await forkSourceRepo(owner, requestedName);
|
|
119
177
|
|
|
120
178
|
const browserUrl = `https://github.com/${owner}/${repoName}`;
|
|
121
179
|
const cloneUrl = `${browserUrl}.git`;
|