@percepta/create 3.0.1 → 3.1.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.
Files changed (47) hide show
  1. package/README.md +16 -9
  2. package/dist/{chunk-GEVZERMP.js → chunk-CG7IJSB4.js} +33 -2
  3. package/dist/{chunk-R4FWPE4A.js → chunk-DCM7JOSC.js} +2 -2
  4. package/dist/index.js +281 -82
  5. package/dist/{init-Z4VGBHAK.js → init-XDWSYHYK.js} +1 -1
  6. package/dist/{status-MITGDLTT.js → status-BTHGN6QH.js} +1 -1
  7. package/dist/{sync-J4SFZHDX.js → sync-3Q27L7XZ.js} +1 -1
  8. package/dist/{upstream-AQI7P4EU.js → upstream-C5KFAHVR.js} +1 -1
  9. package/package.json +3 -2
  10. package/templates/monorepo/gitignore.template +1 -0
  11. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +3 -2
  12. package/templates/webapp/AGENTS.md +8 -2
  13. package/templates/webapp/Dockerfile +0 -1
  14. package/templates/webapp/README.md +1 -0
  15. package/templates/webapp/agent-skills/database.md +1 -0
  16. package/templates/webapp/agent-skills/deploy.md +45 -32
  17. package/templates/webapp/agent-skills/oneshot.md +3 -3
  18. package/templates/webapp/deploy/README.md +32 -6
  19. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +0 -2
  20. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +28 -31
  21. package/templates/webapp/drizzle.config.ts +15 -6
  22. package/templates/webapp/env.example.template +1 -0
  23. package/templates/webapp/eslint.config.mjs +8 -0
  24. package/templates/webapp/gitignore.template +1 -0
  25. package/templates/webapp/package.json.template +6 -6
  26. package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +495 -0
  27. package/templates/webapp/scripts/seed.ts +1 -1
  28. package/templates/webapp/scripts/setup-database.ts +16 -1
  29. package/templates/webapp/scripts/start.sh +3 -2
  30. package/templates/webapp/src/app/(app)/layout.tsx +1 -5
  31. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +11 -1
  32. package/templates/webapp/src/app/(auth)/auth/signup/CredentialsSignUpForm.tsx +113 -0
  33. package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +30 -0
  34. package/templates/webapp/src/app/global-error.tsx +3 -1
  35. package/templates/webapp/src/components/FaroProvider.tsx +2 -4
  36. package/templates/webapp/src/components/form/FormItem.tsx +2 -2
  37. package/templates/webapp/src/config/getEnvConfig.ts +1 -0
  38. package/templates/webapp/src/drizzle/db.ts +5 -1
  39. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +3 -3
  40. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +7 -19
  41. package/templates/webapp/src/drizzle/searchPath.test.ts +21 -0
  42. package/templates/webapp/src/drizzle/searchPath.ts +16 -0
  43. package/templates/webapp/src/drizzle/ssl.ts +5 -0
  44. package/templates/webapp/src/lib/auth/index.ts +1 -1
  45. package/templates/webapp/src/lib/auth-client.ts +1 -1
  46. package/templates/webapp/src/services/observability/initFaro.ts +1 -1
  47. package/templates/webapp/src/styles/globals.css +0 -7
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  toSnakeCase,
8
8
  toTitleCase,
9
9
  validateProjectName
