@percepta/create 4.1.11 → 4.1.13
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/dist/index.js +158 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/infra/os.blueprint.yaml.template +4 -0
- package/templates/monorepo/auth/package.json +1 -0
- package/templates/monorepo/auth/src/auth.ts +73 -0
- package/templates/webapp/AGENTS.md +1 -1
- package/templates/webapp/README.md +40 -7
- package/templates/webapp/env.example.template +11 -0
- package/templates/webapp/scripts/seed.ts +61 -24
- package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +102 -54
- package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +2 -2
- package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +28 -2
- package/templates/webapp/src/lib/auth/app-auth-mode.ts +20 -0
- package/templates/webapp/src/lib/auth/index.ts +3 -2
- package/templates/webapp/src/lib/auth-client.ts +4 -2
package/dist/index.js
CHANGED
|
@@ -171,7 +171,7 @@ async function writeManifest(dir, manifest) {
|
|
|
171
171
|
async function manifestExists(dir) {
|
|
172
172
|
return fs.pathExists(getManifestPath(dir));
|
|
173
173
|
}
|
|
174
|
-
function derivePlaceholders(appName, appTitle, repoName = appName, customerSlug = repoName, designTheme = DEFAULT_MOSAIC_DESIGN_THEME) {
|
|
174
|
+
function derivePlaceholders(appName, appTitle, repoName = appName, customerSlug = repoName, designTheme = DEFAULT_MOSAIC_DESIGN_THEME, authMode = "username-password") {
|
|
175
175
|
const nameSnake = appName.replace(/-/g, "_");
|
|
176
176
|
const repoNameSnake = repoName.replace(/-/g, "_");
|
|
177
177
|
return {
|
|
@@ -183,7 +183,8 @@ function derivePlaceholders(appName, appTitle, repoName = appName, customerSlug
|
|
|
183
183
|
__REPO_NAME__: repoName,
|
|
184
184
|
__REPO_NAME_SNAKE__: repoNameSnake,
|
|
185
185
|
__CUSTOMER_SLUG__: customerSlug,
|
|
186
|
-
__MOSAIC_DESIGN_THEME__: designTheme
|
|
186
|
+
__MOSAIC_DESIGN_THEME__: designTheme,
|
|
187
|
+
__AUTH_MODE__: authMode
|
|
187
188
|
};
|
|
188
189
|
}
|
|
189
190
|
function resolveMosaicRepoPath(options) {
|
|
@@ -225,6 +226,14 @@ const VALID_PROJECT_TYPES = [
|
|
|
225
226
|
"webapp",
|
|
226
227
|
"library"
|
|
227
228
|
];
|
|
229
|
+
const VALID_AUTH_MODES = [
|
|
230
|
+
"username-password",
|
|
231
|
+
"google",
|
|
232
|
+
"okta"
|
|
233
|
+
];
|
|
234
|
+
function isValidAuthMode(value) {
|
|
235
|
+
return typeof value === "string" && VALID_AUTH_MODES.includes(value);
|
|
236
|
+
}
|
|
228
237
|
function isValidProjectType(value) {
|
|
229
238
|
return typeof value === "string" && VALID_PROJECT_TYPES.includes(value);
|
|
230
239
|
}
|
|
@@ -280,9 +289,71 @@ async function resolveDesignTheme(projectType, defaults) {
|
|
|
280
289
|
if (defaults.projectType && defaults.name) return DEFAULT_MOSAIC_DESIGN_THEME;
|
|
281
290
|
return promptDesignTheme();
|
|
282
291
|
}
|
|
292
|
+
async function promptWorkspaceAuthMode() {
|
|
293
|
+
const { authMode } = await inquirer.prompt([{
|
|
294
|
+
type: "rawlist",
|
|
295
|
+
name: "authMode",
|
|
296
|
+
message: "Workspace auth setup?",
|
|
297
|
+
default: "username-password",
|
|
298
|
+
choices: [
|
|
299
|
+
{
|
|
300
|
+
name: "Username/password — local Better Auth credentials",
|
|
301
|
+
value: "username-password"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "Google — Google OAuth via Better Auth",
|
|
305
|
+
value: "google"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "Okta — Okta OIDC via Better Auth",
|
|
309
|
+
value: "okta"
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
}]);
|
|
313
|
+
return authMode;
|
|
314
|
+
}
|
|
315
|
+
async function promptAppAuthMode(defaultAuthMode) {
|
|
316
|
+
const overrideChoices = [
|
|
317
|
+
{
|
|
318
|
+
name: "Username/password — local Better Auth credentials",
|
|
319
|
+
value: "username-password"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "Google — Google OAuth via Better Auth",
|
|
323
|
+
value: "google"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "Okta — Okta OIDC via Better Auth",
|
|
327
|
+
value: "okta"
|
|
328
|
+
}
|
|
329
|
+
].filter((choice) => choice.value !== defaultAuthMode);
|
|
330
|
+
const { authMode } = await inquirer.prompt([{
|
|
331
|
+
type: "rawlist",
|
|
332
|
+
name: "authMode",
|
|
333
|
+
message: "App auth setup?",
|
|
334
|
+
default: defaultAuthMode,
|
|
335
|
+
choices: [{
|
|
336
|
+
name: "Use workspace default",
|
|
337
|
+
value: defaultAuthMode
|
|
338
|
+
}, ...overrideChoices]
|
|
339
|
+
}]);
|
|
340
|
+
return authMode;
|
|
341
|
+
}
|
|
342
|
+
async function resolveWorkspaceAuthMode(defaults) {
|
|
343
|
+
if (defaults.authMode) return defaults.authMode;
|
|
344
|
+
if (defaults.projectType && defaults.name) return "username-password";
|
|
345
|
+
return promptWorkspaceAuthMode();
|
|
346
|
+
}
|
|
347
|
+
async function resolvePackageAuthMode(projectType, defaults) {
|
|
348
|
+
if (projectType !== "webapp") return void 0;
|
|
349
|
+
if (defaults.authMode) return defaults.authMode;
|
|
350
|
+
const workspaceAuthMode = defaults.workspaceAuthMode ?? "username-password";
|
|
351
|
+
if (defaults.projectType && defaults.name) return workspaceAuthMode;
|
|
352
|
+
return promptAppAuthMode(workspaceAuthMode);
|
|
353
|
+
}
|
|
283
354
|
/**
|
|
284
|
-
* Outside a monorepo we collect
|
|
285
|
-
* "is it a webapp?" Y/n. Yes (default) → monorepo + webapp, no → bare
|
|
355
|
+
* Outside a monorepo we collect monorepo-level settings first, then ask a
|
|
356
|
+
* single "is it a webapp?" Y/n. Yes (default) → monorepo + webapp, no → bare
|
|
286
357
|
* monorepo. Library at the top level is rare enough that we leave it as a
|
|
287
358
|
* `--type library` flag rather than a third prompt option. The repo name is
|
|
288
359
|
* collected separately from any initial package name so a customer monorepo
|
|
@@ -330,6 +401,7 @@ async function promptProjectDetails(defaults) {
|
|
|
330
401
|
const customerSlug = await resolveCustomerSlug(defaults);
|
|
331
402
|
const repoName = defaults.repoName || (defaults.projectType === "monorepo" ? defaults.name : void 0) || await promptName("Repo name?", `${customerSlug}-os`);
|
|
332
403
|
const repoTitle = toTitleCase(repoName);
|
|
404
|
+
const authMode = await resolveWorkspaceAuthMode(defaults);
|
|
333
405
|
projectType = defaults.projectType ?? await promptOutsideMonorepoType();
|
|
334
406
|
await defaults.beforeNamePrompt?.(projectType);
|
|
335
407
|
if (projectType === "monorepo") {
|
|
@@ -343,6 +415,7 @@ async function promptProjectDetails(defaults) {
|
|
|
343
415
|
title: finalTitle,
|
|
344
416
|
installDeps: !defaults.skipInstall,
|
|
345
417
|
customerSlug,
|
|
418
|
+
authMode,
|
|
346
419
|
monorepoName: repoName,
|
|
347
420
|
monorepoTitle: repoTitle
|
|
348
421
|
};
|
|
@@ -360,12 +433,14 @@ async function promptProjectDetails(defaults) {
|
|
|
360
433
|
installDeps: !defaults.skipInstall,
|
|
361
434
|
customerSlug,
|
|
362
435
|
designTheme,
|
|
436
|
+
authMode,
|
|
363
437
|
monorepoName: repoName,
|
|
364
438
|
monorepoTitle: repoTitle
|
|
365
439
|
};
|
|
366
440
|
}
|
|
367
441
|
const finalTitle = finalName ? toTitleCase(finalName) : "";
|
|
368
442
|
const designTheme = await resolveDesignTheme(projectType, defaults);
|
|
443
|
+
const authMode = await resolvePackageAuthMode(projectType, defaults);
|
|
369
444
|
const finalDirectory = !inMonorepo && finalName ? path.resolve(cwd, finalName) : "";
|
|
370
445
|
return {
|
|
371
446
|
projectType,
|
|
@@ -373,7 +448,8 @@ async function promptProjectDetails(defaults) {
|
|
|
373
448
|
name: finalName,
|
|
374
449
|
title: finalTitle,
|
|
375
450
|
installDeps: !defaults.skipInstall,
|
|
376
|
-
designTheme
|
|
451
|
+
designTheme,
|
|
452
|
+
authMode
|
|
377
453
|
};
|
|
378
454
|
}
|
|
379
455
|
//#endregion
|
|
@@ -419,7 +495,9 @@ const PLACEHOLDERS = {
|
|
|
419
495
|
__CUSTOMER_SLUG__: "customerSlug",
|
|
420
496
|
__CREATE_PACKAGE__: "createPackage",
|
|
421
497
|
__CREATE_VERSION__: "createVersion",
|
|
422
|
-
__MOSAIC_DESIGN_THEME__: "designTheme"
|
|
498
|
+
__MOSAIC_DESIGN_THEME__: "designTheme",
|
|
499
|
+
__AUTH_MODE__: "authMode",
|
|
500
|
+
__AUTH_MODE_LABEL__: "authModeLabel"
|
|
423
501
|
};
|
|
424
502
|
const SKIP_DIRS = new Set([
|
|
425
503
|
"node_modules",
|
|
@@ -537,11 +615,12 @@ const WORKSPACE_MANIFEST_FILENAME = ".mosaic-workspace.json";
|
|
|
537
615
|
function getWorkspaceManifestPath(rootDir) {
|
|
538
616
|
return path.join(rootDir, WORKSPACE_MANIFEST_FILENAME);
|
|
539
617
|
}
|
|
540
|
-
function createWorkspaceManifest({ customerSlug, createdAt = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
618
|
+
function createWorkspaceManifest({ customerSlug, authMode, createdAt = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
541
619
|
const createPackage = readCreatePackageMetadata();
|
|
542
620
|
return {
|
|
543
621
|
schemaVersion: 1,
|
|
544
622
|
customerSlug,
|
|
623
|
+
authMode,
|
|
545
624
|
createPackage: createPackage.name,
|
|
546
625
|
createVersion: createPackage.version,
|
|
547
626
|
monorepoTemplateVersion: getTemplateVersion("monorepo"),
|
|
@@ -552,6 +631,9 @@ function createWorkspaceManifest({ customerSlug, createdAt = (/* @__PURE__ */ ne
|
|
|
552
631
|
createdAt
|
|
553
632
|
};
|
|
554
633
|
}
|
|
634
|
+
function getWorkspaceAuthMode(manifest) {
|
|
635
|
+
return manifest?.authMode ?? "username-password";
|
|
636
|
+
}
|
|
555
637
|
async function readWorkspaceManifest(rootDir) {
|
|
556
638
|
const manifestPath = getWorkspaceManifestPath(rootDir);
|
|
557
639
|
if (!await fs.pathExists(manifestPath)) return null;
|
|
@@ -778,13 +860,13 @@ async function writeMosaicFiles(packageDir, config, projectType, templateVersion
|
|
|
778
860
|
templateVersion,
|
|
779
861
|
templateCommit,
|
|
780
862
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
781
|
-
placeholders: derivePlaceholders(config.name, config.title, config.repoName, config.customerSlug, config.designTheme),
|
|
863
|
+
placeholders: derivePlaceholders(config.name, config.title, config.repoName, config.customerSlug, config.designTheme, config.authMode),
|
|
782
864
|
source: { templatePath: `packages/blueberry/templates/${projectType}` }
|
|
783
865
|
});
|
|
784
866
|
const notesPath = path.join(packageDir, "mosaic-template-notes.md");
|
|
785
867
|
await fs.writeFile(notesPath, `# Mosaic Divergence Notes\n\nDocument intentional differences from the ${projectType} template here.\nClaude reads this file during sync to preserve your customizations.\n\n## Intentional Divergences\n\n_None yet — freshly created from template._\n`);
|
|
786
868
|
}
|
|
787
|
-
function buildAppConfig(name, title = toTitleCase(name), repoName = name, customerSlug = repoName, designTheme = DEFAULT_MOSAIC_DESIGN_THEME) {
|
|
869
|
+
function buildAppConfig(name, title = toTitleCase(name), repoName = name, customerSlug = repoName, designTheme = DEFAULT_MOSAIC_DESIGN_THEME, authMode = "username-password") {
|
|
788
870
|
const createPackage = readCreatePackageMetadata();
|
|
789
871
|
return {
|
|
790
872
|
name,
|
|
@@ -797,7 +879,9 @@ function buildAppConfig(name, title = toTitleCase(name), repoName = name, custom
|
|
|
797
879
|
customerSlug,
|
|
798
880
|
createPackage: createPackage.name,
|
|
799
881
|
createVersion: createPackage.version,
|
|
800
|
-
designTheme
|
|
882
|
+
designTheme,
|
|
883
|
+
authMode,
|
|
884
|
+
authModeLabel: getAuthModeLabel(authMode)
|
|
801
885
|
};
|
|
802
886
|
}
|
|
803
887
|
/** Copy the monorepo template into `targetDir` and replace its placeholders. */
|
|
@@ -942,8 +1026,9 @@ async function runGeneratedProjectChecks(args) {
|
|
|
942
1026
|
* Gated on `installSucceeded` because starting setup without node_modules
|
|
943
1027
|
* leaves an orphan Docker container.
|
|
944
1028
|
*/
|
|
945
|
-
async function maybeAutoRunWebapp(packageDir, monorepoRoot, projectType, installSucceeded) {
|
|
1029
|
+
async function maybeAutoRunWebapp(packageDir, monorepoRoot, projectType, authMode, installSucceeded) {
|
|
946
1030
|
if (!packageDir || projectType !== "webapp" || !installSucceeded) return false;
|
|
1031
|
+
if (authMode !== "username-password") return false;
|
|
947
1032
|
return autoRunWebapp(packageDir, monorepoRoot);
|
|
948
1033
|
}
|
|
949
1034
|
function getProjectTypeLabel(projectType) {
|
|
@@ -954,6 +1039,14 @@ function getProjectTypeLabel(projectType) {
|
|
|
954
1039
|
default: throw new Error(`Unknown project type: ${String(projectType)}`);
|
|
955
1040
|
}
|
|
956
1041
|
}
|
|
1042
|
+
function getAuthModeLabel(authMode) {
|
|
1043
|
+
switch (authMode) {
|
|
1044
|
+
case "username-password": return "Username/password";
|
|
1045
|
+
case "google": return "Google OAuth";
|
|
1046
|
+
case "okta": return "Okta OIDC";
|
|
1047
|
+
default: throw new Error(`Unknown auth mode: ${String(authMode)}`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
957
1050
|
function requireNpmTokenForWebappInstall(projectType, installDeps) {
|
|
958
1051
|
if (projectType !== "webapp" || !installDeps || process.env.NPM_TOKEN) return;
|
|
959
1052
|
console.log();
|
|
@@ -996,6 +1089,10 @@ async function createProject(options) {
|
|
|
996
1089
|
console.error(chalk.red(`Error: Invalid design theme "${options.theme}". Valid themes are: ${VALID_MOSAIC_DESIGN_THEMES.join(", ")}`));
|
|
997
1090
|
process.exit(1);
|
|
998
1091
|
}
|
|
1092
|
+
if (options.auth !== void 0 && !isValidAuthMode(options.auth)) {
|
|
1093
|
+
console.error(chalk.red(`Error: Invalid auth setup "${options.auth}". Valid auth setups are: ${VALID_AUTH_MODES.join(", ")}`));
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
}
|
|
999
1096
|
console.log();
|
|
1000
1097
|
console.log(chalk.bold("Creating a new Mosaic package..."));
|
|
1001
1098
|
console.log();
|
|
@@ -1012,8 +1109,10 @@ async function createProject(options) {
|
|
|
1012
1109
|
else console.log(chalk.dim(" No monorepo detected. A new monorepo will be created."));
|
|
1013
1110
|
console.log();
|
|
1014
1111
|
const workspaceManifest = monorepoContext.found ? await readWorkspaceManifest(monorepoContext.rootDir) : null;
|
|
1112
|
+
const workspaceAuthMode = getWorkspaceAuthMode(workspaceManifest);
|
|
1015
1113
|
if (workspaceManifest) {
|
|
1016
1114
|
console.log(chalk.dim(" Workspace create version:"), chalk.cyan(`${workspaceManifest.createPackage}@${workspaceManifest.createVersion}`));
|
|
1115
|
+
console.log(chalk.dim(" Workspace auth setup:"), chalk.cyan(getAuthModeLabel(workspaceAuthMode)));
|
|
1017
1116
|
console.log();
|
|
1018
1117
|
} else if (monorepoContext.found) {
|
|
1019
1118
|
console.log(chalk.yellow("!"), "No .mosaic-workspace.json found; using this CLI's bundled templates.");
|
|
@@ -1062,6 +1161,7 @@ async function createProject(options) {
|
|
|
1062
1161
|
installDeps: !options.skipInstall,
|
|
1063
1162
|
customerSlug: monorepoContext.found ? void 0 : customerSlug ?? kebabRepoName,
|
|
1064
1163
|
designTheme: projectType === "webapp" ? options.theme ?? "modern" : void 0,
|
|
1164
|
+
authMode: options.auth ?? (monorepoContext.found ? workspaceAuthMode : "username-password"),
|
|
1065
1165
|
monorepoName: monorepoContext.found ? void 0 : kebabRepoName,
|
|
1066
1166
|
monorepoTitle: monorepoContext.found ? void 0 : toTitleCase(kebabRepoName)
|
|
1067
1167
|
};
|
|
@@ -1071,6 +1171,8 @@ async function createProject(options) {
|
|
|
1071
1171
|
name: projectName ? toKebabCase(projectName) : void 0,
|
|
1072
1172
|
customerSlug,
|
|
1073
1173
|
designTheme: options.theme,
|
|
1174
|
+
authMode: options.auth,
|
|
1175
|
+
workspaceAuthMode: monorepoContext.found ? workspaceAuthMode : void 0,
|
|
1074
1176
|
repoName: repoName ? toKebabCase(repoName) : void 0,
|
|
1075
1177
|
skipInstall: options.skipInstall,
|
|
1076
1178
|
monorepoContext,
|
|
@@ -1082,13 +1184,15 @@ async function createProject(options) {
|
|
|
1082
1184
|
});
|
|
1083
1185
|
if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) answers.directory = path.join(monorepoContext.packageDir, answers.name);
|
|
1084
1186
|
}
|
|
1187
|
+
const selectedAuthMode = answers.authMode ?? (monorepoContext.found ? workspaceAuthMode : "username-password");
|
|
1188
|
+
answers.authMode = selectedAuthMode;
|
|
1085
1189
|
const monorepoName = answers.monorepoName ?? answers.name;
|
|
1086
1190
|
const monorepoTitle = answers.monorepoTitle ?? toTitleCase(monorepoName);
|
|
1087
1191
|
const newWorkspaceCustomerSlug = answers.customerSlug ?? monorepoName;
|
|
1088
|
-
const monorepoConfig = buildAppConfig(monorepoName, monorepoTitle, monorepoName, newWorkspaceCustomerSlug, DEFAULT_MOSAIC_DESIGN_THEME);
|
|
1192
|
+
const monorepoConfig = buildAppConfig(monorepoName, monorepoTitle, monorepoName, newWorkspaceCustomerSlug, DEFAULT_MOSAIC_DESIGN_THEME, selectedAuthMode);
|
|
1089
1193
|
const configRepoName = monorepoContext.found ? path.basename(monorepoContext.rootDir) : monorepoName;
|
|
1090
1194
|
const effectiveCustomerSlug = monorepoContext.found ? workspaceManifest?.customerSlug ?? configRepoName : newWorkspaceCustomerSlug;
|
|
1091
|
-
const config = buildAppConfig(answers.name, answers.title, configRepoName, effectiveCustomerSlug, answers.designTheme);
|
|
1195
|
+
const config = buildAppConfig(answers.name, answers.title, configRepoName, effectiveCustomerSlug, answers.designTheme, selectedAuthMode);
|
|
1092
1196
|
const typeLabel = getProjectTypeLabel(answers.projectType);
|
|
1093
1197
|
if (monorepoContext.found) {
|
|
1094
1198
|
const monorepoRoot = monorepoContext.rootDir;
|
|
@@ -1100,6 +1204,8 @@ async function createProject(options) {
|
|
|
1100
1204
|
if (answers.projectType === "webapp") {
|
|
1101
1205
|
console.log(chalk.dim(" Database:"), config.dbName);
|
|
1102
1206
|
console.log(chalk.dim(" Design theme:"), config.designTheme);
|
|
1207
|
+
console.log(chalk.dim(" App auth setup:"), config.authModeLabel);
|
|
1208
|
+
if (selectedAuthMode !== workspaceAuthMode) console.log(chalk.dim(" Workspace auth default:"), getAuthModeLabel(workspaceAuthMode));
|
|
1103
1209
|
}
|
|
1104
1210
|
console.log();
|
|
1105
1211
|
if (await fs.pathExists(packageDir)) {
|
|
@@ -1128,7 +1234,7 @@ async function createProject(options) {
|
|
|
1128
1234
|
console.log();
|
|
1129
1235
|
console.log(chalk.green("✔"), chalk.bold(`Created ${typeLabel} at`), chalk.cyan(path.relative(monorepoRoot, packageDir)));
|
|
1130
1236
|
console.log();
|
|
1131
|
-
if (await maybeAutoRunWebapp(packageDir, monorepoRoot, answers.projectType, installSucceeded && checksSucceeded)) return;
|
|
1237
|
+
if (await maybeAutoRunWebapp(packageDir, monorepoRoot, answers.projectType, answers.authMode, installSucceeded && checksSucceeded)) return;
|
|
1132
1238
|
printNextStepsExisting(answers, packageDir, !installSucceeded);
|
|
1133
1239
|
} else {
|
|
1134
1240
|
const isBareMonorepo = answers.projectType === "monorepo";
|
|
@@ -1140,11 +1246,13 @@ async function createProject(options) {
|
|
|
1140
1246
|
console.log(chalk.dim(" Customer:"), newWorkspaceCustomerSlug);
|
|
1141
1247
|
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
1142
1248
|
console.log(chalk.dim(" Title:"), monorepoConfig.title);
|
|
1249
|
+
console.log(chalk.dim(" Workspace auth setup:"), monorepoConfig.authModeLabel);
|
|
1143
1250
|
} else {
|
|
1144
1251
|
console.log(chalk.dim(" Package type:"), typeLabel);
|
|
1145
1252
|
console.log(chalk.dim(" Monorepo directory:"), monorepoRoot);
|
|
1146
1253
|
console.log(chalk.dim(" Customer:"), newWorkspaceCustomerSlug);
|
|
1147
1254
|
console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
|
|
1255
|
+
console.log(chalk.dim(" Workspace auth setup:"), monorepoConfig.authModeLabel);
|
|
1148
1256
|
console.log(chalk.dim(" Package:"), `packages/${answers.name}/`);
|
|
1149
1257
|
console.log(chalk.dim(" Name:"), config.name);
|
|
1150
1258
|
console.log(chalk.dim(" Title:"), config.title);
|
|
@@ -1161,7 +1269,10 @@ async function createProject(options) {
|
|
|
1161
1269
|
}
|
|
1162
1270
|
}
|
|
1163
1271
|
await scaffoldMonorepo(monorepoRoot, monorepoConfig);
|
|
1164
|
-
const newWorkspaceManifest = createWorkspaceManifest({
|
|
1272
|
+
const newWorkspaceManifest = createWorkspaceManifest({
|
|
1273
|
+
customerSlug: newWorkspaceCustomerSlug,
|
|
1274
|
+
authMode: selectedAuthMode
|
|
1275
|
+
});
|
|
1165
1276
|
await writeWorkspaceManifest(monorepoRoot, newWorkspaceManifest);
|
|
1166
1277
|
if (packageDir && answers.projectType !== "monorepo") await addPackageToMonorepo({
|
|
1167
1278
|
packageDir,
|
|
@@ -1184,7 +1295,7 @@ async function createProject(options) {
|
|
|
1184
1295
|
console.log(chalk.green("✔"), chalk.bold(isBareMonorepo ? `Created ${typeLabel} at` : "Created monorepo at"), chalk.cyan(monorepoRoot));
|
|
1185
1296
|
if (!isBareMonorepo) console.log(chalk.green("✔"), chalk.bold(`Created ${typeLabel} at`), chalk.cyan(`packages/${answers.name}/`));
|
|
1186
1297
|
console.log();
|
|
1187
|
-
if (await maybeAutoRunWebapp(packageDir, monorepoRoot, answers.projectType, installSucceeded && checksSucceeded)) return;
|
|
1298
|
+
if (await maybeAutoRunWebapp(packageDir, monorepoRoot, answers.projectType, answers.authMode, installSucceeded && checksSucceeded)) return;
|
|
1188
1299
|
printNextStepsNew(answers, monorepoRoot, !installSucceeded);
|
|
1189
1300
|
}
|
|
1190
1301
|
}
|
|
@@ -1209,6 +1320,7 @@ function printWebappNextSteps(params) {
|
|
|
1209
1320
|
const pkgFromRoot = shPath(packageRelativePathFromRoot ?? `packages/${answers.name}`) || ".";
|
|
1210
1321
|
const setupStep = "pnpm run setup";
|
|
1211
1322
|
const devStep = "pnpm dev";
|
|
1323
|
+
printAuthSetupNotes(answers);
|
|
1212
1324
|
if (variant === "new") {
|
|
1213
1325
|
const oneLinerParts = [];
|
|
1214
1326
|
if (repoRel !== ".") oneLinerParts.push(`cd ${repoRel}`);
|
|
@@ -1247,6 +1359,32 @@ function printWebappNextSteps(params) {
|
|
|
1247
1359
|
console.log(chalk.dim(` ${step++}.`), `cd ${pkgFromRoot}`);
|
|
1248
1360
|
console.log(chalk.dim(` ${step++}.`), devStep);
|
|
1249
1361
|
}
|
|
1362
|
+
function printAuthSetupNotes(answers) {
|
|
1363
|
+
if (!answers.authMode) return;
|
|
1364
|
+
switch (answers.authMode) {
|
|
1365
|
+
case "username-password": return;
|
|
1366
|
+
case "google":
|
|
1367
|
+
console.log();
|
|
1368
|
+
console.log(chalk.bold("Google auth setup:"));
|
|
1369
|
+
console.log(chalk.dim(" Fill in"), chalk.cyan("GOOGLE_CLIENT_ID"), chalk.dim("and"), chalk.cyan("GOOGLE_CLIENT_SECRET"), chalk.dim("in each app environment before OAuth sign-in."));
|
|
1370
|
+
console.log(chalk.dim(" Optional: set"), chalk.cyan("GOOGLE_HOSTED_DOMAIN"), chalk.dim("to hint/restrict the Google Workspace domain."));
|
|
1371
|
+
console.log(chalk.dim(" Register redirect URIs:"), chalk.cyan("http://localhost:3000/api/auth/callback/google"), chalk.dim("and"), chalk.cyan("https://<app-host>/api/auth/callback/google"));
|
|
1372
|
+
console.log(chalk.dim(" Provider users still need SpiceDB app access after sign-in; seed or grant access for their user."));
|
|
1373
|
+
return;
|
|
1374
|
+
case "okta":
|
|
1375
|
+
console.log();
|
|
1376
|
+
console.log(chalk.bold("Okta auth setup:"));
|
|
1377
|
+
console.log(chalk.dim(" Fill in"), chalk.cyan("OKTA_CLIENT_ID"), chalk.dim(","), chalk.cyan("OKTA_CLIENT_SECRET"), chalk.dim("and"), chalk.cyan("OKTA_ISSUER"), chalk.dim("in each app environment before OAuth sign-in."));
|
|
1378
|
+
console.log(chalk.dim(" Example issuer:"), chalk.cyan("https://dev-xxxxx.okta.com/oauth2/default"));
|
|
1379
|
+
console.log(chalk.dim(" Register redirect URIs:"), chalk.cyan("http://localhost:3000/api/auth/oauth2/callback/okta"), chalk.dim("and"), chalk.cyan("https://<app-host>/api/auth/oauth2/callback/okta"));
|
|
1380
|
+
console.log(chalk.dim(" Provider users still need SpiceDB app access after sign-in; seed or grant access for their user."));
|
|
1381
|
+
return;
|
|
1382
|
+
default: {
|
|
1383
|
+
const exhaustiveCheck = answers.authMode;
|
|
1384
|
+
throw new Error(`Unknown auth mode: ${String(exhaustiveCheck)}`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1250
1388
|
async function warnIfMissingRootNpmrc(rootDir) {
|
|
1251
1389
|
const rootNpmrc = path.join(rootDir, ".npmrc");
|
|
1252
1390
|
let contents = "";
|
|
@@ -1268,6 +1406,7 @@ function printNextStepsNew(answers, targetDir, installNeeded) {
|
|
|
1268
1406
|
console.log();
|
|
1269
1407
|
switch (answers.projectType) {
|
|
1270
1408
|
case "monorepo": {
|
|
1409
|
+
printAuthSetupNotes(answers);
|
|
1271
1410
|
let step = 1;
|
|
1272
1411
|
const repoRel = shPath(relativePath);
|
|
1273
1412
|
if (repoRel !== ".") console.log(chalk.dim(` ${step++}.`), `cd ${repoRel}`);
|
|
@@ -1285,6 +1424,7 @@ function printNextStepsNew(answers, targetDir, installNeeded) {
|
|
|
1285
1424
|
});
|
|
1286
1425
|
break;
|
|
1287
1426
|
case "library": {
|
|
1427
|
+
printAuthSetupNotes(answers);
|
|
1288
1428
|
let step = 1;
|
|
1289
1429
|
const repoRel = shPath(relativePath);
|
|
1290
1430
|
if (repoRel !== ".") console.log(chalk.dim(` ${step++}.`), `cd ${repoRel}`);
|
|
@@ -1342,8 +1482,8 @@ function printNextStepsExisting(answers, packageDir, installNeeded) {
|
|
|
1342
1482
|
//#region src/index.ts
|
|
1343
1483
|
const packageJson = readCreatePackageMetadata();
|
|
1344
1484
|
program.name("create").description("Scaffold and manage Mosaic packages").version(packageJson.version);
|
|
1345
|
-
program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "Package/app name").option("--customer <slug>", "Customer slug for the new monorepo").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--repo-name <name>", "Repository name when creating a new monorepo").option("--cwd <dir>", "Run create as if started from this directory").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(createProject);
|
|
1346
|
-
program.command("add").description("Add a Mosaic package to the current monorepo").argument("[typeOrName]", "Package type (webapp/library) or package name").argument("[name]", "Package/app name").option("-t, --type <type>", "Package type: webapp or library").option("--name <name>", "Package/app name").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(async (typeOrName, name, options) => {
|
|
1485
|
+
program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "Package/app name").option("--customer <slug>", "Customer slug for the new monorepo").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--auth <mode>", "Workspace auth setup: username-password, google, or okta").option("--repo-name <name>", "Repository name when creating a new monorepo").option("--cwd <dir>", "Run create as if started from this directory").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(createProject);
|
|
1486
|
+
program.command("add").description("Add a Mosaic package to the current monorepo").argument("[typeOrName]", "Package type (webapp/library) or package name").argument("[name]", "Package/app name").option("-t, --type <type>", "Package type: webapp or library").option("--name <name>", "Package/app name").option("--theme <theme>", "Webapp design theme: modern, paper, or dense").option("--auth <mode>", "App auth setup override: username-password, google, or okta").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(async (typeOrName, name, options) => {
|
|
1347
1487
|
let projectType = options.type;
|
|
1348
1488
|
let projectName = options.name;
|
|
1349
1489
|
if (typeOrName === "webapp" || typeOrName === "library") {
|