@sculptor/cli 0.2.2 → 0.3.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/dist/agents.js ADDED
@@ -0,0 +1,100 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { loadConfig } from "@sculptor/config";
4
+ import { loadPackageRegistry } from "./package-registry.js";
5
+ const cliVersion = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
6
+ const safeLoadConfig = (rootDir) => {
7
+ try {
8
+ const config = loadConfig(rootDir);
9
+ return {
10
+ srcRoot: String(config.framework.project?.srcRoot ?? "src"),
11
+ routingStyle: String(config.framework.routing?.style ?? "decorator")
12
+ };
13
+ }
14
+ catch {
15
+ return {
16
+ srcRoot: "src",
17
+ routingStyle: "decorator"
18
+ };
19
+ }
20
+ };
21
+ const safeLoadPackages = (rootDir) => {
22
+ try {
23
+ return Object.values(loadPackageRegistry(rootDir).packages)
24
+ .map((record) => record.name)
25
+ .sort((left, right) => left.localeCompare(right));
26
+ }
27
+ catch {
28
+ return [];
29
+ }
30
+ };
31
+ const buildPackageList = (packages) => packages.length > 0 ? packages.map((name) => `- \`${name}\``).join("\n") : "- _none detected_";
32
+ export const buildAgentsMarkdown = (rootDir) => {
33
+ const { srcRoot, routingStyle } = safeLoadConfig(rootDir);
34
+ const packages = safeLoadPackages(rootDir);
35
+ const version = cliVersion.version ?? "0.0.0";
36
+ return `# AGENTS.md
37
+
38
+ Sculptor CLI \`v${version}\`
39
+
40
+ ## Working Rules
41
+
42
+ - Exact package names only. Do not singularize or pluralize package identities.
43
+ - Treat package index files as the package contract.
44
+ - Update generated sections only when markers exist.
45
+ - Keep runtime composition explicit and scanner-friendly.
46
+ - Prefer warnings and diagnostics over silent mutation.
47
+
48
+ ## Package Architecture
49
+
50
+ - Project src root: \`${srcRoot}\`
51
+ - Routing style: \`${routingStyle}\`
52
+ - Package registry artifact: \`sculptor.packages.json\`
53
+ - Package index source of truth: \`index.ts\` inside each package
54
+ - Generated markers: \`[sculptor:imports:start]\`, \`[sculptor:exports:start]\`, \`[sculptor:package:start]\`
55
+
56
+ ## DI Conventions
57
+
58
+ - Use \`@Service()\`, \`@Repository()\`, \`@Middleware()\`, and \`@AutoInject()\`.
59
+ - Injection is explicit only.
60
+ - Constructor injection and property injection are both supported.
61
+ - Avoid hidden autowiring or token guessing.
62
+
63
+ ## Registry Conventions
64
+
65
+ - \`sc sync\` validates registry state and keeps \`sculptor.packages.json\` current.
66
+ - \`sc ls\` and \`sc pkg\` should reflect exact stored package names.
67
+ - \`sc doctor\` is the calm diagnostics entrypoint.
68
+ - \`sc update\` only updates \`@sculptor/cli\`.
69
+
70
+ ## Generator Conventions
71
+
72
+ - Generators must preserve manual code outside generated markers.
73
+ - Package generation should use exact names and deterministic paths.
74
+ - Use the configured src root unless an explicit \`in\` path is supplied.
75
+
76
+ ## Runtime Conventions
77
+
78
+ - Keep global registry composition thin.
79
+ - Flatten package composition internally.
80
+ - Preserve compatibility with legacy flat registries during migration.
81
+
82
+ ## CLI Conventions
83
+
84
+ - \`sc agents\` writes this file.
85
+ - \`sc agents refresh\` regenerates this file.
86
+ - \`sc doctor\` reports diagnostics without mutating the project.
87
+ - \`sc update\` only manages the global CLI binary.
88
+
89
+ ## Detected Packages
90
+
91
+ ${buildPackageList(packages)}
92
+ `;
93
+ };
94
+ export const writeAgentsMarkdown = (rootDir) => {
95
+ const filePath = path.join(rootDir, "AGENTS.md");
96
+ const content = buildAgentsMarkdown(rootDir);
97
+ fs.writeFileSync(filePath, `${content}\n`, "utf8");
98
+ return filePath;
99
+ };
100
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAC3B,EAAE,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC7C,CAAC;AAE1B,MAAM,cAAc,GAAG,CAAC,OAAe,EAA6C,EAAE;IACpF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC;YAC3D,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,IAAI,WAAW,CAAC;SACrE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,OAAO,EAAE,KAAK;YACd,YAAY,EAAE,WAAW;SAC1B,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAY,EAAE;IACrD,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;aACxD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;aAC5B,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,QAAkB,EAAU,EAAE,CACtD,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC;AAEjG,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAU,EAAE;IAC7D,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,IAAI,OAAO,CAAC;IAE9C,OAAO;;kBAES,OAAO;;;;;;;;;;;;wBAYD,OAAO;qBACV,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwC/B,gBAAgB,CAAC,QAAQ,CAAC;CAC3B,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAU,EAAE;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7C,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,OAAO,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}
package/dist/cli.js CHANGED
@@ -7,11 +7,47 @@ import { stdin, stdout } from "node:process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { loadConfig } from "@sculptor/config";
9
9
  import { getConfigValue, listConfigEntries, setConfigValue } from "./config-commands.js";
