@a5gard/bifrost 1.0.1 → 1.0.2
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 +84 -38
- package/dist/index.d.ts +3 -1
- package/dist/index.js +688 -391
- package/dist/registry.bifrost +37 -0
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { Command } from "commander";
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import inquirer3 from "inquirer";
|
|
5
3
|
import chalk5 from "chalk";
|
|
6
4
|
|
|
7
5
|
// src/constants.ts
|
|
@@ -125,211 +123,6 @@ var PLATFORMS = {
|
|
|
125
123
|
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
126
124
|
var TEMP_DIR_PREFIX = "bifrost-temp-";
|
|
127
125
|
|
|
128
|
-
// src/prompts.ts
|
|
129
|
-
import prompts from "prompts";
|
|
130
|
-
|
|
131
|
-
// src/utilts.ts
|
|
132
|
-
import fs from "fs-extra";
|
|
133
|
-
import validateNpmPackageName from "validate-npm-package-name";
|
|
134
|
-
function toValidPackageName(name) {
|
|
135
|
-
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9-~]+/g, "-");
|
|
136
|
-
}
|
|
137
|
-
function parseStackReference(template) {
|
|
138
|
-
const parts = template.split("/");
|
|
139
|
-
if (parts.length !== 2) {
|
|
140
|
-
throw new Error("Stack must be in format: owner/repo");
|
|
141
|
-
}
|
|
142
|
-
return { owner: parts[0], repo: parts[1] };
|
|
143
|
-
}
|
|
144
|
-
async function directoryExists(dir) {
|
|
145
|
-
try {
|
|
146
|
-
const stats = await fs.stat(dir);
|
|
147
|
-
return stats.isDirectory();
|
|
148
|
-
} catch {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
async function isDirectoryEmpty(dir) {
|
|
153
|
-
const files = await fs.readdir(dir);
|
|
154
|
-
return files.length === 0;
|
|
155
|
-
}
|
|
156
|
-
function getPackageManagerCommand(pm, command) {
|
|
157
|
-
const commands = {
|
|
158
|
-
npm: { install: "npm install", run: "npm run" },
|
|
159
|
-
pnpm: { install: "pnpm install", run: "pnpm" },
|
|
160
|
-
yarn: { install: "yarn", run: "yarn" },
|
|
161
|
-
bun: { install: "bun install", run: "bun" }
|
|
162
|
-
};
|
|
163
|
-
return commands[pm][command];
|
|
164
|
-
}
|
|
165
|
-
async function detectPackageManager() {
|
|
166
|
-
const userAgent = process.env.npm_config_user_agent;
|
|
167
|
-
if (!userAgent) return "bun";
|
|
168
|
-
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
169
|
-
if (userAgent.startsWith("yarn")) return "yarn";
|
|
170
|
-
if (userAgent.startsWith("bun")) return "bun";
|
|
171
|
-
if (userAgent.startsWith("npm")) return "npm";
|
|
172
|
-
return "bun";
|
|
173
|
-
}
|
|
174
|
-
function detectPlatformFromStack(template) {
|
|
175
|
-
const lowerStack = template.toLowerCase();
|
|
176
|
-
if (lowerStack.includes("remix")) return "remix";
|
|
177
|
-
if (lowerStack.includes("next")) return "nextjs";
|
|
178
|
-
if (lowerStack.includes("vite")) return "vite";
|
|
179
|
-
if (lowerStack.includes("vue")) return "vue";
|
|
180
|
-
if (lowerStack.includes("svelte")) return "svelte";
|
|
181
|
-
if (lowerStack.includes("astro")) return "astro";
|
|
182
|
-
if (lowerStack.includes("solid")) return "solid";
|
|
183
|
-
if (lowerStack.includes("qwik")) return "qwik";
|
|
184
|
-
if (lowerStack.includes("react") || lowerStack.includes("cra")) return "react";
|
|
185
|
-
return void 0;
|
|
186
|
-
}
|
|
187
|
-
function detectTagsFromStack(template) {
|
|
188
|
-
const tags = [];
|
|
189
|
-
const lowerStack = template.toLowerCase();
|
|
190
|
-
if (lowerStack.includes("typescript") || lowerStack.includes("-ts")) tags.push("typescript");
|
|
191
|
-
if (lowerStack.includes("javascript") || lowerStack.includes("-js")) tags.push("javascript");
|
|
192
|
-
if (lowerStack.includes("tailwind")) tags.push("tailwind");
|
|
193
|
-
if (lowerStack.includes("prisma")) tags.push("prisma");
|
|
194
|
-
if (lowerStack.includes("postgres")) tags.push("postgresql");
|
|
195
|
-
if (lowerStack.includes("sqlite")) tags.push("sqlite");
|
|
196
|
-
if (lowerStack.includes("mongo")) tags.push("mongodb");
|
|
197
|
-
if (lowerStack.includes("aws")) tags.push("aws");
|
|
198
|
-
if (lowerStack.includes("cloudflare")) tags.push("cloudflare");
|
|
199
|
-
if (lowerStack.includes("vercel")) tags.push("vercel");
|
|
200
|
-
if (lowerStack.includes("react")) tags.push("react");
|
|
201
|
-
return tags;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// src/prompts.ts
|
|
205
|
-
async function promptForMissingOptions(projectName, template, packageManager, install) {
|
|
206
|
-
const detectedPM = await detectPackageManager();
|
|
207
|
-
const questions = [];
|
|
208
|
-
if (!projectName) {
|
|
209
|
-
questions.push({
|
|
210
|
-
type: "text",
|
|
211
|
-
name: "projectName",
|
|
212
|
-
message: "What would you like to name your new project?",
|
|
213
|
-
initial: "my-bifrost-app",
|
|
214
|
-
validate: (value) => {
|
|
215
|
-
if (!value) return "Project name is required";
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
if (!template) {
|
|
221
|
-
questions.push({
|
|
222
|
-
type: "select",
|
|
223
|
-
name: "platform",
|
|
224
|
-
message: "Which platform would you like to use?",
|
|
225
|
-
choices: Object.entries(PLATFORMS).map(([key, platform]) => ({
|
|
226
|
-
title: platform.name,
|
|
227
|
-
value: key,
|
|
228
|
-
description: platform.description || ""
|
|
229
|
-
})),
|
|
230
|
-
initial: 0
|
|
231
|
-
});
|
|
232
|
-
questions.push({
|
|
233
|
-
type: (prev) => {
|
|
234
|
-
const platform = PLATFORMS[prev];
|
|
235
|
-
return platform.templates ? "select" : null;
|
|
236
|
-
},
|
|
237
|
-
name: "template",
|
|
238
|
-
message: "Select a template:",
|
|
239
|
-
choices: (prev) => {
|
|
240
|
-
const platform = PLATFORMS[prev];
|
|
241
|
-
if (!platform.templates) return [];
|
|
242
|
-
return Object.entries(platform.templates).map(([key, template2]) => ({
|
|
243
|
-
title: template2.name,
|
|
244
|
-
value: template2.repo,
|
|
245
|
-
description: template2.description
|
|
246
|
-
}));
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
questions.push({
|
|
250
|
-
type: (prev, values) => {
|
|
251
|
-
const platformKey = values.platform;
|
|
252
|
-
return platformKey === "custom" ? "text" : null;
|
|
253
|
-
},
|
|
254
|
-
name: "customStack",
|
|
255
|
-
message: "Enter template (owner/repo):",
|
|
256
|
-
validate: (value) => {
|
|
257
|
-
if (!value || !value.includes("/")) {
|
|
258
|
-
return "Stack must be in format: owner/repo";
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
if (!packageManager) {
|
|
265
|
-
questions.push({
|
|
266
|
-
type: "select",
|
|
267
|
-
name: "packageManager",
|
|
268
|
-
message: "Which package manager do you prefer?",
|
|
269
|
-
choices: PACKAGE_MANAGERS.map((pm) => ({
|
|
270
|
-
title: pm,
|
|
271
|
-
value: pm,
|
|
272
|
-
selected: pm === detectedPM
|
|
273
|
-
})),
|
|
274
|
-
initial: PACKAGE_MANAGERS.indexOf(detectedPM)
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
if (install === void 0) {
|
|
278
|
-
questions.push({
|
|
279
|
-
type: "confirm",
|
|
280
|
-
name: "install",
|
|
281
|
-
message: "Would you like to have the install command run once the project has initialized?",
|
|
282
|
-
initial: true
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
questions.push({
|
|
286
|
-
type: "confirm",
|
|
287
|
-
name: "gitPush",
|
|
288
|
-
message: "Would you like to auto create and push the first commit to GitHub?",
|
|
289
|
-
initial: false
|
|
290
|
-
});
|
|
291
|
-
questions.push({
|
|
292
|
-
type: "confirm",
|
|
293
|
-
name: "runWizard",
|
|
294
|
-
message: "Would you like to run the config.bifrost wizard?",
|
|
295
|
-
initial: false
|
|
296
|
-
});
|
|
297
|
-
questions.push({
|
|
298
|
-
type: "confirm",
|
|
299
|
-
name: "submitToRegistry",
|
|
300
|
-
message: "Would you like to submit your template to the bifrost registry?",
|
|
301
|
-
initial: false
|
|
302
|
-
});
|
|
303
|
-
const answers = await prompts(questions, {
|
|
304
|
-
onCancel: () => {
|
|
305
|
-
console.log("\nOperation cancelled");
|
|
306
|
-
process.exit(0);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
let finalStack = template;
|
|
310
|
-
if (!template) {
|
|
311
|
-
if (answers.platform === "custom") {
|
|
312
|
-
finalStack = answers.customStack;
|
|
313
|
-
} else if (answers.template) {
|
|
314
|
-
finalStack = answers.template;
|
|
315
|
-
} else {
|
|
316
|
-
const platform = PLATFORMS[answers.platform];
|
|
317
|
-
if (platform.repo) {
|
|
318
|
-
finalStack = platform.repo;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return {
|
|
323
|
-
projectName: projectName || answers.projectName,
|
|
324
|
-
template: finalStack,
|
|
325
|
-
packageManager: packageManager || answers.packageManager,
|
|
326
|
-
install: install !== void 0 ? install : answers.install,
|
|
327
|
-
gitPush: answers.gitPush,
|
|
328
|
-
runWizard: answers.runWizard,
|
|
329
|
-
submitToRegistry: answers.submitToRegistry
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
126
|
// src/creator.ts
|
|
334
127
|
import fs5 from "fs-extra";
|
|
335
128
|
import path4 from "path";
|
|
@@ -338,7 +131,7 @@ import ora2 from "ora";
|
|
|
338
131
|
|
|
339
132
|
// src/git.ts
|
|
340
133
|
import { execa } from "execa";
|
|
341
|
-
import
|
|
134
|
+
import fs from "fs-extra";
|
|
342
135
|
import path from "path";
|
|
343
136
|
import os from "os";
|
|
344
137
|
async function isGitInstalled() {
|
|
@@ -358,11 +151,11 @@ async function cloneRepository(owner, repo, targetDir) {
|
|
|
358
151
|
const tempDir = path.join(os.tmpdir(), `${TEMP_DIR_PREFIX}${Date.now()}`);
|
|
359
152
|
try {
|
|
360
153
|
await execa("git", ["clone", "--depth", "1", repoUrl, tempDir]);
|
|
361
|
-
await
|
|
362
|
-
await
|
|
363
|
-
await
|
|
154
|
+
await fs.remove(path.join(tempDir, ".git"));
|
|
155
|
+
await fs.copy(tempDir, targetDir, { overwrite: true });
|
|
156
|
+
await fs.remove(tempDir);
|
|
364
157
|
} catch (error) {
|
|
365
|
-
await
|
|
158
|
+
await fs.remove(tempDir).catch(() => {
|
|
366
159
|
});
|
|
367
160
|
if (error instanceof Error) {
|
|
368
161
|
if (error.message.includes("not found") || error.message.includes("not exist")) {
|
|
@@ -402,6 +195,72 @@ async function pushToGitHub(projectDir) {
|
|
|
402
195
|
|
|
403
196
|
// src/install.ts
|
|
404
197
|
import { execa as execa2 } from "execa";
|
|
198
|
+
|
|
199
|
+
// src/utilts.ts
|
|
200
|
+
import fs2 from "fs-extra";
|
|
201
|
+
import validateNpmPackageName from "validate-npm-package-name";
|
|
202
|
+
function toValidPackageName(name) {
|
|
203
|
+
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9-~]+/g, "-");
|
|
204
|
+
}
|
|
205
|
+
function parseStackReference(template) {
|
|
206
|
+
const parts = template.split("/");
|
|
207
|
+
if (parts.length !== 2) {
|
|
208
|
+
throw new Error("Stack must be in format: owner/repo");
|
|
209
|
+
}
|
|
210
|
+
return { owner: parts[0], repo: parts[1] };
|
|
211
|
+
}
|
|
212
|
+
async function directoryExists(dir) {
|
|
213
|
+
try {
|
|
214
|
+
const stats = await fs2.stat(dir);
|
|
215
|
+
return stats.isDirectory();
|
|
216
|
+
} catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function isDirectoryEmpty(dir) {
|
|
221
|
+
const files = await fs2.readdir(dir);
|
|
222
|
+
return files.length === 0;
|
|
223
|
+
}
|
|
224
|
+
function getPackageManagerCommand(pm, command) {
|
|
225
|
+
const commands = {
|
|
226
|
+
npm: { install: "npm install", run: "npm run" },
|
|
227
|
+
pnpm: { install: "pnpm install", run: "pnpm" },
|
|
228
|
+
yarn: { install: "yarn", run: "yarn" },
|
|
229
|
+
bun: { install: "bun install", run: "bun" }
|
|
230
|
+
};
|
|
231
|
+
return commands[pm][command];
|
|
232
|
+
}
|
|
233
|
+
function detectPlatformFromStack(template) {
|
|
234
|
+
const lowerStack = template.toLowerCase();
|
|
235
|
+
if (lowerStack.includes("remix")) return "remix";
|
|
236
|
+
if (lowerStack.includes("next")) return "nextjs";
|
|
237
|
+
if (lowerStack.includes("vite")) return "vite";
|
|
238
|
+
if (lowerStack.includes("vue")) return "vue";
|
|
239
|
+
if (lowerStack.includes("svelte")) return "svelte";
|
|
240
|
+
if (lowerStack.includes("astro")) return "astro";
|
|
241
|
+
if (lowerStack.includes("solid")) return "solid";
|
|
242
|
+
if (lowerStack.includes("qwik")) return "qwik";
|
|
243
|
+
if (lowerStack.includes("react") || lowerStack.includes("cra")) return "react";
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
function detectTagsFromStack(template) {
|
|
247
|
+
const tags = [];
|
|
248
|
+
const lowerStack = template.toLowerCase();
|
|
249
|
+
if (lowerStack.includes("typescript") || lowerStack.includes("-ts")) tags.push("typescript");
|
|
250
|
+
if (lowerStack.includes("javascript") || lowerStack.includes("-js")) tags.push("javascript");
|
|
251
|
+
if (lowerStack.includes("tailwind")) tags.push("tailwind");
|
|
252
|
+
if (lowerStack.includes("prisma")) tags.push("prisma");
|
|
253
|
+
if (lowerStack.includes("postgres")) tags.push("postgresql");
|
|
254
|
+
if (lowerStack.includes("sqlite")) tags.push("sqlite");
|
|
255
|
+
if (lowerStack.includes("mongo")) tags.push("mongodb");
|
|
256
|
+
if (lowerStack.includes("aws")) tags.push("aws");
|
|
257
|
+
if (lowerStack.includes("cloudflare")) tags.push("cloudflare");
|
|
258
|
+
if (lowerStack.includes("vercel")) tags.push("vercel");
|
|
259
|
+
if (lowerStack.includes("react")) tags.push("react");
|
|
260
|
+
return tags;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/install.ts
|
|
405
264
|
async function installDependencies(projectDir, packageManager) {
|
|
406
265
|
const installCommand = getPackageManagerCommand(packageManager, "install");
|
|
407
266
|
const [cmd, ...args] = installCommand.split(" ");
|
|
@@ -430,7 +289,7 @@ async function runPostInstallScripts(projectDir, packageManager, scripts) {
|
|
|
430
289
|
import fs3 from "fs-extra";
|
|
431
290
|
import path2 from "path";
|
|
432
291
|
import os2 from "os";
|
|
433
|
-
import
|
|
292
|
+
import inquirer from "inquirer";
|
|
434
293
|
import chalk from "chalk";
|
|
435
294
|
import ora from "ora";
|
|
436
295
|
import { execa as execa3 } from "execa";
|
|
@@ -462,20 +321,22 @@ async function installPluginLibraries(projectDir, packageManager, libraries) {
|
|
|
462
321
|
});
|
|
463
322
|
}
|
|
464
323
|
async function promptForFileLocation(fileName, suggestedLocation) {
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
324
|
+
const { location } = await inquirer.prompt([
|
|
325
|
+
{
|
|
326
|
+
type: "input",
|
|
327
|
+
name: "location",
|
|
328
|
+
message: `Location for ${chalk.cyan(fileName)}:`,
|
|
329
|
+
default: suggestedLocation,
|
|
330
|
+
validate: (value) => {
|
|
331
|
+
if (!value) return "Location is required";
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
473
334
|
}
|
|
474
|
-
|
|
475
|
-
if (!
|
|
335
|
+
]);
|
|
336
|
+
if (!location) {
|
|
476
337
|
throw new Error("Operation cancelled");
|
|
477
338
|
}
|
|
478
|
-
return
|
|
339
|
+
return location;
|
|
479
340
|
}
|
|
480
341
|
async function copyPluginFiles(projectDir, pluginTempDir, files) {
|
|
481
342
|
for (const file of files) {
|
|
@@ -595,8 +456,79 @@ async function createBifrostConfig(projectDir, projectName, template, platform,
|
|
|
595
456
|
}
|
|
596
457
|
|
|
597
458
|
// src/creator.ts
|
|
459
|
+
import { execSync } from "child_process";
|
|
460
|
+
async function installTailwind(absolutePath, packageManager, useNgin) {
|
|
461
|
+
const spinner = ora2(`Installing Tailwind CSS with ${useNgin ? "preset ngin" : "base config"}...`).start();
|
|
462
|
+
try {
|
|
463
|
+
const installCmd = packageManager === "npm" ? "npm install -D tailwindcss postcss autoprefixer" : `${packageManager} add -D tailwindcss postcss autoprefixer`;
|
|
464
|
+
execSync(installCmd, { cwd: absolutePath, stdio: "ignore" });
|
|
465
|
+
execSync("npx tailwindcss init -p", { cwd: absolutePath, stdio: "ignore" });
|
|
466
|
+
if (useNgin) {
|
|
467
|
+
const tailwindConfig = `import type { Config } from 'tailwindcss';
|
|
468
|
+
import ngin from '@a5gard/ngin';
|
|
469
|
+
|
|
470
|
+
export default {
|
|
471
|
+
presets: [ngin],
|
|
472
|
+
content: ['./app/**/*.{js,jsx,ts,tsx}'],
|
|
473
|
+
} satisfies Config;`;
|
|
474
|
+
await fs5.writeFile(path4.join(absolutePath, "tailwind.config.ts"), tailwindConfig);
|
|
475
|
+
}
|
|
476
|
+
const appCssPath = path4.join(absolutePath, "app", "tailwind.css");
|
|
477
|
+
const rootCssPath = path4.join(absolutePath, "app", "root.css");
|
|
478
|
+
const cssPath = await fs5.pathExists(appCssPath) ? appCssPath : rootCssPath;
|
|
479
|
+
const tailwindDirectives = `@tailwind base;
|
|
480
|
+
@tailwind components;
|
|
481
|
+
@tailwind utilities;
|
|
482
|
+
`;
|
|
483
|
+
if (await fs5.pathExists(cssPath)) {
|
|
484
|
+
const existingCss = await fs5.readFile(cssPath, "utf-8");
|
|
485
|
+
if (!existingCss.includes("@tailwind")) {
|
|
486
|
+
await fs5.writeFile(cssPath, tailwindDirectives + existingCss);
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
await fs5.ensureDir(path4.dirname(cssPath));
|
|
490
|
+
await fs5.writeFile(cssPath, tailwindDirectives);
|
|
491
|
+
}
|
|
492
|
+
spinner.succeed(`Installed Tailwind CSS with ${useNgin ? "preset ngin" : "base config"}`);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
spinner.fail("Failed to install Tailwind CSS");
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async function installMidgardr(absolutePath, packageManager, withNgin) {
|
|
499
|
+
const spinner = ora2("Installing MI\xD0GAR\xD0R UI components...").start();
|
|
500
|
+
try {
|
|
501
|
+
const command = withNgin ? "full-w-ngin" : "full-install";
|
|
502
|
+
execSync(`bunx @a5gard/midgardr ${command}`, { cwd: absolutePath, stdio: "inherit" });
|
|
503
|
+
spinner.succeed("Installed MI\xD0GAR\xD0R UI components");
|
|
504
|
+
} catch (error) {
|
|
505
|
+
spinner.fail("Failed to install MI\xD0GAR\xD0R UI components");
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function installBaldr(absolutePath, packageManager) {
|
|
510
|
+
const spinner = ora2("Installing @a5gard/baldr icons...").start();
|
|
511
|
+
try {
|
|
512
|
+
const installCmd = packageManager === "npm" ? "npm install @a5gard/baldr" : `${packageManager} add @a5gard/baldr`;
|
|
513
|
+
execSync(installCmd, { cwd: absolutePath, stdio: "ignore" });
|
|
514
|
+
spinner.succeed("Installed @a5gard/baldr icons");
|
|
515
|
+
} catch (error) {
|
|
516
|
+
spinner.fail("Failed to install @a5gard/baldr icons");
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
598
520
|
async function createProject(context) {
|
|
599
|
-
const {
|
|
521
|
+
const {
|
|
522
|
+
projectName,
|
|
523
|
+
template,
|
|
524
|
+
packageManager,
|
|
525
|
+
install,
|
|
526
|
+
gitPush,
|
|
527
|
+
tailwindBase,
|
|
528
|
+
tailwindNgin,
|
|
529
|
+
midgardr,
|
|
530
|
+
baldr
|
|
531
|
+
} = context;
|
|
600
532
|
const absolutePath = path4.resolve(projectName);
|
|
601
533
|
console.log();
|
|
602
534
|
console.log(chalk2.bold("Creating your Bifrost project..."));
|
|
@@ -636,6 +568,19 @@ async function createProject(context) {
|
|
|
636
568
|
installSpinner.fail("Failed to install dependencies");
|
|
637
569
|
throw error;
|
|
638
570
|
}
|
|
571
|
+
if (midgardr) {
|
|
572
|
+
await installMidgardr(absolutePath, packageManager, tailwindNgin || false);
|
|
573
|
+
} else {
|
|
574
|
+
if (tailwindBase) {
|
|
575
|
+
await installTailwind(absolutePath, packageManager, false);
|
|
576
|
+
}
|
|
577
|
+
if (tailwindNgin) {
|
|
578
|
+
await installTailwind(absolutePath, packageManager, true);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (baldr) {
|
|
582
|
+
await installBaldr(absolutePath, packageManager);
|
|
583
|
+
}
|
|
639
584
|
if (stackConfig?.postInstall && Array.isArray(stackConfig.postInstall)) {
|
|
640
585
|
const postInstallSpinner = ora2("Running post-install scripts...").start();
|
|
641
586
|
try {
|
|
@@ -681,13 +626,17 @@ async function createProject(context) {
|
|
|
681
626
|
console.log();
|
|
682
627
|
console.log(chalk2.bold.green("\u2713 Project created successfully!"));
|
|
683
628
|
console.log();
|
|
684
|
-
console.log(chalk2.bold("Next steps:"));
|
|
685
629
|
console.log();
|
|
686
|
-
console.log(` ${chalk2.cyan("cd")} ${projectName}`);
|
|
687
630
|
if (!install) {
|
|
631
|
+
console.log(chalk2.bold("Next steps:"));
|
|
632
|
+
console.log(` ${chalk2.cyan("cd")} ${projectName}`);
|
|
688
633
|
console.log(` ${chalk2.cyan(`${packageManager} install`)}`);
|
|
634
|
+
console.log(` ${chalk2.cyan(`${packageManager} ${packageManager === "npm" ? "run " : ""}dev`)}`);
|
|
635
|
+
} else {
|
|
636
|
+
console.log(chalk2.bold.green("changing directories and starting the first dev server..."));
|
|
637
|
+
execSync(`cd ${projectName}`, { cwd: absolutePath, stdio: "inherit" });
|
|
638
|
+
execSync(`${packageManager} ${packageManager === "npm" ? "run " : ""}dev`, { cwd: absolutePath, stdio: "inherit" });
|
|
689
639
|
}
|
|
690
|
-
console.log(` ${chalk2.cyan(`${packageManager} ${packageManager === "npm" ? "run " : ""}dev`)}`);
|
|
691
640
|
console.log();
|
|
692
641
|
}
|
|
693
642
|
|
|
@@ -695,11 +644,26 @@ async function createProject(context) {
|
|
|
695
644
|
import fs6 from "fs-extra";
|
|
696
645
|
import path5 from "path";
|
|
697
646
|
import chalk3 from "chalk";
|
|
698
|
-
import
|
|
699
|
-
import { execSync } from "child_process";
|
|
647
|
+
import inquirer2 from "inquirer";
|
|
648
|
+
import { execSync as execSync2 } from "child_process";
|
|
649
|
+
function drawBox(title, content, footer) {
|
|
650
|
+
const width = 117;
|
|
651
|
+
const horizontalLine = "\u2500".repeat(width - 2);
|
|
652
|
+
console.log(`\u256D${horizontalLine}\u256E`);
|
|
653
|
+
console.log(`\u2502${title.padStart(Math.floor((width - 2 + title.length) / 2)).padEnd(width - 2)}\u2502`);
|
|
654
|
+
console.log(`\u251C${horizontalLine}\u2524`);
|
|
655
|
+
content.forEach((line) => {
|
|
656
|
+
console.log(`\u2502 ${line.padEnd(width - 4)} \u2502`);
|
|
657
|
+
});
|
|
658
|
+
if (footer) {
|
|
659
|
+
console.log(`\u251C${horizontalLine}\u2524`);
|
|
660
|
+
console.log(`\u2502${footer.padStart(Math.floor((width - 2 + footer.length) / 2)).padEnd(width - 2)}\u2502`);
|
|
661
|
+
}
|
|
662
|
+
console.log(`\u2570${horizontalLine}\u256F`);
|
|
663
|
+
}
|
|
700
664
|
async function detectGitHubRepo() {
|
|
701
665
|
try {
|
|
702
|
-
const remote =
|
|
666
|
+
const remote = execSync2("git config --get remote.origin.url", { encoding: "utf-8" }).trim();
|
|
703
667
|
const match = remote.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
704
668
|
if (match) {
|
|
705
669
|
return match[1];
|
|
@@ -723,27 +687,37 @@ async function detectGitHubRepo() {
|
|
|
723
687
|
return null;
|
|
724
688
|
}
|
|
725
689
|
async function promptForGitHubRepo() {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
690
|
+
drawBox(
|
|
691
|
+
"GITHUB REPOSITORY REQUIRED",
|
|
692
|
+
[
|
|
693
|
+
chalk3.yellow("\u26A0 No GitHub repository detected"),
|
|
694
|
+
"",
|
|
695
|
+
"Please push your project and create a public repository before continuing."
|
|
696
|
+
]
|
|
697
|
+
);
|
|
698
|
+
const { hasRepo } = await inquirer2.prompt([
|
|
699
|
+
{
|
|
700
|
+
type: "confirm",
|
|
701
|
+
name: "hasRepo",
|
|
702
|
+
message: "Have you created a public GitHub repository?",
|
|
703
|
+
default: false
|
|
704
|
+
}
|
|
705
|
+
]);
|
|
734
706
|
if (!hasRepo) {
|
|
735
707
|
console.log(chalk3.red("\nPlease create a public GitHub repository first"));
|
|
736
708
|
process.exit(1);
|
|
737
709
|
}
|
|
738
|
-
const { repo } = await
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
710
|
+
const { repo } = await inquirer2.prompt([
|
|
711
|
+
{
|
|
712
|
+
type: "input",
|
|
713
|
+
name: "repo",
|
|
714
|
+
message: "Enter your GitHub repository (owner/repo):",
|
|
715
|
+
validate: (value) => {
|
|
716
|
+
const pattern = /^[\w-]+\/[\w-]+$/;
|
|
717
|
+
return pattern.test(value) || "Invalid format. Use: owner/repo";
|
|
718
|
+
}
|
|
745
719
|
}
|
|
746
|
-
|
|
720
|
+
]);
|
|
747
721
|
if (!repo) {
|
|
748
722
|
console.log(chalk3.red("\nRepository is required"));
|
|
749
723
|
process.exit(1);
|
|
@@ -751,68 +725,85 @@ async function promptForGitHubRepo() {
|
|
|
751
725
|
return repo;
|
|
752
726
|
}
|
|
753
727
|
async function runConfigWizard() {
|
|
754
|
-
|
|
728
|
+
drawBox(
|
|
729
|
+
"CONFIG.BIFROST WIZARD",
|
|
730
|
+
[
|
|
731
|
+
"This wizard will guide you through creating a config.bifrost file for your template.",
|
|
732
|
+
"",
|
|
733
|
+
"This configuration enables your template to be shared with the community."
|
|
734
|
+
]
|
|
735
|
+
);
|
|
755
736
|
const configPath = path5.join(process.cwd(), "config.bifrost");
|
|
756
737
|
if (await fs6.pathExists(configPath)) {
|
|
757
|
-
const { overwrite } = await
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
738
|
+
const { overwrite } = await inquirer2.prompt([
|
|
739
|
+
{
|
|
740
|
+
type: "confirm",
|
|
741
|
+
name: "overwrite",
|
|
742
|
+
message: "config.bifrost already exists. Overwrite?",
|
|
743
|
+
default: false
|
|
744
|
+
}
|
|
745
|
+
]);
|
|
763
746
|
if (!overwrite) {
|
|
764
747
|
const existingConfig = await fs6.readJson(configPath);
|
|
765
748
|
return existingConfig;
|
|
766
749
|
}
|
|
767
750
|
}
|
|
768
751
|
const detectedRepo = await detectGitHubRepo();
|
|
769
|
-
|
|
752
|
+
drawBox(
|
|
753
|
+
"TEMPLATE INFORMATION",
|
|
754
|
+
[
|
|
755
|
+
"Provide basic information about your template.",
|
|
756
|
+
"",
|
|
757
|
+
"This helps users discover and understand your template."
|
|
758
|
+
]
|
|
759
|
+
);
|
|
760
|
+
const responses = await inquirer2.prompt([
|
|
770
761
|
{
|
|
771
|
-
type: "
|
|
762
|
+
type: "input",
|
|
772
763
|
name: "name",
|
|
773
764
|
message: "Template name:",
|
|
774
765
|
validate: (value) => value.trim().length > 0 || "Name is required"
|
|
775
766
|
},
|
|
776
767
|
{
|
|
777
|
-
type: "
|
|
768
|
+
type: "input",
|
|
778
769
|
name: "description",
|
|
779
770
|
message: "Description:",
|
|
780
771
|
validate: (value) => value.trim().length > 0 || "Description is required"
|
|
781
772
|
},
|
|
782
773
|
{
|
|
783
|
-
type: "
|
|
774
|
+
type: "input",
|
|
784
775
|
name: "platform",
|
|
785
776
|
message: "Platform:",
|
|
786
|
-
|
|
777
|
+
default: "remix",
|
|
787
778
|
validate: (value) => value.trim().length > 0 || "Platform is required"
|
|
788
779
|
},
|
|
789
780
|
{
|
|
790
|
-
type: "
|
|
781
|
+
type: "input",
|
|
791
782
|
name: "github",
|
|
792
783
|
message: "GitHub repository (owner/repo):",
|
|
793
|
-
|
|
784
|
+
default: detectedRepo || "",
|
|
794
785
|
validate: (value) => {
|
|
795
786
|
const pattern = /^[\w-]+\/[\w-]+$/;
|
|
796
787
|
return pattern.test(value) || "Invalid format. Use: owner/repo";
|
|
797
788
|
}
|
|
798
789
|
},
|
|
799
790
|
{
|
|
800
|
-
type: "
|
|
791
|
+
type: "input",
|
|
801
792
|
name: "tags",
|
|
802
793
|
message: "Tags (comma-separated):",
|
|
803
794
|
validate: (value) => value.trim().length > 0 || "At least one tag is required"
|
|
804
795
|
},
|
|
805
796
|
{
|
|
806
|
-
type: "
|
|
797
|
+
type: "input",
|
|
807
798
|
name: "postInstall",
|
|
808
799
|
message: "Post-install scripts (comma-separated npm script names):",
|
|
809
|
-
|
|
800
|
+
default: ""
|
|
810
801
|
},
|
|
811
802
|
{
|
|
812
|
-
type: "
|
|
803
|
+
type: "input",
|
|
813
804
|
name: "plugins",
|
|
814
805
|
message: "Plugins to include (comma-separated owner/repo):",
|
|
815
|
-
|
|
806
|
+
default: ""
|
|
816
807
|
}
|
|
817
808
|
]);
|
|
818
809
|
if (!responses.name) {
|
|
@@ -832,11 +823,16 @@ async function runConfigWizard() {
|
|
|
832
823
|
plugins: responses.plugins ? responses.plugins.split(",").map((p) => p.trim()).filter(Boolean) : []
|
|
833
824
|
};
|
|
834
825
|
await fs6.writeJson(configPath, config, { spaces: 2 });
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
826
|
+
drawBox(
|
|
827
|
+
"SUCCESS",
|
|
828
|
+
[
|
|
829
|
+
chalk3.green("\u2705 config.bifrost created successfully!"),
|
|
830
|
+
"",
|
|
831
|
+
"Configuration:",
|
|
832
|
+
"",
|
|
833
|
+
...JSON.stringify(config, null, 2).split("\n").map((line) => chalk3.white(line))
|
|
834
|
+
]
|
|
835
|
+
);
|
|
840
836
|
return config;
|
|
841
837
|
}
|
|
842
838
|
|
|
@@ -844,8 +840,8 @@ async function runConfigWizard() {
|
|
|
844
840
|
import fs7 from "fs-extra";
|
|
845
841
|
import path6 from "path";
|
|
846
842
|
import chalk4 from "chalk";
|
|
847
|
-
import
|
|
848
|
-
import { execSync as
|
|
843
|
+
import prompts from "prompts";
|
|
844
|
+
import { execSync as execSync3 } from "child_process";
|
|
849
845
|
var REGISTRY_REPO = "A5GARD/BIFROST";
|
|
850
846
|
var REGISTRY_FILE = "dist/registry.bifrost";
|
|
851
847
|
async function verifyPublicRepo(github) {
|
|
@@ -864,7 +860,7 @@ async function submitTemplate() {
|
|
|
864
860
|
let config;
|
|
865
861
|
if (!await fs7.pathExists(configPath)) {
|
|
866
862
|
console.log(chalk4.yellow("\u26A0 config.bifrost not found\n"));
|
|
867
|
-
const { runWizard } = await
|
|
863
|
+
const { runWizard } = await prompts({
|
|
868
864
|
type: "confirm",
|
|
869
865
|
name: "runWizard",
|
|
870
866
|
message: "Would you like to run the config wizard to create it?",
|
|
@@ -883,7 +879,7 @@ async function submitTemplate() {
|
|
|
883
879
|
if (!isPublic) {
|
|
884
880
|
console.log(chalk4.red("\n\u274C Repository must be public"));
|
|
885
881
|
console.log(chalk4.yellow("Please make your repository public before submitting"));
|
|
886
|
-
const { madePublic } = await
|
|
882
|
+
const { madePublic } = await prompts({
|
|
887
883
|
type: "confirm",
|
|
888
884
|
name: "madePublic",
|
|
889
885
|
message: "Have you made the repository public?",
|
|
@@ -913,7 +909,7 @@ async function submitTemplate() {
|
|
|
913
909
|
console.log(`Plugins: ${chalk4.white(config.plugins.join(", "))}`);
|
|
914
910
|
}
|
|
915
911
|
console.log(chalk4.gray("\u2500".repeat(50)));
|
|
916
|
-
const { confirm } = await
|
|
912
|
+
const { confirm } = await prompts({
|
|
917
913
|
type: "confirm",
|
|
918
914
|
name: "confirm",
|
|
919
915
|
message: "Submit this template to the registry?",
|
|
@@ -933,13 +929,13 @@ async function submitTemplate() {
|
|
|
933
929
|
tags: config.tags
|
|
934
930
|
};
|
|
935
931
|
console.log(chalk4.blue("\n\u{1F504} Forking registry repository..."));
|
|
936
|
-
|
|
937
|
-
const username =
|
|
932
|
+
execSync3(`gh repo fork ${REGISTRY_REPO} --clone=false`, { stdio: "inherit" });
|
|
933
|
+
const username = execSync3("gh api user -q .login", { encoding: "utf-8" }).trim();
|
|
938
934
|
const forkRepo = `${username}/BIFROST`;
|
|
939
935
|
console.log(chalk4.blue("\u{1F4E5} Cloning forked repository..."));
|
|
940
936
|
const tempDir = path6.join(process.cwd(), ".bifrost-temp");
|
|
941
937
|
await fs7.ensureDir(tempDir);
|
|
942
|
-
|
|
938
|
+
execSync3(`gh repo clone ${forkRepo} ${tempDir}`, { stdio: "inherit" });
|
|
943
939
|
console.log(chalk4.blue("\u{1F4CB} Fetching current registry..."));
|
|
944
940
|
const registryUrl = `https://raw.githubusercontent.com/${REGISTRY_REPO}/main/${REGISTRY_FILE}`;
|
|
945
941
|
const registryResponse = await fetch(registryUrl);
|
|
@@ -959,11 +955,11 @@ async function submitTemplate() {
|
|
|
959
955
|
await fs7.writeJson(registryPath, registry, { spaces: 2 });
|
|
960
956
|
console.log(chalk4.blue("\u{1F4BE} Committing changes..."));
|
|
961
957
|
process.chdir(tempDir);
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
958
|
+
execSync3("git add .", { stdio: "inherit" });
|
|
959
|
+
execSync3(`git commit -m "Add/Update template: ${config.name}"`, { stdio: "inherit" });
|
|
960
|
+
execSync3("git push", { stdio: "inherit" });
|
|
965
961
|
console.log(chalk4.blue("\u{1F500} Creating pull request..."));
|
|
966
|
-
const prUrl =
|
|
962
|
+
const prUrl = execSync3(
|
|
967
963
|
`gh pr create --repo ${REGISTRY_REPO} --title "Add template: ${config.name}" --body "Submitting template ${config.name} to the registry.
|
|
968
964
|
|
|
969
965
|
Platform: ${config.platform}
|
|
@@ -990,129 +986,430 @@ Description: ${config.description}"`,
|
|
|
990
986
|
}
|
|
991
987
|
}
|
|
992
988
|
|
|
993
|
-
// src/
|
|
989
|
+
// src/index.ts
|
|
990
|
+
var VERSION = "1.0.0";
|
|
994
991
|
async function loadRegistry() {
|
|
995
|
-
const registryFile = Bun.file(new URL("../registry.bifrost", import.meta.url));
|
|
992
|
+
const registryFile = Bun.file(new URL("../dist/registry.bifrost", import.meta.url));
|
|
996
993
|
return await registryFile.json();
|
|
997
994
|
}
|
|
998
|
-
|
|
999
|
-
const
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
995
|
+
function drawBox2(title, content, footer) {
|
|
996
|
+
const width = 117;
|
|
997
|
+
const horizontalLine = "\u2500".repeat(width - 2);
|
|
998
|
+
console.log(`\u256D${horizontalLine}\u256E`);
|
|
999
|
+
console.log(`\u2502${title.padStart(Math.floor((width - 2 + title.length) / 2)).padEnd(width - 2)}\u2502`);
|
|
1000
|
+
console.log(`\u251C${horizontalLine}\u2524`);
|
|
1001
|
+
content.forEach((line) => {
|
|
1002
|
+
console.log(`\u2502 ${line.padEnd(width - 4)} \u2502`);
|
|
1003
|
+
});
|
|
1004
|
+
if (footer) {
|
|
1005
|
+
console.log(`\u251C${horizontalLine}\u2524`);
|
|
1006
|
+
console.log(`\u2502${footer.padStart(Math.floor((width - 2 + footer.length) / 2)).padEnd(width - 2)}\u2502`);
|
|
1007
|
+
}
|
|
1008
|
+
console.log(`\u2570${horizontalLine}\u256F`);
|
|
1009
|
+
}
|
|
1010
|
+
async function promptMainMenu() {
|
|
1011
|
+
drawBox2(
|
|
1012
|
+
"@a5gard/bifrost",
|
|
1013
|
+
[
|
|
1014
|
+
"Platform-agnostic project creator with extensible template system.",
|
|
1015
|
+
"",
|
|
1016
|
+
"Choose an action to get started."
|
|
1017
|
+
]
|
|
1018
|
+
);
|
|
1019
|
+
const { action } = await inquirer3.prompt([
|
|
1020
|
+
{
|
|
1021
|
+
type: "list",
|
|
1022
|
+
name: "action",
|
|
1023
|
+
message: "What would you like to do?",
|
|
1024
|
+
choices: [
|
|
1025
|
+
{ name: "Create a new project", value: "create" },
|
|
1026
|
+
{ name: "config.bifrost wizard", value: "wizard" },
|
|
1027
|
+
{ name: "Submit template to bifrost registry", value: "submit" }
|
|
1028
|
+
]
|
|
1005
1029
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1030
|
+
]);
|
|
1031
|
+
return action;
|
|
1032
|
+
}
|
|
1033
|
+
async function promptProjectName() {
|
|
1034
|
+
drawBox2(
|
|
1035
|
+
"PROJECT SETUP",
|
|
1036
|
+
[
|
|
1037
|
+
"Enter a name for your new project.",
|
|
1038
|
+
"",
|
|
1039
|
+
"This will be used as the directory name and package name."
|
|
1040
|
+
]
|
|
1041
|
+
);
|
|
1042
|
+
const { projectName } = await inquirer3.prompt([
|
|
1043
|
+
{
|
|
1044
|
+
type: "input",
|
|
1045
|
+
name: "projectName",
|
|
1046
|
+
message: "What would you like to name your new project?",
|
|
1047
|
+
validate: (input) => {
|
|
1048
|
+
if (!input.trim()) return "Project name is required";
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
]);
|
|
1053
|
+
return projectName;
|
|
1054
|
+
}
|
|
1055
|
+
async function promptPlatform() {
|
|
1056
|
+
drawBox2(
|
|
1057
|
+
"SELECT PLATFORM",
|
|
1058
|
+
[
|
|
1059
|
+
"Choose the platform/framework for your new project.",
|
|
1060
|
+
"",
|
|
1061
|
+
"Available platforms are listed below."
|
|
1062
|
+
]
|
|
1063
|
+
);
|
|
1064
|
+
const platformChoices = Object.entries(PLATFORMS).map(([key, platform2]) => ({
|
|
1065
|
+
name: platform2.name,
|
|
1066
|
+
value: key
|
|
1067
|
+
}));
|
|
1068
|
+
const { platform } = await inquirer3.prompt([
|
|
1069
|
+
{
|
|
1070
|
+
type: "list",
|
|
1071
|
+
name: "platform",
|
|
1072
|
+
message: "Which platform would you like to use?",
|
|
1073
|
+
choices: platformChoices
|
|
1009
1074
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1075
|
+
]);
|
|
1076
|
+
return platform;
|
|
1077
|
+
}
|
|
1078
|
+
async function promptTemplateChoice(platform) {
|
|
1079
|
+
const platformData = PLATFORMS[platform];
|
|
1080
|
+
if (!platformData.templates) {
|
|
1081
|
+
return { useTemplate: false };
|
|
1082
|
+
}
|
|
1083
|
+
drawBox2(
|
|
1084
|
+
"INSTALLATION TYPE",
|
|
1085
|
+
[
|
|
1086
|
+
"Choose between the platform default or select from available templates.",
|
|
1087
|
+
"",
|
|
1088
|
+
"Templates provide pre-configured setups for specific use cases."
|
|
1089
|
+
]
|
|
1090
|
+
);
|
|
1091
|
+
const { choice } = await inquirer3.prompt([
|
|
1092
|
+
{
|
|
1093
|
+
type: "list",
|
|
1094
|
+
name: "choice",
|
|
1095
|
+
message: "Do you prefer to use the platform's default install or would you like to opt for a template instead?",
|
|
1096
|
+
choices: [
|
|
1097
|
+
{ name: "Platform Default", value: "default" },
|
|
1098
|
+
{ name: "Choose Template", value: "template" }
|
|
1099
|
+
]
|
|
1013
1100
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1101
|
+
]);
|
|
1102
|
+
if (choice === "default") {
|
|
1103
|
+
const templateKeys = Object.keys(platformData.templates);
|
|
1104
|
+
if (templateKeys.length === 1) {
|
|
1105
|
+
return { useTemplate: true, template: platformData.templates[templateKeys[0]].repo };
|
|
1017
1106
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const gitPush = prompted.gitPush;
|
|
1038
|
-
const runWizard = prompted.runWizard;
|
|
1039
|
-
const submitToRegistry = prompted.submitToRegistry;
|
|
1040
|
-
const validProjectName = toValidPackageName(finalProjectName);
|
|
1041
|
-
await createProject({
|
|
1042
|
-
projectName: validProjectName,
|
|
1043
|
-
template: finalStack,
|
|
1044
|
-
packageManager: finalPackageManager,
|
|
1045
|
-
install: finalInstall,
|
|
1046
|
-
gitPush
|
|
1047
|
-
});
|
|
1048
|
-
if (runWizard) {
|
|
1049
|
-
await runConfigWizard();
|
|
1107
|
+
drawBox2(
|
|
1108
|
+
`${platformData.name.toUpperCase()} DEFAULT OPTIONS`,
|
|
1109
|
+
[
|
|
1110
|
+
"This platform offers multiple default starter options.",
|
|
1111
|
+
"",
|
|
1112
|
+
"Select the one that best fits your needs."
|
|
1113
|
+
]
|
|
1114
|
+
);
|
|
1115
|
+
const defaultChoices = Object.entries(platformData.templates).map(([key, tmpl]) => ({
|
|
1116
|
+
name: `${tmpl.name} - ${tmpl.description}`,
|
|
1117
|
+
value: tmpl.repo,
|
|
1118
|
+
short: tmpl.name
|
|
1119
|
+
}));
|
|
1120
|
+
const { template: template2 } = await inquirer3.prompt([
|
|
1121
|
+
{
|
|
1122
|
+
type: "list",
|
|
1123
|
+
name: "template",
|
|
1124
|
+
message: "Which default would you like to use?",
|
|
1125
|
+
choices: defaultChoices
|
|
1050
1126
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1127
|
+
]);
|
|
1128
|
+
return { useTemplate: true, template: template2 };
|
|
1129
|
+
}
|
|
1130
|
+
const registry = await loadRegistry();
|
|
1131
|
+
const platformTemplates = registry.filter((t) => t.platform === platform);
|
|
1132
|
+
if (platformTemplates.length === 0) {
|
|
1133
|
+
console.log(chalk5.yellow("No community templates available for this platform. Using default."));
|
|
1134
|
+
const templateKeys = Object.keys(platformData.templates);
|
|
1135
|
+
return { useTemplate: true, template: platformData.templates[templateKeys[0]].repo };
|
|
1136
|
+
}
|
|
1137
|
+
drawBox2(
|
|
1138
|
+
`${platformData.name.toUpperCase()} TEMPLATES`,
|
|
1139
|
+
[
|
|
1140
|
+
"Select a community template from the available options below.",
|
|
1141
|
+
"",
|
|
1142
|
+
"Each template includes specific configurations and best practices."
|
|
1143
|
+
]
|
|
1144
|
+
);
|
|
1145
|
+
const templateChoices = platformTemplates.map((t) => ({
|
|
1146
|
+
name: `${t.owner}/${t.repo} - ${t.description}`,
|
|
1147
|
+
value: `${t.owner}/${t.repo}`,
|
|
1148
|
+
short: `${t.owner}/${t.repo}`
|
|
1149
|
+
}));
|
|
1150
|
+
const { template } = await inquirer3.prompt([
|
|
1151
|
+
{
|
|
1152
|
+
type: "list",
|
|
1153
|
+
name: "template",
|
|
1154
|
+
message: "Which template would you like to use?",
|
|
1155
|
+
choices: templateChoices
|
|
1059
1156
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1157
|
+
]);
|
|
1158
|
+
return { useTemplate: true, template };
|
|
1062
1159
|
}
|
|
1063
|
-
function
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
${chalk5.cyan("--help, -h")} Print this help message
|
|
1083
|
-
${chalk5.cyan("--version, -V")} Print the CLI version
|
|
1084
|
-
${chalk5.cyan("--template, -s")} Stack to use (format: owner/repo)
|
|
1085
|
-
${chalk5.cyan("--pkg-mgr, -p")} Package manager (npm, pnpm, yarn, bun)
|
|
1086
|
-
${chalk5.cyan("--no-install")} Skip dependency installation
|
|
1087
|
-
${chalk5.cyan("--list-templates")} List all available community templates
|
|
1088
|
-
${chalk5.cyan("--wizard")} Run config.bifrost wizard
|
|
1089
|
-
${chalk5.cyan("--submit")} Submit template to bifrost registry
|
|
1090
|
-
`);
|
|
1160
|
+
async function promptPackageManager() {
|
|
1161
|
+
drawBox2(
|
|
1162
|
+
"PACKAGE MANAGER",
|
|
1163
|
+
[
|
|
1164
|
+
"Select your preferred package manager for dependency installation.",
|
|
1165
|
+
"",
|
|
1166
|
+
"Supported: npm, pnpm, yarn, bun"
|
|
1167
|
+
]
|
|
1168
|
+
);
|
|
1169
|
+
const { packageManager } = await inquirer3.prompt([
|
|
1170
|
+
{
|
|
1171
|
+
type: "list",
|
|
1172
|
+
name: "packageManager",
|
|
1173
|
+
message: "Which package manager do you prefer?",
|
|
1174
|
+
choices: PACKAGE_MANAGERS.map((pm) => ({ name: pm, value: pm }))
|
|
1175
|
+
}
|
|
1176
|
+
]);
|
|
1177
|
+
return packageManager;
|
|
1091
1178
|
}
|
|
1092
|
-
function
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1179
|
+
async function promptAdditionalOptions() {
|
|
1180
|
+
drawBox2(
|
|
1181
|
+
"ADDITIONAL OPTIONS",
|
|
1182
|
+
[
|
|
1183
|
+
"Configure additional features and tools for your project.",
|
|
1184
|
+
"",
|
|
1185
|
+
"Would you like tailwind and its requirements to be installed and configured:"
|
|
1186
|
+
]
|
|
1187
|
+
);
|
|
1188
|
+
const { options } = await inquirer3.prompt([
|
|
1189
|
+
{
|
|
1190
|
+
type: "checkbox",
|
|
1191
|
+
name: "options",
|
|
1192
|
+
message: "Select the options you would like to include (use spacebar to toggle):",
|
|
1193
|
+
choices: [
|
|
1194
|
+
{ name: "Using the base tailwind config", value: "tailwindBase" },
|
|
1195
|
+
{ name: "Using the preset ngin", value: "tailwindNgin" },
|
|
1196
|
+
{ name: "Pre-install MI\xD0GAR\xD0R UI components", value: "midgardr" },
|
|
1197
|
+
{ name: "Pre-install @a5gard/baldr icons", value: "baldr" },
|
|
1198
|
+
{ name: "Auto install the project's libraries once the project has initialized", value: "install", checked: true },
|
|
1199
|
+
{ name: "Auto create and push the first commit to GitHub", value: "gitPush" }
|
|
1200
|
+
]
|
|
1201
|
+
}
|
|
1202
|
+
]);
|
|
1203
|
+
return {
|
|
1204
|
+
tailwindBase: options.includes("tailwindBase"),
|
|
1205
|
+
tailwindNgin: options.includes("tailwindNgin"),
|
|
1206
|
+
midgardr: options.includes("midgardr"),
|
|
1207
|
+
baldr: options.includes("baldr"),
|
|
1208
|
+
install: options.includes("install"),
|
|
1209
|
+
gitPush: options.includes("gitPush")
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
function showTemplates(registry, platformFilter) {
|
|
1213
|
+
let filteredTemplates = registry;
|
|
1214
|
+
if (platformFilter) {
|
|
1215
|
+
filteredTemplates = registry.filter((t) => t.platform === platformFilter);
|
|
1216
|
+
if (filteredTemplates.length === 0) {
|
|
1217
|
+
console.log(chalk5.yellow(`No templates found for platform: ${platformFilter}`));
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
const groupedByPlatform = filteredTemplates.reduce((acc, template) => {
|
|
1097
1222
|
if (!acc[template.platform]) {
|
|
1098
1223
|
acc[template.platform] = [];
|
|
1099
1224
|
}
|
|
1100
1225
|
acc[template.platform].push(template);
|
|
1101
1226
|
return acc;
|
|
1102
1227
|
}, {});
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1228
|
+
console.log();
|
|
1229
|
+
drawBox2(
|
|
1230
|
+
"AVAILABLE COMMUNITY TEMPLATES",
|
|
1231
|
+
Object.entries(groupedByPlatform).flatMap(([platform, templates]) => {
|
|
1232
|
+
const lines = [
|
|
1233
|
+
"",
|
|
1234
|
+
chalk5.bold.cyan(platform.toUpperCase()),
|
|
1235
|
+
""
|
|
1236
|
+
];
|
|
1237
|
+
templates.forEach((template) => {
|
|
1238
|
+
lines.push(` ${chalk5.green("\u203A")} ${chalk5.bold(`${template.owner}/${template.repo}`)}`);
|
|
1239
|
+
lines.push(` ${chalk5.gray(template.description)}`);
|
|
1240
|
+
lines.push(` ${chalk5.gray(`Tags: ${template.tags.join(", ")}`)}`);
|
|
1241
|
+
lines.push("");
|
|
1242
|
+
});
|
|
1243
|
+
return lines;
|
|
1244
|
+
})
|
|
1245
|
+
);
|
|
1246
|
+
console.log();
|
|
1113
1247
|
console.log(chalk5.gray("Use any template with: ") + chalk5.cyan("bunx @a5gard/bifrost my-app --template owner/repo"));
|
|
1114
1248
|
console.log();
|
|
1115
1249
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1250
|
+
function showHelp() {
|
|
1251
|
+
drawBox2(
|
|
1252
|
+
"BIFROST CLI HELP",
|
|
1253
|
+
[
|
|
1254
|
+
chalk5.white("BIFR\xD6ST unifies the fragmented landscape of project starters. Instead of learning npx create-remix, npx create-next-app, npx create-vite, and so on\u2014use one CLI for all platforms with community-driven templates and a plugin system."),
|
|
1255
|
+
chalk5.white("Whenever a platform has been selected, you have the option of using the default installer provided by the platform's creators, or you may opt instead to use a configured template that was created by other developers."),
|
|
1256
|
+
chalk5.white("Templates are opinionated variants that will include file scaffolding, configurations in place to meet the needed requirements, route files, pre-installed libraries and more."),
|
|
1257
|
+
chalk5.white("Allowing you to hit the ground running when starting a new project, instead of wasting time or getting bogged down with all the required to-do items whenever a new app is created."),
|
|
1258
|
+
chalk5.white("Currently focusing on React-based platforms. Once the details are ironed out, that focus will be expanded upon however I can."),
|
|
1259
|
+
"",
|
|
1260
|
+
chalk5.white("BIFR\xD6ST is not only striving to fill a gap where no one else has even really attempted, but also introducing a plugin system that can be used with, alongside, or on its own with the default installer."),
|
|
1261
|
+
chalk5.white("A plugin will contain everything needed to add that feature to your project. For example, one-time password authentication for Remix Run. The plugin will contain and install all required route files, create/update all required configuration files, and will ensure all required libraries are installed within the project."),
|
|
1262
|
+
chalk5.white("Once the plugin's installation process has completed, other than setting up your own personal Resend account, the plugin will be ready to use."),
|
|
1263
|
+
chalk5.white(""),
|
|
1264
|
+
chalk5.white("Another benefit that has come from the plugin system: for developers that can't live with a one-template-fits-all lifestyle. Instead, create a bare-bones essential app where only the libraries, configs, and routes that are absolutely essential, no matter the scenario, are included."),
|
|
1265
|
+
chalk5.white("At which time, instead of configuring several time-consuming full-stack variations, you can create plugins to fill in the needs of whatever use case you can think of. So instead of having several projects that need not only to be taken care of in all the forms that are required, where at times you will be updating the same configs and libraries across all variants."),
|
|
1266
|
+
chalk5.white("Because we don't want to deal with all of the headaches that come along with it, not to mention the time spent going that route. In its place, have one app that will serve as the foundation for all the plugins."),
|
|
1267
|
+
chalk5.white("In the end, there's one app and one plugin to take care of instead of updating the same auth library across 4, 5, or whatever number of applications."),
|
|
1268
|
+
chalk5.white(""),
|
|
1269
|
+
"",
|
|
1270
|
+
chalk5.bold("Usage:"),
|
|
1271
|
+
"",
|
|
1272
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost")} ${chalk5.gray("<projectName> <...options>")}`,
|
|
1273
|
+
"",
|
|
1274
|
+
chalk5.bold("Examples:"),
|
|
1275
|
+
"",
|
|
1276
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost")}`,
|
|
1277
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost my-app")}`,
|
|
1278
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost my-app --template remix-run/indie-template")}`,
|
|
1279
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost my-app -t owner/repo -p bun")}`,
|
|
1280
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost my-app -t owner/repo --no-install")}`,
|
|
1281
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost --list-templates")}`,
|
|
1282
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost --list-templates remix")}`,
|
|
1283
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost --wizard")}`,
|
|
1284
|
+
` ${chalk5.cyan("$ bunx @a5gard/bifrost --submit")}`,
|
|
1285
|
+
"",
|
|
1286
|
+
chalk5.bold("Options:"),
|
|
1287
|
+
"",
|
|
1288
|
+
` ${chalk5.cyan("--help, -h")} Print this help message`,
|
|
1289
|
+
` ${chalk5.cyan("--version, -V")} Print the CLI version`,
|
|
1290
|
+
` ${chalk5.cyan("--template, -t")} Template to use (format: owner/repo)`,
|
|
1291
|
+
` ${chalk5.cyan("--pkg-mgr, -p")} Package manager (npm, pnpm, yarn, bun)`,
|
|
1292
|
+
` ${chalk5.cyan("--no-install")} Skip dependency installation`,
|
|
1293
|
+
` ${chalk5.cyan("--list-templates")} List all available community templates`,
|
|
1294
|
+
` ${chalk5.cyan("--wizard")} Run config.bifrost wizard`,
|
|
1295
|
+
` ${chalk5.cyan("--submit")} Submit template to bifrost registry`,
|
|
1296
|
+
""
|
|
1297
|
+
]
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
async function runCLI(argv) {
|
|
1301
|
+
const registry = await loadRegistry();
|
|
1302
|
+
const args = argv.slice(2);
|
|
1303
|
+
const flags = {};
|
|
1304
|
+
let projectName;
|
|
1305
|
+
for (let i = 0; i < args.length; i++) {
|
|
1306
|
+
const arg = args[i];
|
|
1307
|
+
if (arg === "--help" || arg === "-h") {
|
|
1308
|
+
flags.help = true;
|
|
1309
|
+
} else if (arg === "--version" || arg === "-V") {
|
|
1310
|
+
flags.version = true;
|
|
1311
|
+
} else if (arg === "--list-templates") {
|
|
1312
|
+
flags.listTemplates = true;
|
|
1313
|
+
if (args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
1314
|
+
flags.platform = args[++i];
|
|
1315
|
+
}
|
|
1316
|
+
} else if (arg === "--wizard") {
|
|
1317
|
+
flags.wizard = true;
|
|
1318
|
+
} else if (arg === "--submit") {
|
|
1319
|
+
flags.submit = true;
|
|
1320
|
+
} else if (arg === "--no-install") {
|
|
1321
|
+
flags.noInstall = true;
|
|
1322
|
+
} else if (arg === "--template" || arg === "-t") {
|
|
1323
|
+
flags.template = args[++i];
|
|
1324
|
+
} else if (arg === "--pkg-mgr" || arg === "-p") {
|
|
1325
|
+
flags.pkgMgr = args[++i];
|
|
1326
|
+
} else if (!arg.startsWith("-") && !projectName) {
|
|
1327
|
+
projectName = arg;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (flags.help) {
|
|
1331
|
+
showHelp();
|
|
1332
|
+
process.exit(0);
|
|
1333
|
+
}
|
|
1334
|
+
if (flags.version) {
|
|
1335
|
+
console.log(`BIFR\xD6ST V${VERSION}`);
|
|
1336
|
+
process.exit(0);
|
|
1337
|
+
}
|
|
1338
|
+
if (flags.listTemplates) {
|
|
1339
|
+
showTemplates(registry, flags.platform);
|
|
1340
|
+
process.exit(0);
|
|
1341
|
+
}
|
|
1342
|
+
if (flags.wizard) {
|
|
1343
|
+
await runConfigWizard();
|
|
1344
|
+
process.exit(0);
|
|
1345
|
+
}
|
|
1346
|
+
if (flags.submit) {
|
|
1347
|
+
await submitTemplate();
|
|
1348
|
+
process.exit(0);
|
|
1349
|
+
}
|
|
1350
|
+
try {
|
|
1351
|
+
if (flags.pkgMgr && !PACKAGE_MANAGERS.includes(flags.pkgMgr)) {
|
|
1352
|
+
console.error(chalk5.red(`Invalid package manager. Must be one of: ${PACKAGE_MANAGERS.join(", ")}`));
|
|
1353
|
+
process.exit(1);
|
|
1354
|
+
}
|
|
1355
|
+
let action = "create";
|
|
1356
|
+
let finalProjectName = projectName;
|
|
1357
|
+
let finalTemplate = flags.template;
|
|
1358
|
+
let finalPackageManager = flags.pkgMgr;
|
|
1359
|
+
let finalInstall = flags.noInstall ? false : void 0;
|
|
1360
|
+
let tailwindBase = false;
|
|
1361
|
+
let tailwindNgin = false;
|
|
1362
|
+
let midgardr = false;
|
|
1363
|
+
let baldr = false;
|
|
1364
|
+
let gitPush = false;
|
|
1365
|
+
if (!projectName && !flags.template) {
|
|
1366
|
+
action = await promptMainMenu();
|
|
1367
|
+
if (action === "wizard") {
|
|
1368
|
+
await runConfigWizard();
|
|
1369
|
+
process.exit(0);
|
|
1370
|
+
}
|
|
1371
|
+
if (action === "submit") {
|
|
1372
|
+
await submitTemplate();
|
|
1373
|
+
process.exit(0);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
if (!finalProjectName) {
|
|
1377
|
+
finalProjectName = await promptProjectName();
|
|
1378
|
+
}
|
|
1379
|
+
if (!finalTemplate) {
|
|
1380
|
+
const platform = await promptPlatform();
|
|
1381
|
+
const templateChoice = await promptTemplateChoice(platform);
|
|
1382
|
+
finalTemplate = templateChoice.template;
|
|
1383
|
+
}
|
|
1384
|
+
if (!finalPackageManager) {
|
|
1385
|
+
finalPackageManager = await promptPackageManager();
|
|
1386
|
+
}
|
|
1387
|
+
const additionalOptions = await promptAdditionalOptions();
|
|
1388
|
+
tailwindBase = additionalOptions.tailwindBase;
|
|
1389
|
+
tailwindNgin = additionalOptions.tailwindNgin;
|
|
1390
|
+
midgardr = additionalOptions.midgardr;
|
|
1391
|
+
baldr = additionalOptions.baldr;
|
|
1392
|
+
finalInstall = finalInstall !== void 0 ? finalInstall : additionalOptions.install;
|
|
1393
|
+
gitPush = additionalOptions.gitPush;
|
|
1394
|
+
const validProjectName = toValidPackageName(finalProjectName);
|
|
1395
|
+
await createProject({
|
|
1396
|
+
projectName: validProjectName,
|
|
1397
|
+
template: finalTemplate,
|
|
1398
|
+
packageManager: finalPackageManager,
|
|
1399
|
+
install: finalInstall,
|
|
1400
|
+
gitPush,
|
|
1401
|
+
tailwindBase,
|
|
1402
|
+
tailwindNgin,
|
|
1403
|
+
midgardr,
|
|
1404
|
+
baldr
|
|
1405
|
+
});
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
console.error();
|
|
1408
|
+
console.error(chalk5.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
1409
|
+
console.error();
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
export {
|
|
1414
|
+
runCLI
|
|
1415
|
+
};
|