fabrica-e-commerce 0.1.4 → 0.1.6
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/README.md +9 -7
- package/package.json +1 -1
- package/src/cli.js +15 -5
- package/src/deploy.js +22 -0
- package/src/github.js +43 -66
- package/src/system.js +1 -0
- package/src/ui.js +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ On Windows CMD, these commands do not require Unix tools such as `find` or `xarg
|
|
|
38
38
|
|
|
39
39
|
## Commands
|
|
40
40
|
|
|
41
|
-
- `build` — checks
|
|
41
|
+
- `build` — checks Git, creates a Fabrica Connect Supabase job, opens the OAuth bridge, asks for required secrets, clones `https://github.com/trucount/fabrica-final-e-c.git`, then asks whether to run locally or deploy on Vercel. Local mode writes `.env.local`, installs dependencies, and starts the Next.js dev server. Vercel mode checks GitHub/Vercel dependencies, verifies/logs in to Vercel, creates a user-owned repo from the hardcoded storefront source (fork/template, not `git push`), connects Vercel to that repo, writes every environment variable to production/preview/development, and deploys with `vercel --prod`.
|
|
42
42
|
- `list` — shows locally saved deployments and lets you replace a saved project's Vercel environment variable (across production, preview, and development), then redeploys.
|
|
43
43
|
- `vins` — verifies the CLI's external dependencies (`git`, GitHub CLI `gh`, and the Vercel CLI via `npx`) and automatically installs anything missing using your system's package manager (`apt-get`/`dnf`/`pacman` on Linux, `brew` on macOS, `winget`/`choco` on Windows). Prints manual install links for anything it can't install automatically.
|
|
44
44
|
- `info` / `.info` — prints package, bridge, repository, and local data paths.
|
|
@@ -60,12 +60,14 @@ On Windows CMD, these commands do not require Unix tools such as `find` or `xarg
|
|
|
60
60
|
- `UMAMI_API_CLIENT_ENDPOINT=https://api.umami.is/v1`
|
|
61
61
|
- `SUPABASE_SERVICE_ROLE_KEY=0000`
|
|
62
62
|
5. Adds `SUPABASE_URL` and `SUPABASE_ANON_KEY` from the bridge response.
|
|
63
|
-
6. **Step 3 —
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
63
|
+
6. **Step 3 — run target:**
|
|
64
|
+
- Asks whether to run the storefront **locally** or deploy on **Vercel cloud**.
|
|
65
|
+
- Local mode writes every collected variable to `.env.local` inside the cloned Next.js app, installs dependencies (`pnpm install` when available, otherwise `npm install`), and starts `pnpm run dev`/`npm run dev` at `http://localhost:3000`.
|
|
66
|
+
- Vercel mode verifies you're logged in to Vercel (`vercel whoami`); if not, runs an interactive `vercel login` and waits for it to finish.
|
|
67
|
+
- Vercel mode logs in to the GitHub CLI if needed (`gh auth login`) and creates a user-owned repo from the hardcoded storefront source with a fork/template flow, avoiding local `git push` credentials.
|
|
68
|
+
- Vercel mode links a new Vercel project with `vercel link --yes --project <name>` and connects it to the new GitHub repo with `vercel git connect` so future pushes auto-deploy.
|
|
69
|
+
- Vercel mode writes every collected environment variable (including the Supabase URL/anon key) to the **production, preview, and development** environments with `vercel env add`, so the values are permanent for the project, not just the first deploy.
|
|
70
|
+
- Vercel mode creates the production deployment with `vercel --prod --yes`.
|
|
69
71
|
|
|
70
72
|
## Dependency check (`vins`)
|
|
71
73
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -3,11 +3,12 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { connectSupabase } from './bridge.js';
|
|
6
|
-
import { collectEnv, cloneRepo, deployToVercel, editProjectEnv, ensureVercelLogin } from './deploy.js';
|
|
6
|
+
import { collectEnv, cloneRepo, deployToVercel, editProjectEnv, ensureVercelLogin, runLocally } from './deploy.js';
|
|
7
7
|
import { createGithubRepoFromClone } from './github.js';
|
|
8
8
|
import { ensureDependencies, vinsCommand } from './deps.js';
|
|
9
9
|
import { BRIDGE_ORIGIN, STORE_REPO } from './config.js';
|
|
10
10
|
import { dataDir, readProjects } from './store.js';
|
|
11
|
+
import { choose } from './prompt.js';
|
|
11
12
|
import { banner, help, kv, section } from './ui.js';
|
|
12
13
|
|
|
13
14
|
async function packageVersion() {
|
|
@@ -20,7 +21,7 @@ async function build() {
|
|
|
20
21
|
banner();
|
|
21
22
|
|
|
22
23
|
section('Dependency check');
|
|
23
|
-
await ensureDependencies({ names: ['git'
|
|
24
|
+
await ensureDependencies({ names: ['git'] });
|
|
24
25
|
|
|
25
26
|
section('Supabase Connect');
|
|
26
27
|
kv('BRIDGE', 'ONLINE');
|
|
@@ -29,9 +30,18 @@ async function build() {
|
|
|
29
30
|
const env = await collectEnv(supabase);
|
|
30
31
|
const project = await cloneRepo();
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
section('Step 3: Run target');
|
|
34
|
+
const target = await choose('Where should Fabrica run this storefront?', [
|
|
35
|
+
{ name: 'Deploy on Vercel cloud', value: 'vercel' },
|
|
36
|
+
{ name: 'Run locally on this computer', value: 'local' }
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
if (target === 'local') {
|
|
40
|
+
await runLocally(project, env);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await ensureDependencies({ names: ['gh', 'vercel'] });
|
|
35
45
|
await ensureVercelLogin();
|
|
36
46
|
const githubRepo = await createGithubRepoFromClone(project);
|
|
37
47
|
const record = await deployToVercel(project, env, githubRepo);
|
package/src/deploy.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import crypto from 'node:crypto';
|
|
3
4
|
import { HARDCODED_ENV, REQUIRED_ENV_KEYS, STORE_REPO } from './config.js';
|
|
@@ -103,3 +104,24 @@ export async function editProjectEnv(projects) {
|
|
|
103
104
|
await runCommand('npx', ['vercel@latest', '--prod', '--yes'], { cwd: project.target });
|
|
104
105
|
kv('Redeployed', project.projectName);
|
|
105
106
|
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export async function runLocally(project, env) {
|
|
110
|
+
section('Local Next.js setup');
|
|
111
|
+
const envPath = path.join(project.target, '.env.local');
|
|
112
|
+
const contents = Object.entries(env).map(([key, value]) => `${key}=${String(value).replace(/\n/g, '\\n')}`).join('\n') + '\n';
|
|
113
|
+
await fs.writeFile(envPath, contents, 'utf8');
|
|
114
|
+
kv('Env file', envPath);
|
|
115
|
+
|
|
116
|
+
const hasPnpmLock = await fs.access(path.join(project.target, 'pnpm-lock.yaml')).then(() => true, () => false);
|
|
117
|
+
const pnpmAvailable = (await runCommandCapture('pnpm', ['--version'])).code === 0;
|
|
118
|
+
const installCommand = hasPnpmLock && pnpmAvailable ? ['pnpm', ['install']] : ['npm', ['install']];
|
|
119
|
+
const devCommand = hasPnpmLock && pnpmAvailable ? ['pnpm', ['run', 'dev']] : ['npm', ['run', 'dev']];
|
|
120
|
+
|
|
121
|
+
await runCommand(installCommand[0], installCommand[1], { cwd: project.target });
|
|
122
|
+
section('Local app');
|
|
123
|
+
kv('Path', project.target);
|
|
124
|
+
kv('URL', 'http://localhost:3000');
|
|
125
|
+
console.log('Starting the Next.js dev server. Press Ctrl+C to stop it.');
|
|
126
|
+
await runCommand(devCommand[0], devCommand[1], { cwd: project.target });
|
|
127
|
+
}
|
package/src/github.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
1
|
import { commandExists, runCommand, runCommandCapture } from './system.js';
|
|
2
|
+
import { STORE_REPO } from './config.js';
|
|
3
3
|
import { choose } from './prompt.js';
|
|
4
4
|
import { kv, section, spinner } from './ui.js';
|
|
5
5
|
|
|
@@ -35,12 +35,12 @@ export async function ensureGithubLogin() {
|
|
|
35
35
|
kv('GitHub', 'Logged in');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const gitSpin = spinner('Configuring GitHub credentials
|
|
38
|
+
const gitSpin = spinner('Configuring GitHub API credentials');
|
|
39
39
|
if (await setupGithubGitCredentials()) {
|
|
40
|
-
gitSpin.succeed('GitHub credentials ready
|
|
40
|
+
gitSpin.succeed('GitHub API credentials ready');
|
|
41
41
|
} else {
|
|
42
42
|
gitSpin.fail('Could not configure GitHub git credentials automatically');
|
|
43
|
-
console.log('
|
|
43
|
+
console.log('Continuing with GitHub API publishing; if Git later asks for credentials, run: gh auth setup-git');
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -55,26 +55,6 @@ async function githubRepoExists(owner, repoName) {
|
|
|
55
55
|
return result.code === 0;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async function ensureGithubRepo(owner, repoName) {
|
|
59
|
-
const fullName = `${owner}/${repoName}`;
|
|
60
|
-
if (await githubRepoExists(owner, repoName)) {
|
|
61
|
-
const action = await choose(`GitHub repo ${fullName} already exists. What should Fabrica do?`, [
|
|
62
|
-
{ name: 'Use the existing repo and push this storefront to it', value: 'use' },
|
|
63
|
-
{ name: 'Stop so I can choose a different Vercel project/repo name', value: 'stop' }
|
|
64
|
-
]);
|
|
65
|
-
if (action === 'stop') throw new Error(`GitHub repo ${fullName} already exists. Re-run build with a different project name.`);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const spin = spinner(`Creating GitHub repo ${fullName}`);
|
|
70
|
-
const create = await runCommandCapture('gh', ['repo', 'create', fullName, '--private']);
|
|
71
|
-
if (create.code !== 0) {
|
|
72
|
-
spin.fail('Could not create GitHub repository');
|
|
73
|
-
throw new Error(create.stderr || 'gh repo create failed');
|
|
74
|
-
}
|
|
75
|
-
spin.succeed(`Created GitHub repo ${fullName}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
58
|
async function setOrigin(project, repoUrl) {
|
|
79
59
|
const existing = await runCommandCapture('git', ['remote', 'get-url', 'origin'], { cwd: project.target });
|
|
80
60
|
if (existing.code === 0) {
|
|
@@ -84,65 +64,62 @@ async function setOrigin(project, repoUrl) {
|
|
|
84
64
|
}
|
|
85
65
|
}
|
|
86
66
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
67
|
+
// Creates or reuses a GitHub repository from the hardcoded storefront source
|
|
68
|
+
// repository, so Vercel connects to user-owned code without requiring a local
|
|
69
|
+
// git push from this machine.
|
|
70
|
+
function sourceRepoFullName() {
|
|
71
|
+
return STORE_REPO.replace(/^https:\/\/github\.com\//, '').replace(/\.git$/, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function waitForGithubRepo(owner, repoName) {
|
|
75
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
76
|
+
if (await githubRepoExists(owner, repoName)) return;
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function forkSourceRepo(owner, repoName) {
|
|
82
|
+
const source = sourceRepoFullName();
|
|
83
|
+
const existing = await githubRepoExists(owner, repoName);
|
|
84
|
+
if (existing) {
|
|
85
|
+
const action = await choose(`GitHub repo ${owner}/${repoName} already exists. What should Fabrica do?`, [
|
|
86
|
+
{ name: 'Use the existing repo for Vercel', value: 'use' },
|
|
87
|
+
{ name: 'Stop so I can choose a different Vercel project/repo name', value: 'stop' }
|
|
88
|
+
]);
|
|
89
|
+
if (action === 'stop') throw new Error(`GitHub repo ${owner}/${repoName} already exists. Re-run build with a different project name.`);
|
|
92
90
|
return;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
spin
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
93
|
+
const spin = spinner(`Creating GitHub repo ${owner}/${repoName} from ${source}`);
|
|
94
|
+
const fork = await runCommandCapture('gh', ['api', `repos/${source}/forks`, '--method', 'POST', '--input', '-'], {
|
|
95
|
+
input: JSON.stringify({ name: repoName, default_branch_only: false })
|
|
96
|
+
});
|
|
97
|
+
if (fork.code === 0) {
|
|
98
|
+
await waitForGithubRepo(owner, repoName);
|
|
99
|
+
spin.succeed(`Forked ${source} to ${owner}/${repoName}`);
|
|
101
100
|
return;
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
{ name: 'Stop so I can fix GitHub credentials manually', value: 'stop' }
|
|
108
|
-
]);
|
|
109
|
-
if (action === 'login') {
|
|
110
|
-
await runCommand('gh', ['auth', 'login', '--hostname', 'github.com', '--git-protocol', 'https', '--web']);
|
|
111
|
-
await setupGithubGitCredentials();
|
|
112
|
-
await runCommand('git', ['push', '-u', 'origin', 'main'], { cwd: project.target });
|
|
113
|
-
kv('GitHub push', 'Succeeded after login');
|
|
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}`);
|
|
114
106
|
return;
|
|
115
107
|
}
|
|
116
|
-
|
|
108
|
+
|
|
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');
|
|
117
111
|
}
|
|
118
112
|
|
|
119
|
-
// Re-homes the cloned storefront code into a brand new GitHub repository
|
|
120
|
-
// owned by the logged-in user, so the deployed Vercel project can stay
|
|
121
|
-
// connected to that repo (git push -> auto deploy) instead of the original
|
|
122
|
-
// template repository.
|
|
123
113
|
export async function createGithubRepoFromClone(project) {
|
|
124
114
|
section('GitHub repository');
|
|
125
115
|
await ensureGithubLogin();
|
|
126
116
|
const owner = await getGithubLogin();
|
|
127
117
|
const repoName = project.projectName;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Detach from the template's git history and start a clean repo so we
|
|
131
|
-
// don't try to push into someone else's repository history.
|
|
132
|
-
if (process.platform === 'win32') {
|
|
133
|
-
await runCommand('cmd', ['/c', 'rmdir', '/s', '/q', '.git'], { cwd: project.target, allowFailure: true });
|
|
134
|
-
} else {
|
|
135
|
-
await runCommand('rm', ['-rf', '.git'], { cwd: project.target, allowFailure: true });
|
|
136
|
-
}
|
|
137
|
-
await runCommand('git', ['init', '-b', 'main'], { cwd: project.target });
|
|
138
|
-
await runCommand('git', ['add', '-A'], { cwd: project.target });
|
|
139
|
-
await runCommand('git', ['-c', 'user.email=fabrica-cli@local', '-c', 'user.name=Fabrica CLI', 'commit', '-m', 'Initial commit from Fabrica storefront'], { cwd: project.target });
|
|
140
|
-
|
|
141
|
-
await ensureGithubRepo(owner, repoName);
|
|
142
|
-
await setOrigin(project, repoUrl);
|
|
143
|
-
await pushWithCredentialFallback(project);
|
|
118
|
+
await forkSourceRepo(owner, repoName);
|
|
144
119
|
|
|
145
120
|
const browserUrl = `https://github.com/${owner}/${repoName}`;
|
|
121
|
+
const cloneUrl = `${browserUrl}.git`;
|
|
122
|
+
await setOrigin(project, cloneUrl);
|
|
146
123
|
kv('GitHub repo', browserUrl);
|
|
147
124
|
return { owner, repoName, repoUrl: browserUrl };
|
|
148
125
|
}
|
package/src/system.js
CHANGED
|
@@ -46,6 +46,7 @@ export function runCommandCapture(command, args, options = {}) {
|
|
|
46
46
|
let child;
|
|
47
47
|
try {
|
|
48
48
|
child = spawn(executable(command), args, spawnOptions(command, options, 'pipe'));
|
|
49
|
+
if (options.input) child.stdin.end(options.input);
|
|
49
50
|
} catch (error) {
|
|
50
51
|
resolve({ code: null, stdout: '', stderr: '', error });
|
|
51
52
|
return;
|
package/src/ui.js
CHANGED
|
@@ -21,7 +21,7 @@ export function help() {
|
|
|
21
21
|
banner();
|
|
22
22
|
console.log(`
|
|
23
23
|
${orange('Commands')}
|
|
24
|
-
${bold('build')} Connect Supabase, collect secrets,
|
|
24
|
+
${bold('build')} Connect Supabase, collect secrets, then run locally or deploy to Vercel
|
|
25
25
|
${bold('list')} Show deployed Fabrica projects and edit env variables
|
|
26
26
|
${bold('vins')} Verify CLI dependencies (git, gh, vercel) and auto-install anything missing
|
|
27
27
|
${bold('info')} Show package, bridge, repo, and local storage information
|