10
- import { controllerHelp, generateHelp, generateResourceFiles, middlewareHelp, moduleHelp, parseGenerateMode, readModeFromFlags, routeHelp, scaffoldProject, syncTestHarness, typeHelp, writeGeneratedFiles } from "./scaffold.js";
10
+ import { getPackageFlagValue, handleLsCommand, ensureRootRegistryForPackages, handlePackageCommand, handleRegisterCommand, handleSyncCommand, stripPackageFlag, validatePackageRegistryState } from "./package-commands.js";
11
+ import { getOwningPackage, loadPackageRegistry, savePackageRegistry, syncPackageRegistry, updatePackageIndexForRecord, upsertFileIntoRegistry } from "./package-registry.js";
12
+ import { writeAgentsMarkdown } from "./agents.js";
13
+ import { detectPackageManager, globalInstallArgsFor } from "./package-manager.js";
14
+ import { createDoctorReport, hasDoctorErrors, printDoctorReport } from "./diagnostics.js";
15
+ import { controllerHelp, dtoHelp, generateHelp, generateResourceFiles, middlewareHelp, moduleHelp, parseGenerateMode, readModeFromFlags, routeHelp, scaffoldProject, syncTestHarness, repositoryHelp, typeHelp, writeGeneratedFiles } from "./scaffold.js";
11
16
  import { loadPluginModule, resolvePluginManifest } from "./plugins.js";
12
17
  const cliPackageVersion = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
13
18
  const versionLabel = cliPackageVersion.version ?? "0.0.0";
14
- const isCommand = (value) => ["new", "start", "dev", "build", "lint", "test", "generate", "g", "help", "config", "add"].includes(value);
19
+ const isCommand = (value) => [
20
+ "new",
21
+ "start",
22
+ "dev",
23
+ "build",
24
+ "lint",
25
+ "test",
26
+ "sync",
27
+ "install",
28
+ "i",
29
+ "update",
30
+ "generate",
31
+ "g",
32
+ "pkg",
33
+ "package",
34
+ "ls",
35
+ "list",
36
+ "reg",
37
+ "register",
38
+ "r",
39
+ "ureg",
40
+ "unreg",
41
+ "unregister",
42
+ "ur",
43
+ "rm",
44
+ "remove",
45
+ "help",
46
+ "config",
47
+ "add",
48
+ "agents",
49
+ "doctor"
50
+ ].includes(value);
15
51
  const isFlag = (value) => value.startsWith("-");
16
52
  const isVersionFlag = (value) => ["-v", "--v", "--version", "version", "v"].includes(value);