10
- } from "./chunk-GEVZERMP.js";
10
+ } from "./chunk-CG7IJSB4.js";
11
11
  import {
12
12
  derivePlaceholders,
13
13
  writeManifest
@@ -17,9 +17,9 @@ import {
17
17
  import { program } from "commander";
18
18
 
19
19
  // src/commands/create.ts
20
- import path6 from "path";
20
+ import path7 from "path";
21
21
  import { fileURLToPath as fileURLToPath2 } from "url";
22
- import fs6 from "fs-extra";
22
+ import fs7 from "fs-extra";
23
23
  import chalk from "chalk";
24
24
  import ora from "ora";
25
25
  import { execSync, spawn } from "child_process";
@@ -262,11 +262,28 @@ async function generateEnvLocal(packageDir) {
262
262
  const examplePath = path4.join(packageDir, ".env.example");
263
263
  const localPath = path4.join(packageDir, ".env.local");
264
264
  if (!await fs4.pathExists(examplePath)) return;
265
- if (await fs4.pathExists(localPath)) return;
266
- const authSecret = randomBytes(32).toString("base64");
267
- const encKey = randomBytes(16).toString("hex");
268
- const content = (await fs4.readFile(examplePath, "utf-8")).replace(/^BETTER_AUTH_SECRET=.*$/m, `BETTER_AUTH_SECRET=${authSecret}`).replace(/^ENCRYPTION_SECRET_KEY=.*$/m, `ENCRYPTION_SECRET_KEY=${encKey}`);
269
- await fs4.writeFile(localPath, content);
265
+ if (!await fs4.pathExists(localPath)) {
266
+ const authSecret = randomBytes(32).toString("base64");
267
+ const encKey = randomBytes(16).toString("hex");
268
+ const content = (await fs4.readFile(examplePath, "utf-8")).replace(/^BETTER_AUTH_SECRET=.*$/m, `BETTER_AUTH_SECRET=${authSecret}`).replace(/^ENCRYPTION_SECRET_KEY=.*$/m, `ENCRYPTION_SECRET_KEY=${encKey}`);
269
+ await fs4.writeFile(localPath, content);
270
+ }
271
+ const ryvnSecretsPath = path4.join(
272
+ packageDir,
273
+ "deploy",
274
+ "ryvn",
275
+ "percepta-test.secrets.env"
276
+ );
277
+ if (await fs4.pathExists(ryvnSecretsPath)) return;
278
+ const deployAuthSecret = randomBytes(32).toString("base64");
279
+ const deployEncKey = randomBytes(16).toString("hex");
280
+ const deploySecrets = [
281
+ `BETTER_AUTH_SECRET=${deployAuthSecret}`,
282
+ `ENCRYPTION_SECRET_KEY=${deployEncKey}`,
283
+ ""
284
+ ].join("\n");
285
+ await fs4.ensureDir(path4.dirname(ryvnSecretsPath));
286
+ await fs4.writeFile(ryvnSecretsPath, deploySecrets);
270
287
  }
271
288
 
272
289
  // src/utils/relocate-workflows.ts
@@ -294,14 +311,90 @@ async function relocateWorkflowsToRoot(packageDir, monorepoRoot, appName) {
294
311
  }
295
312
  }
296
313
 
314
+ // src/utils/resolve-percepta-versions.ts
315
+ import path6 from "path";
316
+ import { execFile } from "child_process";
317
+ import { promisify } from "util";
318
+ import fs6 from "fs-extra";
319
+ var execFileAsync = promisify(execFile);
320
+ var DEPENDENCY_SECTIONS = [
321
+ "dependencies",
322
+ "devDependencies",
323
+ "optionalDependencies",
324
+ "peerDependencies"
325
+ ];
326
+ function getPerceptaPackages(pkg) {
327
+ const names = /* @__PURE__ */ new Set();
328
+ for (const section of DEPENDENCY_SECTIONS) {
329
+ const deps = pkg[section];
330
+ if (!deps) continue;
331
+ for (const name of Object.keys(deps)) {
332
+ if (name.startsWith("@percepta/")) {
333
+ names.add(name);
334
+ }
335
+ }
336
+ }
337
+ return [...names].sort();
338
+ }
339
+ async function npmViewDistTagLatest(packageName, cwd) {
340
+ const { stdout } = await execFileAsync(
341
+ "npm",
342
+ ["view", packageName, "dist-tags.latest", "--silent"],
343
+ {
344
+ cwd,
345
+ encoding: "utf8",
346
+ timeout: 5e3
347
+ }
348
+ );
349
+ const version = stdout.trim();
350
+ return version.length > 0 ? version : null;
351
+ }
352
+ async function resolvePerceptaVersionsInPackageJson(packageJsonPath, lookupLatest = npmViewDistTagLatest) {
353
+ const cwd = path6.dirname(packageJsonPath);
354
+ const pkg = await fs6.readJson(packageJsonPath);
355
+ const packageNames = getPerceptaPackages(pkg);
356
+ const resolved = {};
357
+ const failed = [];
358
+ const results = await Promise.all(
359
+ packageNames.map(async (packageName) => {
360
+ try {
361
+ return {
362
+ packageName,
363
+ latest: await lookupLatest(packageName, cwd)
364
+ };
365
+ } catch {
366
+ return { packageName, latest: null };
367
+ }
368
+ })
369
+ );
370
+ for (const { packageName, latest } of results) {
371
+ if (!latest) {
372
+ failed.push(packageName);
373
+ continue;
374
+ }
375
+ resolved[packageName] = latest;
376
+ for (const section of DEPENDENCY_SECTIONS) {
377
+ const deps = pkg[section];
378
+ if (deps?.[packageName]) {
379
+ deps[packageName] = latest;
380
+ }
381
+ }
382
+ }
383
+ if (Object.keys(resolved).length > 0) {
384
+ await fs6.writeJson(packageJsonPath, pkg, { spaces: 2 });
385
+ await fs6.appendFile(packageJsonPath, "\n");
386
+ }
387
+ return { resolved, failed };
388
+ }
389
+
297
390
  // src/commands/create.ts
298
391
  var PACKAGE_MANAGER = "pnpm";
299
392
  function shPath(p) {
300
- return p.split(path6.sep).join("/");
393
+ return p.split(path7.sep).join("/");
301
394
  }
302
- function runPackageManagerInstall(packageManager, cwd) {
395
+ function runPackageManagerInstall(packageManager, cwd, args = ["install"]) {
303
396
  return new Promise((resolve, reject) => {
304
- const child = spawn(packageManager, ["install"], {
397
+ const child = spawn(packageManager, args, {
305
398
  cwd,
306
399
  stdio: "ignore"
307
400
  });
@@ -311,7 +404,7 @@ function runPackageManagerInstall(packageManager, cwd) {
311
404
  else
312
405
  reject(
313
406
  new Error(
314
- `${packageManager} install exited with code ${code ?? "unknown"}`
407
+ `${packageManager} ${args.join(" ")} exited with code ${code ?? "unknown"}`
315
408
  )
316
409
  );
317
410
  });
@@ -415,12 +508,12 @@ async function autoRunWebapp(packageDir) {
415
508
  return true;
416
509
  }
417
510
  function readTemplateVersions() {
418
- const versionsPath = path6.resolve(
419
- path6.dirname(fileURLToPath2(import.meta.url)),
511
+ const versionsPath = path7.resolve(
512
+ path7.dirname(fileURLToPath2(import.meta.url)),
420
513
  "../template-versions.json"
421
514
  );
422
515
  try {
423
- const content = fs6.readFileSync(versionsPath, "utf-8");
516
+ const content = fs7.readFileSync(versionsPath, "utf-8");
424
517
  return JSON.parse(content);
425
518
  } catch {
426
519
  return {};
@@ -439,8 +532,8 @@ async function writeMosaicFiles(packageDir, config, projectType) {
439
532
  }
440
533
  };
441
534
  await writeManifest(packageDir, manifest);
442
- const notesPath = path6.join(packageDir, "mosaic-template-notes.md");
443
- await fs6.writeFile(
535
+ const notesPath = path7.join(packageDir, "mosaic-template-notes.md");
536
+ await fs7.writeFile(
444
537
  notesPath,
445
538
  `# Mosaic Divergence Notes
446
539
 
@@ -453,6 +546,15 @@ _None yet \u2014 freshly created from template._
453
546
  `
454
547
  );
455
548
  }
549
+ function buildAppConfig(name, title = toTitleCase(name)) {
550
+ return {
551
+ name,
552
+ title,
553
+ dbName: `${toSnakeCase(name)}_db`,
554
+ nameUpper: name.toUpperCase(),
555
+ nameSnake: toSnakeCase(name)
556
+ };
557
+ }
456
558
  async function scaffoldMonorepo(targetDir, config) {
457
559
  const monoSpinner = ora("Copying monorepo template...").start();
458
560
  try {
@@ -499,10 +601,32 @@ async function addPackageToMonorepo(args) {
499
601
  }
500
602
  await writeMosaicFiles(packageDir, config, projectType);
501
603
  if (projectType === "webapp") {
604
+ await resolvePerceptaPackageVersions(packageDir);
502
605
  await generateEnvLocal(packageDir);
503
606
  await relocateWorkflowsToRoot(packageDir, monorepoRoot, config.name);
504
607
  }
505
608
  }
609
+ async function resolvePerceptaPackageVersions(packageDir) {
610
+ const packageJsonPath = path7.join(packageDir, "package.json");
611
+ if (!await fs7.pathExists(packageJsonPath)) return;
612
+ const spinner = ora("Resolving latest @percepta/* versions...").start();
613
+ try {
614
+ const result = await resolvePerceptaVersionsInPackageJson(packageJsonPath);
615
+ const count = Object.keys(result.resolved).length;
616
+ if (result.failed.length > 0) {
617
+ spinner.warn(
618
+ `Resolved ${count} @percepta/* versions; kept existing ranges for ${result.failed.join(", ")}`
619
+ );
620
+ } else {
621
+ spinner.succeed(`Resolved ${count} @percepta/* versions`);
622
+ }
623
+ } catch (error) {
624
+ spinner.warn(
625
+ "Could not resolve latest @percepta/* versions; kept template ranges"
626
+ );
627
+ console.log(chalk.dim(error.message));
628
+ }
629
+ }
506
630
  function initGitRepo(targetDir) {
507
631
  const gitSpinner = ora("Initializing git repository...").start();
508
632
  try {
@@ -533,6 +657,27 @@ async function installAtMonorepoRoot(monorepoRoot, installDeps) {
533
657
  return false;
534
658
  }
535
659
  }
660
+ async function installAtWebappPackage(packageDir, projectType, installDeps) {
661
+ if (!installDeps || !packageDir || projectType !== "webapp") {
662
+ return projectType !== "webapp";
663
+ }
664
+ const spinner = ora(
665
+ `Generating package lockfile with ${PACKAGE_MANAGER}...`
666
+ ).start();
667
+ try {
668
+ await runPackageManagerInstall(PACKAGE_MANAGER, packageDir, [
669
+ "install",
670
+ "--ignore-workspace"
671
+ ]);
672
+ spinner.succeed("Generated package lockfile");
673
+ return true;
674
+ } catch {
675
+ spinner.warn(
676
+ `Failed to generate package lockfile. Run '${PACKAGE_MANAGER} install --ignore-workspace' from ${packageDir}.`
677
+ );
678
+ return false;
679
+ }
680
+ }
536
681
  async function maybeAutoRunWebapp(packageDir, projectType, installSucceeded) {
537
682
  if (!packageDir || projectType !== "webapp" || !installSucceeded) return false;
538
683
  return autoRunWebapp(packageDir);
@@ -551,7 +696,37 @@ function getProjectTypeLabel(projectType) {
551
696
  }
552
697
  }
553
698
  }
699
+ function requireNpmTokenForWebappInstall(projectType, installDeps) {
700
+ if (projectType !== "webapp" || !installDeps || process.env.NPM_TOKEN) {
701
+ return;
702
+ }
703
+ console.log();
704
+ console.error(chalk.red("Error: NPM_TOKEN environment variable is not set."));
705
+ console.error(
706
+ chalk.dim(" Required to install private @percepta/* packages.")
707
+ );
708
+ console.error();
709
+ console.error(" 1. Grab the npm token from 1Password:");
710
+ console.error(
711
+ chalk.cyan(
712
+ " https://start.1password.com/open/i?a=5TX2B4O3QNE4FNQ2A7ZJZDRRBI&v=j7trpyuqh7gt635dtuj6y4pwjm&i=cmmdi5trji7ctkn3fseakf4mgi&h=aitco.1password.com"
713
+ )
714
+ );
715
+ console.error(" 2. Add to ~/.zshrc:");
716
+ console.error(chalk.cyan(' export NPM_TOKEN="<paste-token>"'));
717
+ console.error(
718
+ " 3. Open a new terminal (or " + chalk.cyan("source ~/.zshrc") + ") and re-run."
719
+ );
720
+ console.error();
721
+ console.error(
722
+ chalk.dim(
723
+ " Or pass --skip-install to scaffold without running install."
724
+ )
725
+ );
726
+ process.exit(1);
727
+ }
554
728
  async function createProject(options) {
729
+ const cwd = await resolveCreateCwd(options.cwd);
555
730
  if (options.type !== void 0 && !isValidProjectType(options.type)) {
556
731
  console.error(
557
732
  chalk.red(
@@ -560,7 +735,6 @@ async function createProject(options) {
560
735
  );
561
736
  process.exit(1);
562
737
  }
563
- const cwd = process.cwd();
564
738
  console.log();
565
739
  console.log(chalk.bold("Creating a new Mosaic package..."));
566
740
  console.log();
@@ -587,6 +761,7 @@ async function createProject(options) {
587
761
  }
588
762
  console.log();
589
763
  const projectName = options.name;
764
+ const repoName = options.repoName;
590
765
  if (options.yes && !projectName) {
591
766
  console.error(
592
767
  chalk.red("Error: --name is required when using --yes flag")
@@ -600,62 +775,51 @@ async function createProject(options) {
600
775
  process.exit(1);
601
776
  }
602
777
  }
778
+ if (repoName) {
779
+ const validation = validateProjectName(toKebabCase(repoName));
780
+ if (!validation.valid) {
781
+ console.error(chalk.red(`Invalid repo name: ${validation.error}`));
782
+ process.exit(1);
783
+ }
784
+ }
603
785
  let answers;
604
786
  if (options.yes) {
605
787
  const projectType = options.type || "webapp";
788
+ requireNpmTokenForWebappInstall(projectType, !options.skipInstall);
606
789
  const kebabName = toKebabCase(projectName);
607
- const directory = monorepoContext.found && monorepoContext.packageDir ? path6.join(monorepoContext.packageDir, kebabName) : path6.resolve(cwd, kebabName);
790
+ const kebabRepoName = repoName ? toKebabCase(repoName) : kebabName;
791
+ const directory = monorepoContext.found && monorepoContext.packageDir ? path7.join(monorepoContext.packageDir, kebabName) : path7.resolve(cwd, kebabRepoName);
608
792
  answers = {
609
793
  projectType,
610
794
  directory,
611
795
  name: kebabName,
612
796
  title: toTitleCase(kebabName),
613
- installDeps: !options.skipInstall
797
+ installDeps: !options.skipInstall,
798
+ monorepoName: monorepoContext.found ? void 0 : kebabRepoName,
799
+ monorepoTitle: monorepoContext.found ? void 0 : toTitleCase(kebabRepoName)
614
800
  };
615
801
  } else {
616
802
  answers = await promptProjectDetails({
617
803
  projectType: options.type,
618
804
  name: projectName ? toKebabCase(projectName) : void 0,
805
+ repoName: repoName ? toKebabCase(repoName) : void 0,
619
806
  skipInstall: options.skipInstall,
620
- monorepoContext
807
+ monorepoContext,
808
+ cwd,
809
+ beforeNamePrompt: (projectType) => requireNpmTokenForWebappInstall(projectType, !options.skipInstall)
621
810
  });
622
811
  if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) {
623
- answers.directory = path6.join(monorepoContext.packageDir, answers.name);
812
+ answers.directory = path7.join(monorepoContext.packageDir, answers.name);
624
813
  }
625
814
  }
626
- if (answers.projectType === "webapp" && answers.installDeps && !process.env.NPM_TOKEN) {
627
- console.log();
628
- console.error(chalk.red("Error: NPM_TOKEN environment variable is not set."));
629
- console.error(chalk.dim(" Required to install private @percepta/* packages."));
630
- console.error();
631
- console.error(" 1. Grab the npm token from 1Password:");
632
- console.error(
633
- chalk.cyan(
634
- " https://start.1password.com/open/i?a=5TX2B4O3QNE4FNQ2A7ZJZDRRBI&v=j7trpyuqh7gt635dtuj6y4pwjm&i=cmmdi5trji7ctkn3fseakf4mgi&h=aitco.1password.com"
635
- )
636
- );
637
- console.error(" 2. Add to ~/.zshrc:");
638
- console.error(chalk.cyan(' export NPM_TOKEN="<paste-token>"'));
639
- console.error(" 3. Open a new terminal (or " + chalk.cyan("source ~/.zshrc") + ") and re-run.");
640
- console.error();
641
- console.error(
642
- chalk.dim(
643
- " Or pass --skip-install to scaffold without running install."
644
- )
645
- );
646
- process.exit(1);
647
- }
648
- const config = {
649
- name: answers.name,
650
- title: answers.title,
651
- dbName: toSnakeCase(answers.name) + "_db",
652
- nameUpper: answers.name.toUpperCase(),
653
- nameSnake: toSnakeCase(answers.name)
654
- };
815
+ const config = buildAppConfig(answers.name, answers.title);
816
+ const monorepoName = answers.monorepoName ?? answers.name;
817
+ const monorepoTitle = answers.monorepoTitle ?? toTitleCase(monorepoName);
818
+ const monorepoConfig = buildAppConfig(monorepoName, monorepoTitle);
655
819
  const typeLabel = getProjectTypeLabel(answers.projectType);
656
820
  if (monorepoContext.found) {
657
821
  const monorepoRoot = monorepoContext.rootDir;
658
- const packageDir = monorepoContext.packageDir ? path6.join(monorepoContext.packageDir, answers.name) : answers.directory;
822
+ const packageDir = monorepoContext.packageDir ? path7.join(monorepoContext.packageDir, answers.name) : answers.directory;
659
823
  console.log(chalk.dim(" Package type:"), typeLabel);
660
824
  console.log(chalk.dim(" Target:"), packageDir);
661
825
  console.log(chalk.dim(" Name:"), config.name);
@@ -664,8 +828,8 @@ async function createProject(options) {
664
828
  console.log(chalk.dim(" Database:"), config.dbName);
665
829
  }
666
830
  console.log();
667
- if (await fs6.pathExists(packageDir)) {
668
- const files = await fs6.readdir(packageDir);
831
+ if (await fs7.pathExists(packageDir)) {
832
+ const files = await fs7.readdir(packageDir);
669
833
  if (files.length > 0) {
670
834
  console.error(
671
835
  chalk.red(`Error: Directory ${packageDir} is not empty.`)
@@ -682,15 +846,21 @@ async function createProject(options) {
682
846
  });
683
847
  }
684
848
  await warnIfMissingRootNpmrc(monorepoRoot);
685
- const installSucceeded = await installAtMonorepoRoot(
849
+ const rootInstallSucceeded = await installAtMonorepoRoot(
686
850
  monorepoRoot,
687
851
  answers.installDeps
688
852
  );
853
+ const packageInstallSucceeded = await installAtWebappPackage(
854
+ packageDir,
855
+ answers.projectType,
856
+ answers.installDeps
857
+ );
858
+ const installSucceeded = answers.projectType === "webapp" ? rootInstallSucceeded && packageInstallSucceeded : rootInstallSucceeded;
689
859
  console.log();
690
860
  console.log(
691
861
  chalk.green("\u2714"),
692
862
  chalk.bold(`Created ${typeLabel} at`),
693
- chalk.cyan(path6.relative(monorepoRoot, packageDir))
863
+ chalk.cyan(path7.relative(monorepoRoot, packageDir))
694
864
  );
695
865
  console.log();
696
866
  const devStarted = await maybeAutoRunWebapp(
@@ -699,19 +869,20 @@ async function createProject(options) {
699
869
  installSucceeded
700
870
  );
701
871
  if (devStarted) return;
702
- printNextStepsExisting(answers, options, packageDir);
872
+ printNextStepsExisting(answers, packageDir);
703
873
  } else {
704
874
  const isBareMonorepo = answers.projectType === "monorepo";
705
875
  const monorepoRoot = answers.directory;
706
- const packageDir = isBareMonorepo ? null : path6.join(monorepoRoot, "packages", answers.name);
876
+ const packageDir = isBareMonorepo ? null : path7.join(monorepoRoot, "packages", answers.name);
707
877
  if (isBareMonorepo) {
708
878
  console.log(chalk.dim(" Type:"), typeLabel);
709
879
  console.log(chalk.dim(" Directory:"), monorepoRoot);
710
- console.log(chalk.dim(" Name:"), config.name);
711
- console.log(chalk.dim(" Title:"), config.title);
880
+ console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
881
+ console.log(chalk.dim(" Title:"), monorepoConfig.title);
712
882
  } else {
713
883
  console.log(chalk.dim(" Package type:"), typeLabel);
714
884
  console.log(chalk.dim(" Monorepo directory:"), monorepoRoot);
885
+ console.log(chalk.dim(" Repo name:"), monorepoConfig.name);
715
886
  console.log(chalk.dim(" Package:"), `packages/${answers.name}/`);
716
887
  console.log(chalk.dim(" Name:"), config.name);
717
888
  console.log(chalk.dim(" Title:"), config.title);
@@ -720,8 +891,8 @@ async function createProject(options) {
720
891
  }
721
892
  }
722
893
  console.log();
723
- if (await fs6.pathExists(monorepoRoot)) {
724
- const files = await fs6.readdir(monorepoRoot);
894
+ if (await fs7.pathExists(monorepoRoot)) {
895
+ const files = await fs7.readdir(monorepoRoot);
725
896
  if (files.length > 0) {
726
897
  console.error(
727
898
  chalk.red(`Error: Directory ${monorepoRoot} is not empty.`)
@@ -729,7 +900,7 @@ async function createProject(options) {
729
900
  process.exit(1);
730
901
  }
731
902
  }
732
- await scaffoldMonorepo(monorepoRoot, config);
903
+ await scaffoldMonorepo(monorepoRoot, monorepoConfig);
733
904
  if (packageDir && answers.projectType !== "monorepo") {
734
905
  await addPackageToMonorepo({
735
906
  packageDir,
@@ -739,10 +910,16 @@ async function createProject(options) {
739
910
  });
740
911
  }
741
912
  initGitRepo(monorepoRoot);
742
- const installSucceeded = await installAtMonorepoRoot(
913
+ const rootInstallSucceeded = await installAtMonorepoRoot(
743
914
  monorepoRoot,
744
915
  answers.installDeps
745
916
  );
917
+ const packageInstallSucceeded = await installAtWebappPackage(
918
+ packageDir,
919
+ answers.projectType,
920
+ answers.installDeps
921
+ );
922
+ const installSucceeded = answers.projectType === "webapp" ? rootInstallSucceeded && packageInstallSucceeded : rootInstallSucceeded;
746
923
  console.log();
747
924
  console.log(
748
925
  chalk.green("\u2714"),
@@ -763,8 +940,23 @@ async function createProject(options) {
763
940
  installSucceeded
764
941
  );
765
942
  if (devStarted) return;
766
- printNextStepsNew(answers, options, monorepoRoot);
943
+ printNextStepsNew(answers, monorepoRoot);
944
+ }
945
+ }
946
+ async function resolveCreateCwd(cwdOption) {
947
+ const cwd = cwdOption ? path7.resolve(cwdOption) : process.cwd();
948
+ let stat;
949
+ try {
950
+ stat = await fs7.stat(cwd);
951
+ } catch {
952
+ console.error(chalk.red(`Error: --cwd directory does not exist: ${cwd}`));
953
+ process.exit(1);
954
+ }
955
+ if (!stat.isDirectory()) {
956
+ console.error(chalk.red(`Error: --cwd is not a directory: ${cwd}`));
957
+ process.exit(1);
767
958
  }
959
+ return cwd;
768
960
  }
769
961
  function printWebappNextSteps(params) {
770
962
  const {
@@ -776,12 +968,15 @@ function printWebappNextSteps(params) {
776
968
  } = params;
777
969
  const repoRel = shPath(monorepoRelativePath) || ".";
778
970
  const pkgFromRoot = `packages/${answers.name}`;
971
+ const packageInstallStep = `${pm} install --ignore-workspace`;
779
972
  const pnpmSteps = ["pnpm run setup", "pnpm dev"];
780
973
  if (variant === "new") {
781
974
  const oneLinerParts2 = [];
782
975
  if (repoRel !== ".") oneLinerParts2.push(`cd ${repoRel}`);
783
976
  if (!answers.installDeps) oneLinerParts2.push(`${pm} install`);
784
- oneLinerParts2.push(`cd ${pkgFromRoot}`, ...pnpmSteps);
977
+ oneLinerParts2.push(`cd ${pkgFromRoot}`);
978
+ if (!answers.installDeps) oneLinerParts2.push(packageInstallStep);
979
+ oneLinerParts2.push(...pnpmSteps);
785
980
  console.log(chalk.bold("Copy-paste (from your current directory):"));
786
981
  console.log();
787
982
  console.log(chalk.cyan(` ${oneLinerParts2.join(" && ")}`));
@@ -796,6 +991,9 @@ function printWebappNextSteps(params) {
796
991
  console.log(chalk.dim(` ${step2++}.`), `${pm} install`);
797
992
  }
798
993
  console.log(chalk.dim(` ${step2++}.`), `cd ${pkgFromRoot}`);
994
+ if (!answers.installDeps) {
995
+ console.log(chalk.dim(` ${step2++}.`), packageInstallStep);
996
+ }
799
997
  for (const cmd of pnpmSteps) {
800
998
  console.log(chalk.dim(` ${step2++}.`), cmd);
801
999
  }
@@ -805,7 +1003,7 @@ function printWebappNextSteps(params) {
805
1003
  const oneLinerParts = [];
806
1004
  if (!answers.installDeps) {
807
1005
  if (repoRel !== ".") oneLinerParts.push(`cd ${repoRel}`);
808
- oneLinerParts.push(`${pm} install`, `cd ${pkgFromRoot}`);
1006
+ oneLinerParts.push(`${pm} install`, `cd ${pkgFromRoot}`, packageInstallStep);
809
1007
  } else if (pkgRel !== ".") {
810
1008
  oneLinerParts.push(`cd ${pkgRel}`);
811
1009
  }
@@ -823,6 +1021,7 @@ function printWebappNextSteps(params) {
823
1021
  }
824
1022
  console.log(chalk.dim(` ${step++}.`), `${pm} install`);
825
1023
  console.log(chalk.dim(` ${step++}.`), `cd ${pkgFromRoot}`);
1024
+ console.log(chalk.dim(` ${step++}.`), packageInstallStep);
826
1025
  } else if (pkgRel !== ".") {
827
1026
  console.log(chalk.dim(` ${step++}.`), `cd ${pkgRel}`);
828
1027
  }
@@ -831,10 +1030,10 @@ function printWebappNextSteps(params) {
831
1030
  }
832
1031
  }
833
1032
  async function warnIfMissingRootNpmrc(rootDir) {
834
- const rootNpmrc = path6.join(rootDir, ".npmrc");
1033
+ const rootNpmrc = path7.join(rootDir, ".npmrc");
835
1034
  let contents = "";
836
- if (await fs6.pathExists(rootNpmrc)) {
837
- contents = await fs6.readFile(rootNpmrc, "utf8");
1035
+ if (await fs7.pathExists(rootNpmrc)) {
1036
+ contents = await fs7.readFile(rootNpmrc, "utf8");
838
1037
  }
839
1038
  if (contents.includes("@percepta:registry")) {
840
1039
  return;
@@ -849,7 +1048,7 @@ async function warnIfMissingRootNpmrc(rootDir) {
849
1048
  " pnpm reads .npmrc from the workspace root, so add these lines to"
850
1049
  )
851
1050
  );
852
- console.log(chalk.dim(` ${path6.join(rootDir, ".npmrc")}:`));
1051
+ console.log(chalk.dim(` ${path7.join(rootDir, ".npmrc")}:`));
853
1052
  console.log();
854
1053
  console.log(
855
1054
  chalk.cyan(" @percepta:registry=https://registry.npmjs.org/")
@@ -859,9 +1058,9 @@ async function warnIfMissingRootNpmrc(rootDir) {
859
1058
  );
860
1059
  console.log();
861
1060
  }
862
- function printNextStepsNew(answers, options, targetDir) {
1061
+ function printNextStepsNew(answers, targetDir) {
863
1062
  const pm = PACKAGE_MANAGER;
864
- const relativePath = path6.relative(process.cwd(), targetDir) || ".";
1063
+ const relativePath = path7.relative(process.cwd(), targetDir) || ".";
865
1064
  console.log("Next steps:");
866
1065
  console.log();
867
1066
  switch (answers.projectType) {
@@ -915,11 +1114,11 @@ function printNextStepsNew(answers, options, targetDir) {
915
1114
  );
916
1115
  console.log();
917
1116
  }
918
- function printNextStepsExisting(answers, options, packageDir) {
1117
+ function printNextStepsExisting(answers, packageDir) {
919
1118
  const pm = PACKAGE_MANAGER;
920
- const packageRelativePath = path6.relative(process.cwd(), packageDir) || ".";
921
- const monorepoRoot = path6.dirname(path6.dirname(packageDir));
922
- const monorepoRelativePath = path6.relative(process.cwd(), monorepoRoot) || ".";
1119
+ const packageRelativePath = path7.relative(process.cwd(), packageDir) || ".";
1120
+ const monorepoRoot = path7.dirname(path7.dirname(packageDir));
1121
+ const monorepoRelativePath = path7.relative(process.cwd(), monorepoRoot) || ".";
923
1122
  console.log("Next steps:");
924
1123
  console.log();
925
1124
  switch (answers.projectType) {
@@ -959,30 +1158,30 @@ var packageJson = {
959
1158
  version: "1.0.0"
960
1159
  };
961
1160
  program.name("create").description("Scaffold and manage Mosaic packages").version(packageJson.version);
962
- program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "Project name").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);
1161
+ 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("--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);
963
1162
  program.command("status").description("Show template sync status for current app").option(
964
1163
  "--mosaic-template-path <path>",
965
1164
  "Path to local mosaic repo checkout"
966
1165
  ).action(async (options) => {
967
- const { statusCommand } = await import("./status-MITGDLTT.js");
1166
+ const { statusCommand } = await import("./status-BTHGN6QH.js");
968
1167
  await statusCommand(options);
969
1168
  });
970
1169
  program.command("sync").description("Generate downstream sync context (template \u2192 app)").option(
971
1170
  "--mosaic-template-path <path>",
972
1171
  "Path to local mosaic repo checkout"
973
1172
  ).option("--to <version>", "Target template version (default: latest)").action(async (options) => {
974
- const { syncCommand } = await import("./sync-J4SFZHDX.js");
1173
+ const { syncCommand } = await import("./sync-3Q27L7XZ.js");
975
1174
  await syncCommand(options);
976
1175
  });
977
1176
  program.command("upstream").description("Generate upstream context (app \u2192 template)").option(
978
1177
  "--mosaic-template-path <path>",
979
1178
  "Path to local mosaic repo checkout"
980
1179
  ).option("--files <patterns...>", "Specific files to propose upstream").action(async (options) => {
981
- const { upstreamCommand } = await import("./upstream-AQI7P4EU.js");
1180
+ const { upstreamCommand } = await import("./upstream-C5KFAHVR.js");
982
1181
  await upstreamCommand(options);
983
1182
  });
984
1183
  program.command("init").description("Add .mosaic-template.json to an existing app").option("-t, --type <type>", "Template type (e.g., webapp, library)").option("--template-version <version>", "Template version to set").action(async (options) => {
985
- const { initCommand } = await import("./init-Z4VGBHAK.js");
1184
+ const { initCommand } = await import("./init-XDWSYHYK.js");
986
1185
  await initCommand(options);
987
1186
  });
988
1187
  program.parse();
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VALID_PROJECT_TYPES,
3
3
  isValidProjectType
4
- } from "./chunk-GEVZERMP.js";
4
+ } from "./chunk-CG7IJSB4.js";
5
5
  import {
6
6
  derivePlaceholders,
7
7
  manifestExists,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getLatestTemplateTag,
3
3
  getTemplateVersionFromTag
4
- } from "./chunk-R4FWPE4A.js";
4
+ } from "./chunk-DCM7JOSC.js";
5
5
  import {
6
6
  readManifest
7
7
  } from "./chunk-WMJT7CB5.js";
@@ -2,7 +2,7 @@ import {
2
2
  getLatestTemplateTag,
3
3
  getTemplateDiff,
4
4
  getTemplateVersionFromTag
5
- } from "./chunk-R4FWPE4A.js";
5
+ } from "./chunk-DCM7JOSC.js";
6
6
  import {
7
7
  readManifest,
8
8
  resolveMosaicTemplatePath
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getFileAtTag
3
- } from "./chunk-R4FWPE4A.js";
3
+ } from "./chunk-DCM7JOSC.js";
4
4
  import {
5
5
  readManifest,
6
6
  resolveMosaicTemplatePath