@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.
- package/README.md +16 -9
- package/dist/{chunk-GEVZERMP.js → chunk-CG7IJSB4.js} +33 -2
- package/dist/{chunk-R4FWPE4A.js → chunk-DCM7JOSC.js} +2 -2
- package/dist/index.js +281 -82
- package/dist/{init-Z4VGBHAK.js → init-XDWSYHYK.js} +1 -1
- package/dist/{status-MITGDLTT.js → status-BTHGN6QH.js} +1 -1
- package/dist/{sync-J4SFZHDX.js → sync-3Q27L7XZ.js} +1 -1
- package/dist/{upstream-AQI7P4EU.js → upstream-C5KFAHVR.js} +1 -1
- package/package.json +3 -2
- package/templates/monorepo/gitignore.template +1 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +3 -2
- package/templates/webapp/AGENTS.md +8 -2
- package/templates/webapp/Dockerfile +0 -1
- package/templates/webapp/README.md +1 -0
- package/templates/webapp/agent-skills/database.md +1 -0
- package/templates/webapp/agent-skills/deploy.md +45 -32
- package/templates/webapp/agent-skills/oneshot.md +3 -3
- package/templates/webapp/deploy/README.md +32 -6
- package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +0 -2
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +28 -31
- package/templates/webapp/drizzle.config.ts +15 -6
- package/templates/webapp/env.example.template +1 -0
- package/templates/webapp/eslint.config.mjs +8 -0
- package/templates/webapp/gitignore.template +1 -0
- package/templates/webapp/package.json.template +6 -6
- package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +495 -0
- package/templates/webapp/scripts/seed.ts +1 -1
- package/templates/webapp/scripts/setup-database.ts +16 -1
- package/templates/webapp/scripts/start.sh +3 -2
- package/templates/webapp/src/app/(app)/layout.tsx +1 -5
- package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +11 -1
- package/templates/webapp/src/app/(auth)/auth/signup/CredentialsSignUpForm.tsx +113 -0
- package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +30 -0
- package/templates/webapp/src/app/global-error.tsx +3 -1
- package/templates/webapp/src/components/FaroProvider.tsx +2 -4
- package/templates/webapp/src/components/form/FormItem.tsx +2 -2
- package/templates/webapp/src/config/getEnvConfig.ts +1 -0
- package/templates/webapp/src/drizzle/db.ts +5 -1
- package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +3 -3
- package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +7 -19
- package/templates/webapp/src/drizzle/searchPath.test.ts +21 -0
- package/templates/webapp/src/drizzle/searchPath.ts +16 -0
- package/templates/webapp/src/drizzle/ssl.ts +5 -0
- package/templates/webapp/src/lib/auth/index.ts +1 -1
- package/templates/webapp/src/lib/auth-client.ts +1 -1
- package/templates/webapp/src/services/observability/initFaro.ts +1 -1
- 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-
|
|
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
|
|
20
|
+
import path7 from "path";
|
|
21
21
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22
|
-
import
|
|
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))
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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(
|
|
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,
|
|
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}
|
|
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 =
|
|
419
|
-
|
|
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 =
|
|
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 =
|
|
443
|
-
await
|
|
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
|
|
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 =
|
|
812
|
+
answers.directory = path7.join(monorepoContext.packageDir, answers.name);
|
|
624
813
|
}
|
|
625
814
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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 ?
|
|
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
|
|
668
|
-
const files = await
|
|
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
|
|
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(
|
|
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,
|
|
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 :
|
|
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("
|
|
711
|
-
console.log(chalk.dim(" 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
|
|
724
|
-
const files = await
|
|
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,
|
|
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
|
|
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,
|
|
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}
|
|
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 =
|
|
1033
|
+
const rootNpmrc = path7.join(rootDir, ".npmrc");
|
|
835
1034
|
let contents = "";
|
|
836
|
-
if (await
|
|
837
|
-
contents = await
|
|
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(` ${
|
|
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,
|
|
1061
|
+
function printNextStepsNew(answers, targetDir) {
|
|
863
1062
|
const pm = PACKAGE_MANAGER;
|
|
864
|
-
const relativePath =
|
|
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,
|
|
1117
|
+
function printNextStepsExisting(answers, packageDir) {
|
|
919
1118
|
const pm = PACKAGE_MANAGER;
|
|
920
|
-
const packageRelativePath =
|
|
921
|
-
const monorepoRoot =
|
|
922
|
-
const monorepoRelativePath =
|
|
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>", "
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1184
|
+
const { initCommand } = await import("./init-XDWSYHYK.js");
|
|
986
1185
|
await initCommand(options);
|
|
987
1186
|
});
|
|
988
1187
|
program.parse();
|