17
53
  const sculptorCliBanner = String.raw `
@@ -76,6 +112,26 @@ const requireAppRoot = (cwd, command) => {
76
112
  }
77
113
  return cwd;
78
114
  };
115
+ const findAppRoot = (cwd) => {
116
+ let current = path.resolve(cwd);
117
+ for (;;) {
118
+ if (fs.existsSync(path.join(current, "sculptor.json"))) {
119
+ return current;
120
+ }
121
+ const parent = path.dirname(current);
122
+ if (parent === current) {
123
+ return undefined;
124
+ }
125
+ current = parent;
126
+ }
127
+ };
128
+ const requireOutsideAppRoot = (cwd, command) => {
129
+ const appRoot = findAppRoot(cwd);
130
+ if (appRoot) {
131
+ throw new Error(`${command} can only be run outside a Sculptor app root.`);
132
+ }
133
+ return cwd;
134
+ };
79
135
  const extractOutputDir = (args) => {
80
136
  const index = args.findIndex((arg) => arg === "in");
81
137
  if (index < 0 || index === args.length - 1) {
@@ -97,6 +153,15 @@ const getFlagValue = (args, names) => {
97
153
  return undefined;
98
154
  };
99
155
  const getFlagPresence = (args, names) => names.some((name) => args.includes(name) || args.some((arg) => arg.startsWith(`${name}=`)));
156
+ const resolveRegisteredPackagePath = (cwd, packageName) => {
157
+ const registry = loadPackageRegistry(cwd);
158
+ for (const record of Object.values(registry.packages)) {
159
+ if (record.name === packageName) {
160
+ return record.path;
161
+ }
162
+ }
163
+ return undefined;
164
+ };
100
165
  const getPrompt = (prompt) => {
101
166
  if (prompt) {
102
167
  return prompt;
@@ -182,12 +247,31 @@ sc <command> [options]
182
247
  - \`sc build\`
183
248
  - \`sc lint\`
184
249
  - \`sc test\`
250
+ - \`sc sync\`
251
+ - \`sc ls\`
252
+ - \`sc list\`
253
+ - \`sc pkg\`
254
+ - \`sc package\`
255
+ - \`sc reg <file>\` / \`sc register <file>\` / \`sc r <file>\`
256
+ - \`sc ureg <file>\` / \`sc unreg <file>\` / \`sc unregister <file>\` / \`sc ur <file>\`
257
+ - \`sc rm <file>\` / \`sc remove <file>\`
258
+ - \`sc install deps\`
259
+ - \`sc update\`
260
+ - \`sc doctor\`
185
261
  - \`sc generate\` or \`sc g\`
186
262
  - \`sc config <get|set|list>\`
187
263
  - \`sc add <plugin>\`
264
+ - \`sc agents\`
265
+ - \`sc agents refresh\`
188
266
  - \`sc help\`
189
267
  - \`sc help generate\`
190
268
  - \`sc help controller\`
269
+ - \`sc help repository\`
270
+ - \`sc help dto\`
271
+ - \`sc help pkg\`
272
+ - \`sc help package\`
273
+ - \`sc help ls\`
274
+ - \`sc help list\`
191
275
  - \`sc --version\` / \`sc -v\`
192
276
  - \`sc version\`
193
277
 
@@ -197,8 +281,12 @@ sc <command> [options]
197
281
  - \`service\` / \`s\`
198
282
  - \`module\` / \`m\` or \`mo\`
199
283
  - \`middleware\` / \`mw\`
284
+ - \`repository\` / \`repo\`
285
+ - \`dto\` / \`dto\`
200
286
  - \`type\` / \`t\`
201
287
  - \`route\` / \`r\`
288
+ - \`pkg\`
289
+ - \`package\`
202
290
 
203
291
  ## Binary Alias
204
292
 
@@ -226,6 +314,14 @@ const printHelp = (topic, log) => {
226
314
  log(middlewareHelp);
227
315
  return;
228
316
  }
317
+ if (topic === "repository") {
318
+ log(repositoryHelp);
319
+ return;
320
+ }
321
+ if (topic === "dto") {
322
+ log(dtoHelp);
323
+ return;
324
+ }
229
325
  if (topic === "type") {
230
326
  log(typeHelp);
231
327
  return;
@@ -238,10 +334,58 @@ const printHelp = (topic, log) => {
238
334
  log(`# Config\n\nUse \`sc config get\`, \`sc config set\`, or \`sc config list\`.`);
