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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/github.js +72 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabrica-e-commerce",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Orange themed CMD launcher for deploying Fabrica e-commerce stores with Supabase and Vercel.",
5
5
  "type": "module",
6
6
  "bin": {
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
- await waitForGithubRepo(owner, repoName);
99
- spin.succeed(`Forked ${source} to ${owner}/${repoName}`);
100
- return;
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
- const template = await runCommandCapture('gh', ['repo', 'create', `${owner}/${repoName}`, '--private', '--template', source, '--include-all-branches']);
104
- if (template.code === 0) {
105
- spin.succeed(`Created ${owner}/${repoName} from ${source}`);
106
- return;
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.fail('Could not create GitHub repo from source repo');
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 repoName = project.projectName;
118
- await forkSourceRepo(owner, repoName);
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`;