@percepta/create 3.4.2 → 3.5.0
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 +13 -0
- package/dist/index.js +135 -106
- package/dist/index.js.map +1 -1
- package/dist/{init-CtCp7Tv2.js → init-sI9aIrkU.js} +2 -2
- package/dist/init-sI9aIrkU.js.map +1 -0
- package/dist/{upstream-D-LH_1z4.js → upstream-gUHLWSR1.js} +2 -2
- package/dist/upstream-gUHLWSR1.js.map +1 -0
- package/package.json +1 -1
- package/template-versions.json +1 -0
- package/templates/monorepo/README.md +8 -5
- package/templates/monorepo/package.json.template +1 -0
- package/templates/webapp/.claude/commands/upstream.md +1 -1
- package/templates/webapp/AGENTS.md +1 -1
- package/templates/webapp/agent-skills/access-control.md +24 -1
- package/templates/webapp/agent-skills/inngest.md +5 -5
- package/templates/webapp/agent-skills/langfuse.md +4 -4
- package/templates/webapp/agent-skills/llm.md +1 -1
- package/templates/webapp/drizzle.config.ts +1 -1
- package/templates/webapp/package.json.template +10 -17
- package/templates/webapp/src/app/api/inngest/route.ts +12 -22
- package/templates/webapp/src/drizzle/db.ts +1 -2
- package/templates/webapp/src/instrumentation.ts +2 -63
- package/templates/webapp/src/server/trpc.ts +6 -18
- package/templates/webapp/src/services/AuthContextService.ts +7 -59
- package/templates/webapp/src/services/inngest/InngestService.ts +14 -62
- package/templates/webapp/src/services/langfuse/LangfuseService.ts +9 -77
- package/templates/webapp/src/services/llm/LLMService.ts +10 -88
- package/templates/webapp/src/services/logger/AppLogger.ts +3 -48
- package/templates/webapp/src/utils/syncInngestApp.ts +4 -56
- package/dist/init-CtCp7Tv2.js.map +0 -1
- package/dist/upstream-D-LH_1z4.js.map +0 -1
- package/templates/webapp/scripts/generate-migrations.ts +0 -28
- package/templates/webapp/scripts/migrate.ts +0 -21
- package/templates/webapp/scripts/setup-database.ts +0 -78
- package/templates/webapp/scripts/setup-readonly-user.ts +0 -193
- package/templates/webapp/src/drizzle/__tests__/migrationSql.test.ts +0 -24
- package/templates/webapp/src/drizzle/migrationSql.ts +0 -8
- package/templates/webapp/src/drizzle/searchPath.test.ts +0 -21
- package/templates/webapp/src/drizzle/searchPath.ts +0 -16
- package/templates/webapp/src/drizzle/ssl.ts +0 -5
- package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +0 -5
- package/templates/webapp/src/services/llm/LlmProviderService.ts +0 -85
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
26
26
|
## Subcommands
|
|
27
27
|
|
|
28
28
|
- `create` (default) — scaffold a new Mosaic package
|
|
29
|
+
- `add` — add a webapp or library to the current monorepo
|
|
29
30
|
- `status` — show template sync status for the current app
|
|
30
31
|
- `sync` — generate downstream sync context (template → app)
|
|
31
32
|
- `upstream` — generate upstream context (app → template)
|
|
@@ -38,6 +39,18 @@ The bare command above is the canonical UX. The flags below exist for tests and
|
|
|
38
39
|
- **Outside a monorepo** — you're asked "Initialize with a webapp?" (Y/n, default Y), then for the repo name. Picking the webapp option also asks for the webapp name and scaffolds it inside `packages/<webapp-name>/`. Declining gives you an empty monorepo.
|
|
39
40
|
- **Inside a monorepo** — pick `Webapp` (default) or `Library` to add a new package under the workspace pattern.
|
|
40
41
|
|
|
42
|
+
Generated monorepos include a root `.mosaic-workspace.json` and a pinned
|
|
43
|
+
`pnpm mosaic` script. Prefer the workspace-owned command when adding packages:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm mosaic add webapp my-app
|
|
47
|
+
pnpm mosaic add library my-lib
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That command uses the create package version and template compatibility versions
|
|
51
|
+
the monorepo was created with, so a newly added app does not silently drift to a
|
|
52
|
+
newer scaffold.
|
|
53
|
+
|
|
41
54
|
## Happy-path: zero-friction webapp
|
|
42
55
|
|
|
43
56
|
When you scaffold a webapp (the default flow), `create` automatically runs:
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from "commander";
|
|
3
|
-
import {
|
|
3
|
+
import { execSync, spawn } from "node:child_process";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import fs from "fs-extra";
|
|
@@ -10,7 +10,6 @@ import { parse } from "yaml";
|
|
|
10
10
|
import { randomBytes } from "node:crypto";
|
|
11
11
|
import inquirer from "inquirer";
|
|
12
12
|
import validateNpmPackageName from "validate-npm-package-name";
|
|
13
|
-
import { promisify } from "node:util";
|
|
14
13
|
//#region src/utils/case-converters.ts
|
|
15
14
|
/** Lowercase, hyphenated, npm-package-name-safe form: "My Cool App" → "my-cool-app". */
|
|
16
15
|
function toKebabCase(str) {
|
|
@@ -192,6 +191,24 @@ function resolveMosaicTemplatePath(options) {
|
|
|
192
191
|
throw new Error("Mosaic repo path required. Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH.");
|
|
193
192
|
}
|
|
194
193
|
//#endregion
|
|
194
|
+
//#region src/utils/package-metadata.ts
|
|
195
|
+
const FALLBACK_METADATA = {
|
|
196
|
+
name: "@percepta/create",
|
|
197
|
+
version: "0.0.0"
|
|
198
|
+
};
|
|
199
|
+
function readCreatePackageMetadata() {
|
|
200
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
201
|
+
const candidates = [path.resolve(currentDir, "../package.json"), path.resolve(currentDir, "../../package.json")];
|
|
202
|
+
for (const packageJsonPath of candidates) try {
|
|
203
|
+
const pkg = fs.readJsonSync(packageJsonPath);
|
|
204
|
+
if (typeof pkg.name === "string" && typeof pkg.version === "string") return {
|
|
205
|
+
name: pkg.name,
|
|
206
|
+
version: pkg.version
|
|
207
|
+
};
|
|
208
|
+
} catch {}
|
|
209
|
+
return FALLBACK_METADATA;
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
195
212
|
//#region src/utils/validate.ts
|
|
196
213
|
function validateProjectName(name) {
|
|
197
214
|
const result = validateNpmPackageName(name);
|
|
@@ -352,7 +369,9 @@ const PLACEHOLDERS = {
|
|
|
352
369
|
__APP_NAME_UPPER__: "nameUpper",
|
|
353
370
|
__APP_NAME_SNAKE__: "nameSnake",
|
|
354
371
|
__REPO_NAME__: "repoName",
|
|
355
|
-
__REPO_NAME_SNAKE__: "repoNameSnake"
|
|
372
|
+
__REPO_NAME_SNAKE__: "repoNameSnake",
|
|
373
|
+
__CREATE_PACKAGE__: "createPackage",
|
|
374
|
+
__CREATE_VERSION__: "createVersion"
|
|
356
375
|
};
|
|
357
376
|
const SKIP_DIRS = new Set([
|
|
358
377
|
"node_modules",
|
|
@@ -450,77 +469,6 @@ async function replacePlaceholders(targetDir, config) {
|
|
|
450
469
|
return stats;
|
|
451
470
|
}
|
|
452
471
|
//#endregion
|
|
453
|
-
//#region src/utils/resolve-percepta-versions.ts
|
|
454
|
-
const execFileAsync = promisify(execFile);
|
|
455
|
-
const DEPENDENCY_SECTIONS = [
|
|
456
|
-
"dependencies",
|
|
457
|
-
"devDependencies",
|
|
458
|
-
"optionalDependencies",
|
|
459
|
-
"peerDependencies"
|
|
460
|
-
];
|
|
461
|
-
function getPerceptaPackages(pkg) {
|
|
462
|
-
const names = /* @__PURE__ */ new Set();
|
|
463
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
464
|
-
const deps = pkg[section];
|
|
465
|
-
if (!deps) continue;
|
|
466
|
-
for (const name of Object.keys(deps)) if (name.startsWith("@percepta/")) names.add(name);
|
|
467
|
-
}
|
|
468
|
-
return [...names].sort();
|
|
469
|
-
}
|
|
470
|
-
async function npmViewDistTagLatest(packageName, cwd) {
|
|
471
|
-
const { stdout } = await execFileAsync("npm", [
|
|
472
|
-
"view",
|
|
473
|
-
packageName,
|
|
474
|
-
"dist-tags.latest",
|
|
475
|
-
"--silent"
|
|
476
|
-
], {
|
|
477
|
-
cwd,
|
|
478
|
-
encoding: "utf8",
|
|
479
|
-
timeout: 5e3
|
|
480
|
-
});
|
|
481
|
-
const version = stdout.trim();
|
|
482
|
-
return version.length > 0 ? version : null;
|
|
483
|
-
}
|
|
484
|
-
async function resolvePerceptaVersionsInPackageJson(packageJsonPath, lookupLatest = npmViewDistTagLatest) {
|
|
485
|
-
const cwd = path.dirname(packageJsonPath);
|
|
486
|
-
const pkg = await fs.readJson(packageJsonPath);
|
|
487
|
-
const packageNames = getPerceptaPackages(pkg);
|
|
488
|
-
const resolved = {};
|
|
489
|
-
const failed = [];
|
|
490
|
-
const results = await Promise.all(packageNames.map(async (packageName) => {
|
|
491
|
-
try {
|
|
492
|
-
return {
|
|
493
|
-
packageName,
|
|
494
|
-
latest: await lookupLatest(packageName, cwd)
|
|
495
|
-
};
|
|
496
|
-
} catch {
|
|
497
|
-
return {
|
|
498
|
-
packageName,
|
|
499
|
-
latest: null
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
}));
|
|
503
|
-
for (const { packageName, latest } of results) {
|
|
504
|
-
if (!latest) {
|
|
505
|
-
failed.push(packageName);
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
resolved[packageName] = latest;
|
|
509
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
510
|
-
const deps = pkg[section];
|
|
511
|
-
if (deps?.[packageName]) deps[packageName] = latest;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
if (Object.keys(resolved).length > 0) {
|
|
515
|
-
await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
|
516
|
-
await fs.appendFile(packageJsonPath, "\n");
|
|
517
|
-
}
|
|
518
|
-
return {
|
|
519
|
-
resolved,
|
|
520
|
-
failed
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
//#endregion
|
|
524
472
|
//#region src/utils/template-versions.ts
|
|
525
473
|
const FALLBACK_TEMPLATE_VERSION = "1.0.0";
|
|
526
474
|
function readTemplateVersions() {
|
|
@@ -536,6 +484,44 @@ function getTemplateVersion(templateType) {
|
|
|
536
484
|
return readTemplateVersions()[templateType] ?? FALLBACK_TEMPLATE_VERSION;
|
|
537
485
|
}
|
|
538
486
|
//#endregion
|
|
487
|
+
//#region src/utils/workspace-manifest.ts
|
|
488
|
+
const WORKSPACE_MANIFEST_FILENAME = ".mosaic-workspace.json";
|
|
489
|
+
function getWorkspaceManifestPath(rootDir) {
|
|
490
|
+
return path.join(rootDir, WORKSPACE_MANIFEST_FILENAME);
|
|
491
|
+
}
|
|
492
|
+
function createWorkspaceManifest(createdAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
493
|
+
const createPackage = readCreatePackageMetadata();
|
|
494
|
+
return {
|
|
495
|
+
schemaVersion: 1,
|
|
496
|
+
createPackage: createPackage.name,
|
|
497
|
+
createVersion: createPackage.version,
|
|
498
|
+
monorepoTemplateVersion: getTemplateVersion("monorepo"),
|
|
499
|
+
compatibleTemplates: {
|
|
500
|
+
webapp: getTemplateVersion("webapp"),
|
|
501
|
+
library: getTemplateVersion("library")
|
|
502
|
+
},
|
|
503
|
+
createdAt
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
async function readWorkspaceManifest(rootDir) {
|
|
507
|
+
const manifestPath = getWorkspaceManifestPath(rootDir);
|
|
508
|
+
if (!await fs.pathExists(manifestPath)) return null;
|
|
509
|
+
const content = await fs.readFile(manifestPath, "utf-8");
|
|
510
|
+
try {
|
|
511
|
+
return JSON.parse(content);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
throw new Error(`Invalid JSON in ${WORKSPACE_MANIFEST_FILENAME}: ${error.message}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async function writeWorkspaceManifest(rootDir, manifest) {
|
|
517
|
+
const manifestPath = getWorkspaceManifestPath(rootDir);
|
|
518
|
+
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
519
|
+
await fs.appendFile(manifestPath, "\n");
|
|
520
|
+
}
|
|
521
|
+
function getCompatibleTemplateVersion(manifest, templateType) {
|
|
522
|
+
return manifest?.compatibleTemplates[templateType] ?? getTemplateVersion(templateType);
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
539
525
|
//#region src/commands/create.ts
|
|
540
526
|
const PACKAGE_MANAGER = "pnpm";
|
|
541
527
|
/** Paths in copy-paste shell commands (POSIX-style). */
|
|
@@ -696,19 +682,20 @@ async function autoRunWebapp(packageDir, monorepoRoot) {
|
|
|
696
682
|
await closed;
|
|
697
683
|
return true;
|
|
698
684
|
}
|
|
699
|
-
async function writeMosaicFiles(packageDir, config, projectType) {
|
|
685
|
+
async function writeMosaicFiles(packageDir, config, projectType, templateVersion = getTemplateVersion(projectType), templateCommit = "npm") {
|
|
700
686
|
await writeManifest(packageDir, {
|
|
701
687
|
templateType: projectType,
|
|
702
|
-
templateVersion
|
|
703
|
-
templateCommit
|
|
688
|
+
templateVersion,
|
|
689
|
+
templateCommit,
|
|
704
690
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
705
691
|
placeholders: derivePlaceholders(config.name, config.title, config.repoName),
|
|
706
|
-
source: { templatePath: `packages/
|
|
692
|
+
source: { templatePath: `packages/blueberry/templates/${projectType}` }
|
|
707
693
|
});
|
|
708
694
|
const notesPath = path.join(packageDir, "mosaic-template-notes.md");
|
|
709
695
|
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`);
|
|
710
696
|
}
|
|
711
697
|
function buildAppConfig(name, title = toTitleCase(name), repoName = name) {
|
|
698
|
+
const createPackage = readCreatePackageMetadata();
|
|
712
699
|
return {
|
|
713
700
|
name,
|
|
714
701
|
title,
|
|
@@ -716,7 +703,9 @@ function buildAppConfig(name, title = toTitleCase(name), repoName = name) {
|
|
|
716
703
|
nameUpper: name.toUpperCase(),
|
|
717
704
|
nameSnake: toSnakeCase(name),
|
|
718
705
|
repoName,
|
|
719
|
-
repoNameSnake: toSnakeCase(repoName)
|
|
706
|
+
repoNameSnake: toSnakeCase(repoName),
|
|
707
|
+
createPackage: createPackage.name,
|
|
708
|
+
createVersion: createPackage.version
|
|
720
709
|
};
|
|
721
710
|
}
|
|
722
711
|
/** Copy the monorepo template into `targetDir` and replace its placeholders. */
|
|
@@ -746,7 +735,7 @@ async function scaffoldMonorepo(targetDir, config) {
|
|
|
746
735
|
* webapp-only post-copy steps (.env.local, workflow relocation) when applicable.
|
|
747
736
|
*/
|
|
748
737
|
async function addPackageToMonorepo(args) {
|
|
749
|
-
const { packageDir, monorepoRoot, projectType, config } = args;
|
|
738
|
+
const { packageDir, monorepoRoot, projectType, config, templateVersion, templateCommit } = args;
|
|
750
739
|
const copySpinner = ora("Copying package template...").start();
|
|
751
740
|
try {
|
|
752
741
|
await copyTemplate(packageDir, projectType);
|
|
@@ -765,27 +754,12 @@ async function addPackageToMonorepo(args) {
|
|
|
765
754
|
console.error(error);
|
|
766
755
|
process.exit(1);
|
|
767
756
|
}
|
|
768
|
-
await writeMosaicFiles(packageDir, config, projectType);
|
|
757
|
+
await writeMosaicFiles(packageDir, config, projectType, templateVersion, templateCommit);
|
|
769
758
|
if (projectType === "webapp") {
|
|
770
|
-
await resolvePerceptaPackageVersions(packageDir);
|
|
771
759
|
await generateEnvLocal(packageDir);
|
|
772
760
|
await relocateWorkflowsToRoot(packageDir, monorepoRoot, config.name);
|
|
773
761
|
}
|
|
774
762
|
}
|
|
775
|
-
async function resolvePerceptaPackageVersions(packageDir) {
|
|
776
|
-
const packageJsonPath = path.join(packageDir, "package.json");
|
|
777
|
-
if (!await fs.pathExists(packageJsonPath)) return;
|
|
778
|
-
const spinner = ora("Resolving latest @percepta/* versions...").start();
|
|
779
|
-
try {
|
|
780
|
-
const result = await resolvePerceptaVersionsInPackageJson(packageJsonPath);
|
|
781
|
-
const count = Object.keys(result.resolved).length;
|
|
782
|
-
if (result.failed.length > 0) spinner.warn(`Resolved ${count} @percepta/* versions; kept existing ranges for ${result.failed.join(", ")}`);
|
|
783
|
-
else spinner.succeed(`Resolved ${count} @percepta/* versions`);
|
|
784
|
-
} catch (error) {
|
|
785
|
-
spinner.warn("Could not resolve latest @percepta/* versions; kept template ranges");
|
|
786
|
-
console.log(chalk.dim(error.message));
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
763
|
/** Initialize a git repo at `targetDir` with an initial commit. Best-effort. */
|
|
790
764
|
function initGitRepo(targetDir) {
|
|
791
765
|
const gitSpinner = ora("Initializing git repository...").start();
|
|
@@ -858,6 +832,23 @@ function requireNpmTokenForWebappInstall(projectType, installDeps) {
|
|
|
858
832
|
console.error(chalk.dim(" Or pass --skip-install to scaffold without running install."));
|
|
859
833
|
process.exit(1);
|
|
860
834
|
}
|
|
835
|
+
function ensureWorkspaceCreateVersionCompatible(workspaceManifest, projectType) {
|
|
836
|
+
if (!workspaceManifest || projectType === "monorepo") return;
|
|
837
|
+
const createPackage = readCreatePackageMetadata();
|
|
838
|
+
if (workspaceManifest.createPackage === createPackage.name && workspaceManifest.createVersion === createPackage.version) return;
|
|
839
|
+
console.log();
|
|
840
|
+
console.error(chalk.red(`Error: This workspace is pinned to ${workspaceManifest.createPackage}@${workspaceManifest.createVersion}, but you are running ${createPackage.name}@${createPackage.version}.`));
|
|
841
|
+
console.error();
|
|
842
|
+
console.error(chalk.dim(" Run the workspace-owned command so new packages match this monorepo:"));
|
|
843
|
+
console.error(chalk.cyan(` pnpm mosaic add ${projectType} <name>`));
|
|
844
|
+
console.error();
|
|
845
|
+
console.error(chalk.dim(" To adopt newer templates, upgrade the workspace manifest first."));
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
848
|
+
function getTemplateCommitSource(workspaceManifest) {
|
|
849
|
+
if (!workspaceManifest) return "npm";
|
|
850
|
+
return `${workspaceManifest.createPackage}@${workspaceManifest.createVersion}`;
|
|
851
|
+
}
|
|
861
852
|
async function createProject(options) {
|
|
862
853
|
const cwd = await resolveCreateCwd(options.cwd);
|
|
863
854
|
if (options.type !== void 0 && !isValidProjectType(options.type)) {
|
|
@@ -868,6 +859,10 @@ async function createProject(options) {
|
|
|
868
859
|
console.log(chalk.bold("Creating a new Mosaic package..."));
|
|
869
860
|
console.log();
|
|
870
861
|
const monorepoContext = await detectMonorepo(cwd);
|
|
862
|
+
if (options.addOnly && !monorepoContext.found) {
|
|
863
|
+
console.error(chalk.red("Error: 'create add' must be run inside an existing pnpm monorepo."));
|
|
864
|
+
process.exit(1);
|
|
865
|
+
}
|
|
871
866
|
if (options.type === "monorepo" && monorepoContext.found) {
|
|
872
867
|
console.error(chalk.red(`Error: Already inside a monorepo at ${monorepoContext.rootDir}. Choose 'webapp' or 'library' to add a package, or run from outside the monorepo.`));
|
|
873
868
|
process.exit(1);
|
|
@@ -875,6 +870,14 @@ async function createProject(options) {
|
|
|
875
870
|
if (monorepoContext.found) console.log(chalk.dim(" Detected monorepo at"), chalk.cyan(monorepoContext.rootDir));
|
|
876
871
|
else console.log(chalk.dim(" No monorepo detected. A new monorepo will be created."));
|
|
877
872
|
console.log();
|
|
873
|
+
const workspaceManifest = monorepoContext.found ? await readWorkspaceManifest(monorepoContext.rootDir) : null;
|
|
874
|
+
if (workspaceManifest) {
|
|
875
|
+
console.log(chalk.dim(" Workspace create version:"), chalk.cyan(`${workspaceManifest.createPackage}@${workspaceManifest.createVersion}`));
|
|
876
|
+
console.log();
|
|
877
|
+
} else if (monorepoContext.found) {
|
|
878
|
+
console.log(chalk.yellow("!"), "No .mosaic-workspace.json found; using this CLI's bundled templates.");
|
|
879
|
+
console.log();
|
|
880
|
+
}
|
|
878
881
|
const projectName = options.name;
|
|
879
882
|
const repoName = options.repoName;
|
|
880
883
|
if (options.yes && !projectName) {
|
|
@@ -898,6 +901,7 @@ async function createProject(options) {
|
|
|
898
901
|
let answers;
|
|
899
902
|
if (options.yes) {
|
|
900
903
|
const projectType = options.type || "webapp";
|
|
904
|
+
ensureWorkspaceCreateVersionCompatible(workspaceManifest, projectType);
|
|
901
905
|
requireNpmTokenForWebappInstall(projectType, !options.skipInstall);
|
|
902
906
|
const kebabName = toKebabCase(projectName);
|
|
903
907
|
const kebabRepoName = repoName ? toKebabCase(repoName) : kebabName;
|
|
@@ -918,7 +922,10 @@ async function createProject(options) {
|
|
|
918
922
|
skipInstall: options.skipInstall,
|
|
919
923
|
monorepoContext,
|
|
920
924
|
cwd,
|
|
921
|
-
beforeNamePrompt: (projectType) =>
|
|
925
|
+
beforeNamePrompt: (projectType) => {
|
|
926
|
+
ensureWorkspaceCreateVersionCompatible(workspaceManifest, projectType);
|
|
927
|
+
requireNpmTokenForWebappInstall(projectType, !options.skipInstall);
|
|
928
|
+
}
|
|
922
929
|
});
|
|
923
930
|
if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) answers.directory = path.join(monorepoContext.packageDir, answers.name);
|
|
924
931
|
}
|
|
@@ -946,7 +953,9 @@ async function createProject(options) {
|
|
|
946
953
|
packageDir,
|
|
947
954
|
monorepoRoot,
|
|
948
955
|
projectType: answers.projectType,
|
|
949
|
-
config
|
|
956
|
+
config,
|
|
957
|
+
templateVersion: getCompatibleTemplateVersion(workspaceManifest, answers.projectType),
|
|
958
|
+
templateCommit: getTemplateCommitSource(workspaceManifest)
|
|
950
959
|
});
|
|
951
960
|
await warnIfMissingRootNpmrc(monorepoRoot);
|
|
952
961
|
const installSucceeded = await installAtMonorepoRoot(monorepoRoot, answers.installDeps);
|
|
@@ -981,11 +990,15 @@ async function createProject(options) {
|
|
|
981
990
|
}
|
|
982
991
|
}
|
|
983
992
|
await scaffoldMonorepo(monorepoRoot, monorepoConfig);
|
|
993
|
+
const newWorkspaceManifest = createWorkspaceManifest();
|
|
994
|
+
await writeWorkspaceManifest(monorepoRoot, newWorkspaceManifest);
|
|
984
995
|
if (packageDir && answers.projectType !== "monorepo") await addPackageToMonorepo({
|
|
985
996
|
packageDir,
|
|
986
997
|
monorepoRoot,
|
|
987
998
|
projectType: answers.projectType,
|
|
988
|
-
config
|
|
999
|
+
config,
|
|
1000
|
+
templateVersion: getCompatibleTemplateVersion(newWorkspaceManifest, answers.projectType),
|
|
1001
|
+
templateCommit: getTemplateCommitSource(newWorkspaceManifest)
|
|
989
1002
|
});
|
|
990
1003
|
initGitRepo(monorepoRoot);
|
|
991
1004
|
const installSucceeded = await installAtMonorepoRoot(monorepoRoot, answers.installDeps);
|
|
@@ -1081,7 +1094,7 @@ function printNextStepsNew(answers, targetDir) {
|
|
|
1081
1094
|
const repoRel = shPath(relativePath);
|
|
1082
1095
|
if (repoRel !== ".") console.log(chalk.dim(` ${step++}.`), `cd ${repoRel}`);
|
|
1083
1096
|
if (!answers.installDeps) console.log(chalk.dim(` ${step++}.`), `${pm} install`);
|
|
1084
|
-
console.log(chalk.dim(` ${step++}.`), `Add a package: ${chalk.cyan(`
|
|
1097
|
+
console.log(chalk.dim(` ${step++}.`), `Add a package: ${chalk.cyan(`pnpm mosaic add webapp <name>`)}`);
|
|
1085
1098
|
break;
|
|
1086
1099
|
}
|
|
1087
1100
|
case "webapp":
|
|
@@ -1140,11 +1153,27 @@ function printNextStepsExisting(answers, packageDir) {
|
|
|
1140
1153
|
}
|
|
1141
1154
|
//#endregion
|
|
1142
1155
|
//#region src/index.ts
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
version: "1.0.0"
|
|
1146
|
-
}.version);
|
|
1156
|
+
const packageJson = readCreatePackageMetadata();
|
|
1157
|
+
program.name("create").description("Scaffold and manage Mosaic packages").version(packageJson.version);
|
|
1147
1158
|
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);
|
|
1159
|
+
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("--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) => {
|
|
1160
|
+
let projectType = options.type;
|
|
1161
|
+
let projectName = options.name;
|
|
1162
|
+
if (typeOrName === "webapp" || typeOrName === "library") {
|
|
1163
|
+
projectType = projectType ?? typeOrName;
|
|
1164
|
+
projectName = projectName ?? name;
|
|
1165
|
+
} else if (typeOrName === "monorepo") program.error("error: 'add' only supports webapp or library packages");
|
|
1166
|
+
else if (typeOrName) {
|
|
1167
|
+
if (name) program.error(`error: unknown package type "${typeOrName}". Expected webapp or library.`);
|
|
1168
|
+
projectName = projectName ?? typeOrName;
|
|
1169
|
+
}
|
|
1170
|
+
await createProject({
|
|
1171
|
+
...options,
|
|
1172
|
+
type: projectType,
|
|
1173
|
+
name: projectName,
|
|
1174
|
+
addOnly: true
|
|
1175
|
+
});
|
|
1176
|
+
});
|
|
1148
1177
|
program.command("status").description("Show template sync status for current app").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").action(async (options) => {
|
|
1149
1178
|
const { statusCommand } = await import("./status-CKe4aKso.js");
|
|
1150
1179
|
await statusCommand(options);
|
|
@@ -1154,11 +1183,11 @@ program.command("sync").description("Generate downstream sync context (template
|
|
|
1154
1183
|
await syncCommand(options);
|
|
1155
1184
|
});
|
|
1156
1185
|
program.command("upstream").description("Generate upstream context (app → template)").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").option("--files <patterns...>", "Specific files to propose upstream").action(async (options) => {
|
|
1157
|
-
const { upstreamCommand } = await import("./upstream-
|
|
1186
|
+
const { upstreamCommand } = await import("./upstream-gUHLWSR1.js");
|
|
1158
1187
|
await upstreamCommand(options);
|
|
1159
1188
|
});
|
|
1160
1189
|
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) => {
|
|
1161
|
-
const { initCommand } = await import("./init-
|
|
1190
|
+
const { initCommand } = await import("./init-sI9aIrkU.js");
|
|
1162
1191
|
await initCommand(options);
|
|
1163
1192
|
});
|
|
1164
1193
|
program.parse();
|