239
335
  return;
240
336
  }
337
+ if (topic === "sync") {
338
+ log(`# Sync\n\nUse \`sc sync\` to refresh \`sculptor.packages.json\`.`);
339
+ return;
340
+ }
341
+ if (topic === "pkg") {
342
+ log(`# Package\n\nUse \`sc pkg <name>\`, \`sc package <name>\`, \`sc pkg ls\`, or \`sc pkg rm <name>\`.\nPackage names are exact and are not normalized.`);
343
+ return;
344
+ }
345
+ if (topic === "package") {
346
+ log(`# Package\n\nUse \`sc pkg <name>\`, \`sc package <name>\`, \`sc pkg ls\`, or \`sc pkg rm <name>\`.\nPackage names are exact and are not normalized.`);
347
+ return;
348
+ }
349
+ if (topic === "ls") {
350
+ log(`# List\n\nUse \`sc ls\` or \`sc list\`, and \`sc ls -t\` for tree view.`);
351
+ return;
352
+ }
353
+ if (topic === "list") {
354
+ log(`# List\n\nUse \`sc ls\` or \`sc list\`, and \`sc ls -t\` for tree view.`);
355
+ return;
356
+ }
357
+ if (topic === "reg" || topic === "register" || topic === "r") {
358
+ log(`# Register\n\nUse \`sc reg <file>\`, \`sc register <file>\`, or \`sc r <file>\``);
359
+ return;
360
+ }
361
+ if (topic === "ureg" || topic === "unreg" || topic === "unregister" || topic === "ur") {
362
+ log(`# Unregister\n\nUse \`sc ureg <file>\`, \`sc unreg <file>\`, \`sc unregister <file>\`, or \`sc ur <file>\``);
363
+ return;
364
+ }
365
+ if (topic === "rm" || topic === "remove") {
366
+ log(`# Remove\n\nUse \`sc rm <file>\` or \`sc remove <file>\``);
367
+ return;
368
+ }
369
+ if (topic === "install") {
370
+ log(`# Install\n\nUsage: \`sc install deps\` or \`sc i deps\``);
371
+ return;
372
+ }
373
+ if (topic === "update") {
374
+ log(`# Update\n\nUsage: \`sc update\`\n\nUpdates the globally installed Sculptor CLI package.`);
375
+ return;
376
+ }
377
+ if (topic === "doctor") {
378
+ log(`# Doctor\n\nUsage: \`sc doctor\`\n\nRuns diagnostics for the current Sculptor project and package registry.`);
379
+ return;
380
+ }
241
381
  if (topic === "add") {
242
382
  log(`# Add\n\nUsage: \`sc add <plugin>\``);
243
383
  return;
244
384
  }
385
+ if (topic === "agents") {
386
+ log(`# Agents\n\nUsage: \`sc agents\` or \`sc agents refresh\`\n\nWrites \`AGENTS.md\` in the current directory.`);
387
+ return;
388
+ }
245
389
  printMainHelp(log);
246
390
  };
247
391
  const printVersion = (log) => {
@@ -311,16 +455,82 @@ const runSpawn = (command, args, cwd, spawn, log, env) => {
311
455
  process.exit(result.status);
312
456
  }
313
457
  };
