@mars-stack/cli 7.0.6 → 8.0.1
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 +10 -0
- package/dist/index.js +187 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template/e2e/README.md +1 -1
- package/template/e2e/api.spec.ts +2 -1
- package/template/e2e/auth.spec.ts +1 -1
- package/template/scripts/seed.contract.test.ts +15 -0
- package/template/scripts/seed.ts +1 -1
- package/template/src/app/api/csrf/route.test.ts +27 -0
- package/template/src/app/api/csrf/route.ts +1 -1
- package/template/src/features/auth/index.ts +2 -1
- package/template/src/features/auth/validators.ts +1 -1
package/README.md
CHANGED
|
@@ -26,11 +26,21 @@ Scaffold a new project. Walks through four interactive prompt groups:
|
|
|
26
26
|
|
|
27
27
|
Outputs a complete Next.js application with auth, design tokens, database schema, agent infrastructure (AGENTS.md, skills, rules, docs), and a working dev environment.
|
|
28
28
|
|
|
29
|
+
**Non-interactive shortcuts**
|
|
30
|
+
|
|
31
|
+
- `mars create my-app --defaults` — same choices as the default interactive path (minimal feature set, local/console providers).
|
|
32
|
+
- `mars create my-app --starter saas` — deterministic **SaaS** preset: enables common product flags (billing, notifications, search, analytics, etc.) with local/`console`/`none` stubs so you are not prompted for every toggle. Use `mars create --help` for the current `--starter` list.
|
|
33
|
+
|
|
34
|
+
`--defaults` and `--starter` cannot be combined.
|
|
35
|
+
|
|
29
36
|
```bash
|
|
30
37
|
mars create my-app
|
|
31
38
|
# → interactive prompts
|
|
32
39
|
# → scaffolds ~200 files
|
|
33
40
|
# → prints next steps
|
|
41
|
+
|
|
42
|
+
mars create my-app --starter saas
|
|
43
|
+
# → same scaffold path, preset feature + service choices (no feature/theme prompts)
|
|
34
44
|
```
|
|
35
45
|
|
|
36
46
|
### `mars add feature <name>`
|
package/dist/index.js
CHANGED
|
@@ -6538,6 +6538,94 @@ async function promptTheme() {
|
|
|
6538
6538
|
};
|
|
6539
6539
|
}
|
|
6540
6540
|
|
|
6541
|
+
// src/prompts/starter-presets.ts
|
|
6542
|
+
var BUILTIN_STARTER_NAMES = ["minimal", "saas"];
|
|
6543
|
+
function minimalPreset() {
|
|
6544
|
+
const features = getDefaultFeatures();
|
|
6545
|
+
return {
|
|
6546
|
+
name: "minimal",
|
|
6547
|
+
displayName: "Minimal",
|
|
6548
|
+
description: "Same defaults as interactive blank setup (matches --defaults)",
|
|
6549
|
+
features,
|
|
6550
|
+
services: getDefaultServices(features),
|
|
6551
|
+
theme: getDefaultTheme()
|
|
6552
|
+
};
|
|
6553
|
+
}
|
|
6554
|
+
function saasPreset() {
|
|
6555
|
+
const features = {
|
|
6556
|
+
auth: true,
|
|
6557
|
+
googleOAuth: false,
|
|
6558
|
+
emailVerification: true,
|
|
6559
|
+
magicLinks: false,
|
|
6560
|
+
twoFactor: false,
|
|
6561
|
+
admin: true,
|
|
6562
|
+
darkMode: true,
|
|
6563
|
+
notifications: true,
|
|
6564
|
+
onboarding: true,
|
|
6565
|
+
multiTenancy: false,
|
|
6566
|
+
teams: false,
|
|
6567
|
+
billing: true,
|
|
6568
|
+
blog: false,
|
|
6569
|
+
seo: true,
|
|
6570
|
+
comingSoon: false,
|
|
6571
|
+
cookieConsent: true,
|
|
6572
|
+
analytics: true,
|
|
6573
|
+
sentry: true,
|
|
6574
|
+
ai: false,
|
|
6575
|
+
fileUpload: true,
|
|
6576
|
+
search: true,
|
|
6577
|
+
realtime: true,
|
|
6578
|
+
commandPalette: true,
|
|
6579
|
+
featureFlags: true
|
|
6580
|
+
};
|
|
6581
|
+
const services = {
|
|
6582
|
+
email: { provider: "console" },
|
|
6583
|
+
storage: { provider: "local" },
|
|
6584
|
+
database: { provider: "local" },
|
|
6585
|
+
payments: { provider: "stripe" },
|
|
6586
|
+
analytics: { provider: "vercel" },
|
|
6587
|
+
monitoring: { provider: "sentry" },
|
|
6588
|
+
ai: { provider: "none" },
|
|
6589
|
+
search: { provider: "postgres" },
|
|
6590
|
+
realtime: { provider: "sse" },
|
|
6591
|
+
jobs: { provider: "none" }
|
|
6592
|
+
};
|
|
6593
|
+
return {
|
|
6594
|
+
name: "saas",
|
|
6595
|
+
displayName: "SaaS",
|
|
6596
|
+
description: "Common product features: billing, notifications, search, analytics, and more (local/console stubs)",
|
|
6597
|
+
features,
|
|
6598
|
+
services,
|
|
6599
|
+
theme: getDefaultTheme()
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
var BUILTIN = {
|
|
6603
|
+
minimal: minimalPreset(),
|
|
6604
|
+
saas: saasPreset()
|
|
6605
|
+
};
|
|
6606
|
+
function listBuiltinStarterNames() {
|
|
6607
|
+
return BUILTIN_STARTER_NAMES;
|
|
6608
|
+
}
|
|
6609
|
+
function getBuiltinStarterDefinition(name) {
|
|
6610
|
+
if (name === "minimal" || name === "saas") {
|
|
6611
|
+
return BUILTIN[name];
|
|
6612
|
+
}
|
|
6613
|
+
return void 0;
|
|
6614
|
+
}
|
|
6615
|
+
function formatUnknownStarterMessage(name) {
|
|
6616
|
+
const valid = listBuiltinStarterNames().join(", ");
|
|
6617
|
+
return `Unknown starter "${name}". Valid starters: ${valid}`;
|
|
6618
|
+
}
|
|
6619
|
+
function applyBuiltinStarterToConfig(base, starterName) {
|
|
6620
|
+
const def = BUILTIN[starterName];
|
|
6621
|
+
return {
|
|
6622
|
+
...base,
|
|
6623
|
+
features: { ...def.features },
|
|
6624
|
+
services: structuredClone(def.services),
|
|
6625
|
+
theme: { ...def.theme }
|
|
6626
|
+
};
|
|
6627
|
+
}
|
|
6628
|
+
|
|
6541
6629
|
// src/generators/scaffold.ts
|
|
6542
6630
|
init_logger();
|
|
6543
6631
|
import fs4 from "fs-extra";
|
|
@@ -7263,7 +7351,13 @@ var PROJECT_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
|
7263
7351
|
var MAX_PROJECT_NAME_LENGTH = 214;
|
|
7264
7352
|
async function createCommand(projectName, options) {
|
|
7265
7353
|
const useDefaults = options?.defaults === true;
|
|
7354
|
+
const starterArg = options?.starter?.trim();
|
|
7355
|
+
const useStarter = Boolean(starterArg);
|
|
7266
7356
|
log.banner();
|
|
7357
|
+
if (useDefaults && useStarter) {
|
|
7358
|
+
log.error("Cannot combine --defaults with --starter. Use one preset path.");
|
|
7359
|
+
return;
|
|
7360
|
+
}
|
|
7267
7361
|
if (projectName) {
|
|
7268
7362
|
if (!PROJECT_NAME_REGEX.test(projectName) && projectName.length > 1) {
|
|
7269
7363
|
log.error("Project name must be lowercase letters, numbers, and hyphens only.");
|
|
@@ -7282,7 +7376,21 @@ async function createCommand(projectName, options) {
|
|
|
7282
7376
|
log.error("Project name is required when using --defaults.");
|
|
7283
7377
|
return;
|
|
7284
7378
|
}
|
|
7285
|
-
|
|
7379
|
+
if (useStarter && starterArg) {
|
|
7380
|
+
const preset = getBuiltinStarterDefinition(starterArg);
|
|
7381
|
+
if (!preset) {
|
|
7382
|
+
log.error(formatUnknownStarterMessage(starterArg));
|
|
7383
|
+
log.info(`Valid starters: ${listBuiltinStarterNames().join(", ")}`);
|
|
7384
|
+
return;
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
if (useStarter && !projectName) {
|
|
7388
|
+
log.error(
|
|
7389
|
+
"Project name is required when using --starter (non-interactive). Example: mars create my-app --starter saas"
|
|
7390
|
+
);
|
|
7391
|
+
return;
|
|
7392
|
+
}
|
|
7393
|
+
const projectInfo = useDefaults || useStarter ? getDefaultProjectInfo(projectName) : await promptProjectInfo(projectName);
|
|
7286
7394
|
if (!projectInfo) return;
|
|
7287
7395
|
const targetDir = resolveProjectPath(projectInfo.name);
|
|
7288
7396
|
if (await fs23.pathExists(targetDir)) {
|
|
@@ -7292,28 +7400,44 @@ async function createCommand(projectName, options) {
|
|
|
7292
7400
|
return;
|
|
7293
7401
|
}
|
|
7294
7402
|
}
|
|
7295
|
-
|
|
7296
|
-
if (
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7403
|
+
let config;
|
|
7404
|
+
if (useStarter && starterArg) {
|
|
7405
|
+
const preset = getBuiltinStarterDefinition(starterArg);
|
|
7406
|
+
config = applyBuiltinStarterToConfig(projectInfo, preset.name);
|
|
7407
|
+
} else if (useDefaults) {
|
|
7408
|
+
const features = getDefaultFeatures();
|
|
7409
|
+
config = {
|
|
7410
|
+
...projectInfo,
|
|
7411
|
+
features,
|
|
7412
|
+
services: getDefaultServices(features),
|
|
7413
|
+
theme: getDefaultTheme()
|
|
7414
|
+
};
|
|
7415
|
+
} else {
|
|
7416
|
+
const features = await promptFeatures();
|
|
7417
|
+
if (!features) return;
|
|
7418
|
+
const services = await promptServices(features);
|
|
7419
|
+
if (!services) return;
|
|
7420
|
+
const theme = await promptTheme();
|
|
7421
|
+
if (!theme) return;
|
|
7422
|
+
config = {
|
|
7423
|
+
...projectInfo,
|
|
7424
|
+
features,
|
|
7425
|
+
services,
|
|
7426
|
+
theme
|
|
7427
|
+
};
|
|
7428
|
+
}
|
|
7307
7429
|
log.blank();
|
|
7308
7430
|
log.title("Summary");
|
|
7309
7431
|
log.info(`Project: ${pc2.bold(config.displayName)} (${config.name})`);
|
|
7310
7432
|
log.info(`URL: ${config.url}`);
|
|
7311
|
-
log.info(`Features: ${countEnabled(features)} enabled`);
|
|
7312
|
-
log.info(`Database: ${services.database.provider}`);
|
|
7313
|
-
log.info(`Email: ${services.email.provider}`);
|
|
7314
|
-
log.info(
|
|
7433
|
+
log.info(`Features: ${countEnabled(config.features)} enabled`);
|
|
7434
|
+
log.info(`Database: ${config.services.database.provider}`);
|
|
7435
|
+
log.info(`Email: ${config.services.email.provider}`);
|
|
7436
|
+
log.info(
|
|
7437
|
+
`Theme: ${config.theme.primaryColor}, ${config.theme.font}, ${config.theme.designDirection}`
|
|
7438
|
+
);
|
|
7315
7439
|
log.blank();
|
|
7316
|
-
if (!useDefaults) {
|
|
7440
|
+
if (!useDefaults && !useStarter) {
|
|
7317
7441
|
const { confirmed } = await prompts5(
|
|
7318
7442
|
{
|
|
7319
7443
|
type: "confirm",
|
|
@@ -7350,10 +7474,12 @@ async function createCommand(projectName, options) {
|
|
|
7350
7474
|
spinner.succeed(`Scaffolded ${pc2.bold(String(fileCount))} files`);
|
|
7351
7475
|
const generatedFeatures = await generateSelectedFeatures(targetDir, config.features);
|
|
7352
7476
|
if (generatedFeatures.length > 0) {
|
|
7353
|
-
log.success(
|
|
7477
|
+
log.success(
|
|
7478
|
+
`Generated ${generatedFeatures.length} additional feature(s): ${generatedFeatures.join(", ")}`
|
|
7479
|
+
);
|
|
7354
7480
|
}
|
|
7355
7481
|
trackEvent("create", {
|
|
7356
|
-
features: Object.keys(features).filter((f) => features[f]).join(",")
|
|
7482
|
+
features: Object.keys(config.features).filter((f) => config.features[f]).join(",")
|
|
7357
7483
|
});
|
|
7358
7484
|
log.blank();
|
|
7359
7485
|
log.title("Next Steps");
|
|
@@ -8771,7 +8897,10 @@ init_feature_flags();
|
|
|
8771
8897
|
init_logger();
|
|
8772
8898
|
var program = new Command();
|
|
8773
8899
|
program.name("mars").description("MARS CLI: scaffold, configure, and maintain SaaS apps").version(getCliVersion(), "-v, --version", "Print the current mars CLI version").option("-u, --upgrade", "Upgrade Mars packages to latest versions");
|
|
8774
|
-
program.command("create").description("Create a new MARS project").argument("[name]", "Project name (kebab-case)").option("--defaults", "Skip prompts, use default configuration").
|
|
8900
|
+
program.command("create").description("Create a new MARS project").argument("[name]", "Project name (kebab-case)").option("--defaults", "Skip prompts, use default configuration").option(
|
|
8901
|
+
"--starter <name>",
|
|
8902
|
+
"Use a named preset (minimal, saas). Skips feature/service/theme prompts; requires project name"
|
|
8903
|
+
).action(async (name, options) => {
|
|
8775
8904
|
await createCommand(name, options);
|
|
8776
8905
|
});
|
|
8777
8906
|
var add = program.command("add").description("Add a feature, page, model, component, or email template to your project");
|
|
@@ -8790,32 +8919,50 @@ add.command("component").description("Create a new UI component").argument("<nam
|
|
|
8790
8919
|
add.command("email").description("Create a new email template").argument("<name>", "Template name in kebab-case (e.g., invoice, team-invite)").action(async (name) => {
|
|
8791
8920
|
await addEmailCommand(name);
|
|
8792
8921
|
});
|
|
8793
|
-
var generate = program.command("generate").description(
|
|
8922
|
+
var generate = program.command("generate").description(
|
|
8923
|
+
"Generate a feature with full scaffolding (pages, components, server logic, config)"
|
|
8924
|
+
);
|
|
8794
8925
|
generate.command("blog").description("Add an MDX-based blog with listing, post pages, RSS feed, and SEO").action(async () => {
|
|
8795
8926
|
await generateBlog(process.cwd());
|
|
8796
8927
|
});
|
|
8797
|
-
generate.command("dark-mode").description(
|
|
8928
|
+
generate.command("dark-mode").description(
|
|
8929
|
+
"Add dark mode with ThemeProvider, theme toggle, system preference detection, and FOUC prevention"
|
|
8930
|
+
).action(async () => {
|
|
8798
8931
|
await generateDarkMode(process.cwd());
|
|
8799
8932
|
});
|
|
8800
|
-
generate.command("notifications").description(
|
|
8933
|
+
generate.command("notifications").description(
|
|
8934
|
+
"Add in-app notifications with Prisma model, API routes, bell component, and polling hook"
|
|
8935
|
+
).action(async () => {
|
|
8801
8936
|
await generateNotifications(process.cwd());
|
|
8802
8937
|
});
|
|
8803
|
-
generate.command("analytics").description(
|
|
8938
|
+
generate.command("analytics").description(
|
|
8939
|
+
"Add analytics with Vercel/PostHog/Google providers, consent banner, and unified tracking API"
|
|
8940
|
+
).action(async () => {
|
|
8804
8941
|
await generateAnalytics(process.cwd());
|
|
8805
8942
|
});
|
|
8806
|
-
generate.command("command-palette").description(
|
|
8943
|
+
generate.command("command-palette").description(
|
|
8944
|
+
"Add a Cmd+K command palette with fuzzy search, action registry, and keyboard navigation"
|
|
8945
|
+
).action(async () => {
|
|
8807
8946
|
await generateCommandPalette(process.cwd());
|
|
8808
8947
|
});
|
|
8809
|
-
generate.command("onboarding").description(
|
|
8948
|
+
generate.command("onboarding").description(
|
|
8949
|
+
"Add multi-step user onboarding with progress tracking, skip/complete, and redirect logic"
|
|
8950
|
+
).action(async () => {
|
|
8810
8951
|
await generateOnboarding(process.cwd());
|
|
8811
8952
|
});
|
|
8812
|
-
generate.command("search").description(
|
|
8953
|
+
generate.command("search").description(
|
|
8954
|
+
"Add search with Postgres full-text, Algolia, or Meilisearch providers and debounced hook"
|
|
8955
|
+
).action(async () => {
|
|
8813
8956
|
await generateSearch(process.cwd());
|
|
8814
8957
|
});
|
|
8815
|
-
generate.command("realtime").description(
|
|
8958
|
+
generate.command("realtime").description(
|
|
8959
|
+
"Add realtime updates with SSE (default), Pusher, or Ably providers and client hooks"
|
|
8960
|
+
).action(async () => {
|
|
8816
8961
|
await generateRealtime(process.cwd());
|
|
8817
8962
|
});
|
|
8818
|
-
generate.command("ai").description(
|
|
8963
|
+
generate.command("ai").description(
|
|
8964
|
+
"Add AI integration with OpenAI/Anthropic providers, streaming chat API, and useChat hook"
|
|
8965
|
+
).action(async () => {
|
|
8819
8966
|
await generateAI(process.cwd());
|
|
8820
8967
|
});
|
|
8821
8968
|
generate.command("cookie-consent").description("Add a cookie consent banner with preferences dialog and useConsent hook").action(async () => {
|
|
@@ -8827,10 +8974,15 @@ generate.command("coming-soon").description("Add a coming soon landing page with
|
|
|
8827
8974
|
generate.command("sentry").description("Add Sentry error tracking with client/server init and ErrorBoundary component").action(async () => {
|
|
8828
8975
|
await generateSentry(process.cwd());
|
|
8829
8976
|
});
|
|
8830
|
-
generate.command("feature-flags").description(
|
|
8977
|
+
generate.command("feature-flags").description(
|
|
8978
|
+
"Add feature flags with local JSON provider, server getFlag, client hook, and FeatureGate component"
|
|
8979
|
+
).action(async () => {
|
|
8831
8980
|
await generateFeatureFlags(process.cwd());
|
|
8832
8981
|
});
|
|
8833
|
-
program.command("configure").description("Configure a service provider (email, payments, storage, etc.)").argument(
|
|
8982
|
+
program.command("configure").description("Configure a service provider (email, payments, storage, etc.)").argument(
|
|
8983
|
+
"[service]",
|
|
8984
|
+
"Service to configure (email, payments, storage, analytics, monitoring, auth)"
|
|
8985
|
+
).action(async (service) => {
|
|
8834
8986
|
await configureCommand(service);
|
|
8835
8987
|
});
|
|
8836
8988
|
program.command("deploy").description("Deploy your project to Vercel").action(async () => {
|
|
@@ -8855,7 +9007,9 @@ program.command("telemetry").description("Manage anonymous usage telemetry").arg
|
|
|
8855
9007
|
program.hook("preAction", (thisCommand) => {
|
|
8856
9008
|
const globalOpts = program.opts();
|
|
8857
9009
|
if (globalOpts.upgrade && thisCommand !== program) {
|
|
8858
|
-
log.error(
|
|
9010
|
+
log.error(
|
|
9011
|
+
'Cannot combine --upgrade with other commands. Use "mars -u" or "mars upgrade" alone.'
|
|
9012
|
+
);
|
|
8859
9013
|
process.exit(1);
|
|
8860
9014
|
}
|
|
8861
9015
|
});
|