458
+ const installScaffoldDependencies = (cwd, spawn, log) => {
459
+ runSpawn("npm", ["i"], cwd, spawn, log);
460
+ runSpawn("npm", ["i", "@sculptor/core@latest", "@sculptor/paws@latest"], cwd, spawn, log);
461
+ runSpawn("npm", ["i", "-D", "@sculptor/cli@latest", "@sculptor/config@latest", "@sculptor/router@latest"], cwd, spawn, log);
462
+ };
463
+ const updateGlobalCliPackage = (cwd, spawn, log) => {
464
+ const packageManager = detectPackageManager();
465
+ runSpawn(packageManager, globalInstallArgsFor(packageManager, ["@sculptor/cli@latest"]), cwd, spawn, log);
466
+ };
314
467
  const handleNew = async (args, cwd, prompt, spawn, log) => {
315
468
  printBanner(log, "SculptorTS CLI", `v${versionLabel}`);
316
469
  const metadata = await resolveProjectMetadata(args, cwd, prompt);
317
470
  const targetDir = path.join(cwd, metadata.appName);
318
- scaffoldProject(metadata, targetDir);
319
- runSpawn("npm", ["i"], targetDir, spawn, log);
320
- runSpawn("npm", ["i", "@sculptor/core@latest", "@sculptor/paws@latest"], targetDir, spawn, log);
321
- runSpawn("npm", ["i", "-D", "@sculptor/cli@latest", "@sculptor/config@latest", "@sculptor/router@latest"], targetDir, spawn, log);
471
+ const scaffolded = await scaffoldProject(metadata, targetDir, {
472
+ cwd,
473
+ prompt,
474
+ spawn,
475
+ log,
476
+ error: () => undefined
477
+ });
478
+ if (!scaffolded) {
479
+ return;
480
+ }
481
+ installScaffoldDependencies(targetDir, spawn, log);
322
482
  log(`Created SculptorTS project at ${targetDir}`);
323
483
  };
484
+ const handleInstall = (args, cwd, spawn, log, error) => {
485
+ const [subcommand] = args;
486
+ if (!subcommand || subcommand === "help") {
487
+ log(`Usage: sc install deps`);
488
+ return;
489
+ }
490
+ if (subcommand !== "deps") {
491
+ error(`Unknown install subcommand "${subcommand}".`);
492
+ process.exit(1);
493
+ }
494
+ const appRoot = findAppRoot(cwd);
495
+ if (!appRoot) {
496
+ throw new Error("sc install deps can only be run from a Sculptor app root.");
497
+ }
498
+ installScaffoldDependencies(appRoot, spawn, log);
499
+ log(`Installed Sculptor dependencies at ${appRoot}`);
500
+ };
501
+ const handleUpdate = (cwd, spawn, log, error) => {
502
+ requireOutsideAppRoot(cwd, "sc update");
503
+ try {
504
+ updateGlobalCliPackage(cwd, spawn, log);
505
+ log("Updated the globally installed Sculptor CLI package.");
506
+ }
507
+ catch (caught) {
508
+ const message = caught instanceof Error ? caught.message : String(caught);
509
+ error(message);
510
+ process.exit(1);
511
+ }
512
+ };
513
+ const handleDoctor = (cwd, log, error) => {
514
+ const report = createDoctorReport(cwd);
515
+ printDoctorReport(report, log);
516
+ if (hasDoctorErrors(report)) {
517
+ error("Doctor found blocking issues.");
518
+ process.exit(1);
519
+ }
520
+ };
521
+ const handleAgents = (args, cwd, log, error) => {
522
+ const [subcommand] = args;
523
+ if (subcommand && subcommand !== "refresh" && subcommand !== "help") {
524
+ error(`Unknown agents subcommand "${subcommand}".`);
525
+ process.exit(1);
526
+ }
527
+ if (subcommand === "help") {
528
+ log(`# Agents\n\nUsage: \`sc agents\` or \`sc agents refresh\`\n\nWrites \`AGENTS.md\` in the current directory.`);
529
+ return;
530
+ }
531
+ const filePath = writeAgentsMarkdown(cwd);
532
+ log(`Wrote ${path.relative(cwd, filePath) || "AGENTS.md"}`);
533
+ };
324
534
  const handleDev = (args, cwd, spawn, log) => {
325
535
  const appRoot = requireAppRoot(cwd, "sc dev");
326
536
  const devServer = resolveDefaultDevServer(cwd);
@@ -365,6 +575,19 @@ const handleStart = (args, cwd, spawn, log) => {
365
575
  const handleBuild = (cwd, spawn, log) => {
366
576
  const appRoot = requireAppRoot(cwd, "sc build");
367
577
  const appTsconfig = path.join(appRoot, "tsconfig.json");
578
+ try {
579
+ const report = validatePackageRegistryState(appRoot);
580
+ if (report.packageCountDetected > 0 || report.packageCountRegistered > 0 || report.messages.length > 0) {
581
+ log(`Packages detected: ${report.packageCountDetected}`);
582
+ log(`Packages registered: ${report.packageCountRegistered}`);
583
+ for (const message of report.messages) {
584
+ log(message);
585
+ }
586
+ }
587
+ }
588
+ catch (error) {
589
+ log(error instanceof Error ? error.message : String(error));
590
+ }
368
591
  runSpawn("npx", ["tsc", "-p", appTsconfig], appRoot, spawn, log);
369
592
  };
370
593
  const handleLint = (cwd, spawn, log) => {
@@ -427,10 +650,10 @@ const handleAdd = async (args, cwd, log, error) => {
427
650
  const manifest = resolvePluginManifest(module, pluginName);
428
651
  log(`Loaded plugin ${manifest.name} for ${appRoot}`);
429
652
  };
430
- const handleGenerate = (args, cwd, prompt, log, error) => {
653
+ const handleGenerate = async (args, cwd, prompt, spawn, log, error) => {
431
654
  const [kindInput, ...restInput] = args;
432
655
  if (!kindInput) {
433
- error("Usage: sc generate <controller|service|module|middleware|type|route> <name>");
656
+ error("Usage: sc generate <controller|service|repository|dto|module|middleware|type|route|pkg> <name>");
434
657
  process.exit(1);
435
658
  }
436
659
  const kindMap = {
@@ -438,25 +661,32 @@ const handleGenerate = (args, cwd, prompt, log, error) => {
438
661
  controller: "controller",
439
662
  s: "service",
440
663
  service: "service",
664
+ repo: "repository",
665
+ repository: "repository",
441
666
  m: "module",
442
667
  mo: "module",
443
668
  module: "module",
444
669
  mw: "middleware",
445
670
  middleware: "middleware",
671
+ dto: "dto",
446
672
  t: "type",
447
673
  type: "type",
448
674
  r: "route",
449
675
  route: "route",
450
- resource: "route"
676
+ resource: "route",
677
+ pkg: "pkg",
678
+ package: "pkg"
451
679
  };
452
680
  const kind = kindMap[kindInput];
453
681
  if (!kind) {
454
682
  error(`Unknown generator "${kindInput}".`);
455
683
  process.exit(1);
456
684
  }
457
- const { args: rest, outputDir } = extractOutputDir(restInput);
685
+ const { args: restAfterOutputDir, outputDir } = extractOutputDir(restInput);
686
+ const rest = stripPackageFlag(restAfterOutputDir);
458
687
  const positional = rest.filter((arg) => !isFlag(arg));
459
688
  const explicitName = positional[0];
689
+ const packageTarget = getPackageFlagValue(restAfterOutputDir);
460
690
  const fallbackMode = resolveDefaultMode(cwd);
461
691
  const devServer = resolveDefaultDevServer(cwd);
462
692
  const mode = parseGenerateMode(readModeFromFlags(rest, fallbackMode), fallbackMode);
@@ -475,6 +705,21 @@ const handleGenerate = (args, cwd, prompt, log, error) => {
475
705
  process.exit(1);
476
706
  }
477
707
  const appRoot = requireAppRoot(cwd, "sc generate");
708
+ const packagePath = packageTarget ? resolveRegisteredPackagePath(appRoot, packageTarget) : undefined;
709
+ if (packageTarget && !packagePath) {
710
+ error(`Package "${packageTarget}" is not registered.`);
711
+ process.exit(1);
712
+ }
713
+ const packageScopedOutput = packagePath !== undefined
714
+ ? (outputDir
715
+ ? path.posix.join(packagePath, outputDir)
716
+ : kind === "route"
717
+ ? path.posix.join(packagePath, "routes")
718
+ : packagePath)
719
+ : undefined;
720
+ const resolvedOutputDir = kind === "pkg"
721
+ ? outputDir ?? String(loadConfig(appRoot).framework.project?.srcRoot ?? "src")
722
+ : packageScopedOutput ?? outputDir;
478
723
  const resolvedName = explicitName ??
479
724
  (() => {
480
725
  if (!outputDir) {
@@ -483,14 +728,32 @@ const handleGenerate = (args, cwd, prompt, log, error) => {
483
728
  const parts = outputDir.split(/[\\/]/).filter(Boolean);
484
729
  return parts[parts.length - 1] ?? "index";
485
730
  })();
486
- const files = generateResourceFiles(kind, resolvedName, mode, devServer, outputDir, typeVariant, functionalRoutes, resolveTestingGenerate(appRoot));
731
+ const files = await generateResourceFiles(kind, resolvedName, mode, devServer, resolvedOutputDir, typeVariant, functionalRoutes, resolveTestingGenerate(appRoot), { cwd, prompt, spawn, log, error });
487
732
  const targetDir = appRoot;
488
- writeGeneratedFiles(targetDir, files);
733
+ if (!files) {
734
+ return;
735
+ }
736
+ await writeGeneratedFiles(targetDir, files, { cwd, prompt, spawn, log, error });
737
+ if (packagePath || kind === "pkg") {
738
+ const registry = syncPackageRegistry(appRoot);
739
+ for (const filePath of Object.keys(files)) {
740
+ upsertFileIntoRegistry(registry, filePath);
741
+ const owningPackage = getOwningPackage(registry, filePath);
742
+ if (owningPackage) {
743
+ updatePackageIndexForRecord(path.join(appRoot, owningPackage.index), owningPackage);
744
+ }
745
+ }
746
+ savePackageRegistry(appRoot, registry);
747
+ }
489
748
  if (resolveTestingGenerate(appRoot)) {
490
- syncTestHarness(targetDir);
749
+ await syncTestHarness(targetDir, { cwd, prompt, spawn, log, error });
750
+ }
751
+ syncPackageRegistry(appRoot);
752
+ if (kind === "pkg") {
753
+ ensureRootRegistryForPackages(appRoot);
491
754
  }
492
755
  log(`Generated ${kind} "${resolvedName}" using ${mode} mode.`);
493
- return Promise.resolve();
756
+ return;
494
757
  };
495
758
  export const runCli = async (argv = process.argv, options = {}) => {
496
759
  const cwd = options.cwd ?? process.cwd();
@@ -531,21 +794,60 @@ export const runCli = async (argv = process.argv, options = {}) => {
531
794
  case "build":
532
795
  handleBuild(cwd, spawn, log);
533
796
  return;
797
+ case "sync":
798
+ handleSyncCommand(args, { cwd, prompt, log, error });
799
+ return;
534
800
  case "lint":
535
801
  handleLint(cwd, spawn, log);
536
802
  return;
537
803
  case "test":
538
804
  handleTest(cwd, spawn, log);
539
805
  return;
806
+ case "install":
807
+ case "i":
808
+ handleInstall(args, cwd, spawn, log, error);
809
+ return;
810
+ case "update":
811
+ handleUpdate(cwd, spawn, log, error);
812
+ return;
813
+ case "doctor":
814
+ handleDoctor(cwd, log, error);
815
+ return;
540
816
  case "config":
541
817
  handleConfig(args, cwd, log, error);
542
818
  return;
543
819
  case "add":
544
820
  await handleAdd(args, cwd, log, error);
545
821
  return;
822
+ case "agents":
823
+ handleAgents(args, cwd, log, error);
824
+ return;
825
+ case "pkg":
826
+ case "package":
827
+ await handlePackageCommand(args, { cwd, prompt, log, error });
828
+ return;
829
+ case "ls":
830
+ case "list":
831
+ handleLsCommand(args, { cwd, prompt, log, error });
832
+ return;
833
+ case "reg":
834
+ case "register":
835
+ case "r":
836
+ await handleRegisterCommand("reg", args, { cwd, prompt, log, error });
837
+ return;
838
+ case "ureg":
839
+ case "unreg":
840
+ case "unregister":
841
+ case "ur":
842
+ await handleRegisterCommand("ureg", args, { cwd, prompt, log, error });
843
+ return;
844
+ case "rm":
845
+ case "remove":
846
+ await handleRegisterCommand("rm", args, { cwd, prompt, log, error });
847
+ return;
546
848
  case "generate":
547
849
  case "g":
548
- await handleGenerate(args, cwd, prompt, log, error);
850
+ await handleGenerate(args, cwd, prompt, spawn, log, error);
549
851
  return;
550
852
  }
551
853
  };