lattice-ui 0.4.3

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 (71) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +45 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +426 -0
  5. package/dist/commands/add.d.ts +3 -0
  6. package/dist/commands/add.js +156 -0
  7. package/dist/commands/create.d.ts +23 -0
  8. package/dist/commands/create.js +351 -0
  9. package/dist/commands/doctor.d.ts +2 -0
  10. package/dist/commands/doctor.js +164 -0
  11. package/dist/commands/init.d.ts +21 -0
  12. package/dist/commands/init.js +547 -0
  13. package/dist/commands/remove.d.ts +3 -0
  14. package/dist/commands/remove.js +113 -0
  15. package/dist/commands/selection.d.ts +6 -0
  16. package/dist/commands/selection.js +27 -0
  17. package/dist/commands/upgrade.d.ts +3 -0
  18. package/dist/commands/upgrade.js +150 -0
  19. package/dist/core/errors.d.ts +21 -0
  20. package/dist/core/errors.js +52 -0
  21. package/dist/core/fs/copy.d.ts +13 -0
  22. package/dist/core/fs/copy.js +129 -0
  23. package/dist/core/fs/json.d.ts +3 -0
  24. package/dist/core/fs/json.js +163 -0
  25. package/dist/core/fs/patch.d.ts +16 -0
  26. package/dist/core/fs/patch.js +89 -0
  27. package/dist/core/logger.d.ts +27 -0
  28. package/dist/core/logger.js +166 -0
  29. package/dist/core/npm/latest.d.ts +1 -0
  30. package/dist/core/npm/latest.js +40 -0
  31. package/dist/core/output.d.ts +8 -0
  32. package/dist/core/output.js +20 -0
  33. package/dist/core/pm/detect.d.ts +18 -0
  34. package/dist/core/pm/detect.js +147 -0
  35. package/dist/core/pm/npm.d.ts +2 -0
  36. package/dist/core/pm/npm.js +48 -0
  37. package/dist/core/pm/pnpm.d.ts +2 -0
  38. package/dist/core/pm/pnpm.js +48 -0
  39. package/dist/core/pm/types.d.ts +8 -0
  40. package/dist/core/pm/types.js +2 -0
  41. package/dist/core/pm/yarn.d.ts +2 -0
  42. package/dist/core/pm/yarn.js +48 -0
  43. package/dist/core/project/findRoot.d.ts +1 -0
  44. package/dist/core/project/findRoot.js +60 -0
  45. package/dist/core/project/readPackageJson.d.ts +13 -0
  46. package/dist/core/project/readPackageJson.js +41 -0
  47. package/dist/core/project/writePackageJson.d.ts +2 -0
  48. package/dist/core/project/writePackageJson.js +41 -0
  49. package/dist/core/prompt.d.ts +23 -0
  50. package/dist/core/prompt.js +217 -0
  51. package/dist/core/registry/load.d.ts +3 -0
  52. package/dist/core/registry/load.js +59 -0
  53. package/dist/core/registry/schema.d.ts +18 -0
  54. package/dist/core/registry/schema.js +87 -0
  55. package/dist/ctx.d.ts +27 -0
  56. package/dist/ctx.js +75 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +21 -0
  59. package/package.json +27 -0
  60. package/registry/components.json +118 -0
  61. package/registry/presets.json +6 -0
  62. package/templates/init/default.project.json.template +67 -0
  63. package/templates/init/package.json +22 -0
  64. package/templates/init/src/client/App.tsx +21 -0
  65. package/templates/init/src/client/main.client.tsx +26 -0
  66. package/templates/init/src/server/main.server.ts +3 -0
  67. package/templates/init/src/shared/constants.ts +1 -0
  68. package/templates/init/tsconfig.json +27 -0
  69. package/templates/init-lint/.prettierrc +7 -0
  70. package/templates/init-lint/eslint.config.mjs +51 -0
  71. package/templates/init-lint/package.json +19 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2026 astra-void
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @lattice-ui/cli
2
+
3
+ Node CLI for Lattice UI projects.
4
+
5
+ ## Install (Optional Global)
6
+
7
+ ```bash
8
+ npm i -g @lattice-ui/cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ lattice <command> [options]
15
+ ```
16
+
17
+ ### Commands
18
+
19
+ - `lattice create [project-path] [--yes] [--pm <pnpm|npm|yarn>] [--git] [--template rbxts] [--lint] [--no-lint]`
20
+ - `lattice init [--yes] [--dry-run] [--pm <pnpm|npm|yarn>] [--template rbxts] [--lint]`
21
+ - `lattice add [name...] [--preset <preset...>] [--pm <pnpm|npm|yarn>] [--yes] [--dry-run]`
22
+ - `lattice remove [name...] [--preset <preset...>] [--pm <pnpm|npm|yarn>] [--yes] [--dry-run]`
23
+ - `lattice upgrade [name...] [--preset <preset...>] [--pm <pnpm|npm|yarn>] [--yes] [--dry-run]`
24
+ - `lattice doctor [--pm <pnpm|npm|yarn>]`
25
+ - `lattice help`
26
+ - `lattice version`
27
+
28
+ ### Global options
29
+
30
+ - `--help`
31
+ - `--version`
32
+
33
+ ### Examples
34
+
35
+ ```bash
36
+ npx @lattice-ui/cli create
37
+ npx @lattice-ui/cli create my-game --pm npm --git --no-lint
38
+ npx @lattice-ui/cli init --dry-run
39
+ npx @lattice-ui/cli add dialog,toast --preset overlay
40
+ npx @lattice-ui/cli remove dialog --dry-run
41
+ npx @lattice-ui/cli upgrade --dry-run
42
+ npx @lattice-ui/cli doctor
43
+ ```
44
+
45
+ When `--pm` is omitted, the CLI resolves a package manager automatically from the project lockfile or installed managers. Use `--pm` to override that choice explicitly.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runCli(argv: string[]): Promise<void>;
package/dist/cli.js ADDED
@@ -0,0 +1,426 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runCli = runCli;
37
+ const node_fs_1 = require("node:fs");
38
+ const path = __importStar(require("node:path"));
39
+ const add_1 = require("./commands/add");
40
+ const create_1 = require("./commands/create");
41
+ const doctor_1 = require("./commands/doctor");
42
+ const init_1 = require("./commands/init");
43
+ const remove_1 = require("./commands/remove");
44
+ const upgrade_1 = require("./commands/upgrade");
45
+ const errors_1 = require("./core/errors");
46
+ const ctx_1 = require("./ctx");
47
+ const HELP_TEXT = `Lattice CLI
48
+
49
+ Usage:
50
+ lattice <command> [options]
51
+
52
+ Commands:
53
+ create [project-path] [--yes] [--pm <pnpm|npm|yarn>] [--git] [--template rbxts] [--lint] [--no-lint]
54
+ Create a new project from the rbxts template.
55
+
56
+ init [--yes] [--dry-run] [--pm <pnpm|npm|yarn>] [--template rbxts] [--lint]
57
+ Initialize Lattice in an existing project.
58
+
59
+ add [name...] [--preset <preset...>] [--pm <pnpm|npm|yarn>] [--yes] [--dry-run]
60
+ Install component packages and their required peers.
61
+
62
+ remove [name...] [--preset <preset...>] [--pm <pnpm|npm|yarn>] [--yes] [--dry-run]
63
+ Remove selected component packages.
64
+
65
+ upgrade [name...] [--preset <preset...>] [--pm <pnpm|npm|yarn>] [--yes] [--dry-run]
66
+ Upgrade installed @lattice-ui/* packages.
67
+
68
+ doctor [--pm <pnpm|npm|yarn>]
69
+ Check lockfiles, peers, and provider expectations.
70
+
71
+ help
72
+ Show this help message.
73
+
74
+ version
75
+ Print CLI version.
76
+
77
+ Global options:
78
+ --help
79
+ --version
80
+
81
+ Examples:
82
+ npx @lattice-ui/cli create
83
+ npx @lattice-ui/cli create my-game --pm npm --git --no-lint
84
+ npx @lattice-ui/cli init --dry-run
85
+ npx @lattice-ui/cli add dialog,toast --preset overlay
86
+ npx @lattice-ui/cli remove dialog --dry-run
87
+ npx @lattice-ui/cli upgrade --dry-run
88
+ npx @lattice-ui/cli doctor
89
+ `;
90
+ function splitList(value) {
91
+ return value
92
+ .split(",")
93
+ .map((item) => item.trim())
94
+ .filter((item) => item.length > 0);
95
+ }
96
+ function parseTopLevel(argv) {
97
+ if (argv.length === 0) {
98
+ return {
99
+ showHelp: true,
100
+ showVersion: false,
101
+ commandArgs: [],
102
+ };
103
+ }
104
+ const first = argv[0];
105
+ if (first === "--help" || first === "-h" || first === "help") {
106
+ return {
107
+ showHelp: true,
108
+ showVersion: false,
109
+ commandArgs: [],
110
+ };
111
+ }
112
+ if (first === "--version" || first === "-v" || first === "version") {
113
+ return {
114
+ showHelp: false,
115
+ showVersion: true,
116
+ commandArgs: [],
117
+ };
118
+ }
119
+ if (first.startsWith("-")) {
120
+ throw (0, errors_1.usageError)(`Unknown global option: ${first}`);
121
+ }
122
+ return {
123
+ command: first,
124
+ commandArgs: argv.slice(1),
125
+ showHelp: false,
126
+ showVersion: false,
127
+ };
128
+ }
129
+ function parseCreateArgs(args) {
130
+ const positionals = [];
131
+ let yes = false;
132
+ let pm;
133
+ let git;
134
+ let template;
135
+ let lint;
136
+ for (let index = 0; index < args.length; index += 1) {
137
+ const token = args[index];
138
+ if (token === "--yes") {
139
+ yes = true;
140
+ continue;
141
+ }
142
+ if (token === "--pm") {
143
+ const value = args[index + 1];
144
+ if (!value) {
145
+ throw (0, errors_1.usageError)("Missing value for --pm.");
146
+ }
147
+ pm = value;
148
+ index += 1;
149
+ continue;
150
+ }
151
+ if (token.startsWith("--pm=")) {
152
+ pm = token.slice("--pm=".length);
153
+ continue;
154
+ }
155
+ if (token === "--git") {
156
+ git = true;
157
+ continue;
158
+ }
159
+ if (token === "--lint") {
160
+ if (lint === false) {
161
+ throw (0, errors_1.usageError)("Cannot use --lint and --no-lint together.");
162
+ }
163
+ lint = true;
164
+ continue;
165
+ }
166
+ if (token === "--no-lint") {
167
+ if (lint === true) {
168
+ throw (0, errors_1.usageError)("Cannot use --lint and --no-lint together.");
169
+ }
170
+ lint = false;
171
+ continue;
172
+ }
173
+ if (token === "--template") {
174
+ const value = args[index + 1];
175
+ if (!value) {
176
+ throw (0, errors_1.usageError)("Missing value for --template.");
177
+ }
178
+ template = value;
179
+ index += 1;
180
+ continue;
181
+ }
182
+ if (token.startsWith("--template=")) {
183
+ template = token.slice("--template=".length);
184
+ continue;
185
+ }
186
+ if (token.startsWith("-")) {
187
+ throw (0, errors_1.usageError)(`Unknown option for create: ${token}`);
188
+ }
189
+ positionals.push(token);
190
+ }
191
+ if (positionals.length > 1) {
192
+ throw (0, errors_1.usageError)("create accepts at most one [project-path] argument.");
193
+ }
194
+ return {
195
+ projectPath: positionals[0],
196
+ yes,
197
+ pm,
198
+ git,
199
+ template,
200
+ lint,
201
+ };
202
+ }
203
+ function parseInitArgs(args) {
204
+ let yes = false;
205
+ let dryRun = false;
206
+ let pm;
207
+ let template;
208
+ let lint;
209
+ for (let index = 0; index < args.length; index += 1) {
210
+ const token = args[index];
211
+ if (token === "--yes") {
212
+ yes = true;
213
+ continue;
214
+ }
215
+ if (token === "--dry-run") {
216
+ dryRun = true;
217
+ continue;
218
+ }
219
+ if (token === "--pm") {
220
+ const value = args[index + 1];
221
+ if (!value) {
222
+ throw (0, errors_1.usageError)("Missing value for --pm.");
223
+ }
224
+ pm = value;
225
+ index += 1;
226
+ continue;
227
+ }
228
+ if (token.startsWith("--pm=")) {
229
+ pm = token.slice("--pm=".length);
230
+ continue;
231
+ }
232
+ if (token === "--template") {
233
+ const value = args[index + 1];
234
+ if (!value) {
235
+ throw (0, errors_1.usageError)("Missing value for --template.");
236
+ }
237
+ template = value;
238
+ index += 1;
239
+ continue;
240
+ }
241
+ if (token.startsWith("--template=")) {
242
+ template = token.slice("--template=".length);
243
+ continue;
244
+ }
245
+ if (token === "--lint") {
246
+ lint = true;
247
+ continue;
248
+ }
249
+ if (token.startsWith("-")) {
250
+ throw (0, errors_1.usageError)(`Unknown option for init: ${token}`);
251
+ }
252
+ throw (0, errors_1.usageError)("init does not accept positional arguments.");
253
+ }
254
+ return {
255
+ yes,
256
+ dryRun,
257
+ pm,
258
+ template,
259
+ lint,
260
+ };
261
+ }
262
+ function parseSelectionArgs(args, command) {
263
+ const names = [];
264
+ const presets = [];
265
+ let pm;
266
+ let yes = false;
267
+ let dryRun = false;
268
+ for (let index = 0; index < args.length; index += 1) {
269
+ const token = args[index];
270
+ if (token === "--preset") {
271
+ const value = args[index + 1];
272
+ if (!value) {
273
+ throw (0, errors_1.usageError)("Missing value for --preset.");
274
+ }
275
+ presets.push(...splitList(value));
276
+ index += 1;
277
+ continue;
278
+ }
279
+ if (token.startsWith("--preset=")) {
280
+ presets.push(...splitList(token.slice("--preset=".length)));
281
+ continue;
282
+ }
283
+ if (token === "--pm") {
284
+ const value = args[index + 1];
285
+ if (!value) {
286
+ throw (0, errors_1.usageError)("Missing value for --pm.");
287
+ }
288
+ pm = value;
289
+ index += 1;
290
+ continue;
291
+ }
292
+ if (token.startsWith("--pm=")) {
293
+ pm = token.slice("--pm=".length);
294
+ continue;
295
+ }
296
+ if (token === "--yes") {
297
+ yes = true;
298
+ continue;
299
+ }
300
+ if (token === "--dry-run") {
301
+ dryRun = true;
302
+ continue;
303
+ }
304
+ if (token.startsWith("-")) {
305
+ throw (0, errors_1.usageError)(`Unknown option for ${command}: ${token}`);
306
+ }
307
+ names.push(...splitList(token));
308
+ }
309
+ return {
310
+ names,
311
+ presets,
312
+ pm,
313
+ yes,
314
+ dryRun,
315
+ };
316
+ }
317
+ function parseDoctorArgs(args) {
318
+ let pm;
319
+ for (let index = 0; index < args.length; index += 1) {
320
+ const token = args[index];
321
+ if (token === "--pm") {
322
+ const value = args[index + 1];
323
+ if (!value) {
324
+ throw (0, errors_1.usageError)("Missing value for --pm.");
325
+ }
326
+ pm = value;
327
+ index += 1;
328
+ continue;
329
+ }
330
+ if (token.startsWith("--pm=")) {
331
+ pm = token.slice("--pm=".length);
332
+ continue;
333
+ }
334
+ if (token.startsWith("-")) {
335
+ throw (0, errors_1.usageError)(`Unknown option for doctor: ${token}`);
336
+ }
337
+ throw (0, errors_1.usageError)("doctor does not accept positional arguments.");
338
+ }
339
+ return { pm };
340
+ }
341
+ async function readCliVersion() {
342
+ const packageJsonPath = path.resolve(__dirname, "../package.json");
343
+ const content = await node_fs_1.promises.readFile(packageJsonPath, "utf8");
344
+ const parsed = JSON.parse(content);
345
+ return parsed.version ?? "0.0.0";
346
+ }
347
+ function printHelp() {
348
+ process.stdout.write(HELP_TEXT);
349
+ }
350
+ async function runCli(argv) {
351
+ const normalizedArgv = argv[0] === "--" ? argv.slice(1) : argv;
352
+ const parsed = parseTopLevel(normalizedArgv);
353
+ if (parsed.showVersion) {
354
+ process.stdout.write(`${await readCliVersion()}\n`);
355
+ return;
356
+ }
357
+ if (parsed.showHelp || !parsed.command) {
358
+ printHelp();
359
+ return;
360
+ }
361
+ if (parsed.command === "init") {
362
+ const initArgs = parseInitArgs(parsed.commandArgs);
363
+ await (0, init_1.runInitCommand)({
364
+ cwd: process.cwd(),
365
+ ...initArgs,
366
+ });
367
+ return;
368
+ }
369
+ if (parsed.command === "create") {
370
+ const createArgs = parseCreateArgs(parsed.commandArgs);
371
+ await (0, create_1.runCreateCommand)({
372
+ cwd: process.cwd(),
373
+ ...createArgs,
374
+ });
375
+ return;
376
+ }
377
+ if (parsed.command === "add") {
378
+ const selection = parseSelectionArgs(parsed.commandArgs, "add");
379
+ const ctx = await (0, ctx_1.createContext)({
380
+ cwd: process.cwd(),
381
+ pm: selection.pm,
382
+ dryRun: selection.dryRun,
383
+ yes: selection.yes,
384
+ verbose: false,
385
+ });
386
+ await (0, add_1.runAddCommand)(ctx, { names: selection.names, presets: selection.presets });
387
+ return;
388
+ }
389
+ if (parsed.command === "upgrade") {
390
+ const selection = parseSelectionArgs(parsed.commandArgs, "upgrade");
391
+ const ctx = await (0, ctx_1.createContext)({
392
+ cwd: process.cwd(),
393
+ pm: selection.pm,
394
+ dryRun: selection.dryRun,
395
+ yes: selection.yes,
396
+ verbose: false,
397
+ });
398
+ await (0, upgrade_1.runUpgradeCommand)(ctx, { names: selection.names, presets: selection.presets });
399
+ return;
400
+ }
401
+ if (parsed.command === "remove") {
402
+ const selection = parseSelectionArgs(parsed.commandArgs, "remove");
403
+ const ctx = await (0, ctx_1.createContext)({
404
+ cwd: process.cwd(),
405
+ pm: selection.pm,
406
+ dryRun: selection.dryRun,
407
+ yes: selection.yes,
408
+ verbose: false,
409
+ });
410
+ await (0, remove_1.runRemoveCommand)(ctx, { names: selection.names, presets: selection.presets });
411
+ return;
412
+ }
413
+ if (parsed.command === "doctor") {
414
+ const doctorArgs = parseDoctorArgs(parsed.commandArgs);
415
+ const ctx = await (0, ctx_1.createContext)({
416
+ cwd: process.cwd(),
417
+ pm: doctorArgs.pm,
418
+ dryRun: false,
419
+ yes: false,
420
+ verbose: false,
421
+ });
422
+ await (0, doctor_1.runDoctorCommand)(ctx);
423
+ return;
424
+ }
425
+ throw (0, errors_1.usageError)(`Unknown command: ${parsed.command}`);
426
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliContext } from "../ctx";
2
+ import { type SelectionInput } from "./selection";
3
+ export declare function runAddCommand(ctx: CliContext, input: SelectionInput): Promise<void>;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAddCommand = runAddCommand;
4
+ const errors_1 = require("../core/errors");
5
+ const patch_1 = require("../core/fs/patch");
6
+ const output_1 = require("../core/output");
7
+ const readPackageJson_1 = require("../core/project/readPackageJson");
8
+ const prompt_1 = require("../core/prompt");
9
+ const schema_1 = require("../core/registry/schema");
10
+ const selection_1 = require("./selection");
11
+ async function resolveSelectionInput(ctx, input) {
12
+ if (input.names.length > 0 || input.presets.length > 0) {
13
+ return input;
14
+ }
15
+ if (ctx.options.yes) {
16
+ throw (0, errors_1.usageError)("No components selected. Provide component names or --preset when using --yes.");
17
+ }
18
+ const runtime = {
19
+ yes: ctx.options.yes,
20
+ };
21
+ const presetOptions = Object.keys(ctx.registry.presets)
22
+ .sort((left, right) => left.localeCompare(right))
23
+ .map((presetName) => ({
24
+ label: `${presetName} (${ctx.registry.presets[presetName].join(", ")})`,
25
+ value: presetName,
26
+ }));
27
+ const componentOptions = Object.keys(ctx.registry.packages)
28
+ .sort((left, right) => left.localeCompare(right))
29
+ .map((componentName) => ({
30
+ label: componentName,
31
+ value: componentName,
32
+ }));
33
+ const selectedPresets = await (0, prompt_1.promptMultiSelect)(runtime, "Select presets (optional)", presetOptions, {
34
+ allowEmpty: true,
35
+ });
36
+ const selectedComponents = await (0, prompt_1.promptMultiSelect)(runtime, "Select components (optional)", componentOptions, {
37
+ allowEmpty: true,
38
+ });
39
+ return {
40
+ names: selectedComponents,
41
+ presets: selectedPresets,
42
+ };
43
+ }
44
+ async function runAddCommand(ctx, input) {
45
+ const resolvedInput = await resolveSelectionInput(ctx, input);
46
+ const components = (0, selection_1.resolveComponentSelection)(ctx, resolvedInput);
47
+ const specs = new Set();
48
+ const optionalProviders = new Set();
49
+ const notes = [];
50
+ for (const component of components) {
51
+ const entry = ctx.registry.packages[component];
52
+ specs.add(entry.npm);
53
+ for (const peer of entry.peers ?? []) {
54
+ specs.add(peer);
55
+ }
56
+ for (const rawProvider of entry.providers ?? []) {
57
+ const provider = (0, schema_1.parseProviderRequirement)(rawProvider);
58
+ if (provider.optional) {
59
+ optionalProviders.add(provider.raw);
60
+ }
61
+ else {
62
+ specs.add(provider.packageName);
63
+ }
64
+ }
65
+ for (const note of entry.notes ?? []) {
66
+ notes.push(`${component}: ${note}`);
67
+ }
68
+ }
69
+ const packageJson = await (0, readPackageJson_1.readPackageJson)(ctx.projectRoot);
70
+ const installed = (0, patch_1.getDependencyNames)(packageJson);
71
+ const plannedSpecs = [...specs]
72
+ .filter((spec) => !installed.has((0, patch_1.toPackageName)(spec)))
73
+ .sort((left, right) => left.localeCompare(right));
74
+ const localLattice = (0, output_1.resolveLocalLatticeCommand)(ctx.pmName);
75
+ ctx.logger.section("Selecting");
76
+ ctx.logger.kv("Project", ctx.projectRoot);
77
+ ctx.logger.section("Planning");
78
+ const componentSummary = (0, output_1.summarizeItems)(components);
79
+ ctx.logger.kv("Components", String(componentSummary.total));
80
+ if (componentSummary.total > 0) {
81
+ ctx.logger.list(componentSummary.visible);
82
+ if (componentSummary.hidden > 0) {
83
+ ctx.logger.step(`...and ${componentSummary.hidden} more`);
84
+ }
85
+ }
86
+ const plannedSummary = (0, output_1.summarizeItems)(plannedSpecs);
87
+ ctx.logger.kv("Packages to install", String(plannedSummary.total));
88
+ if (plannedSummary.total > 0) {
89
+ ctx.logger.list(plannedSummary.visible);
90
+ if (plannedSummary.hidden > 0) {
91
+ ctx.logger.step(`...and ${plannedSummary.hidden} more`);
92
+ }
93
+ }
94
+ if (ctx.options.dryRun) {
95
+ ctx.logger.section("Dry Run");
96
+ if (plannedSpecs.length > 0) {
97
+ ctx.logger.step(`[dry-run] ${ctx.pmName} add ${plannedSpecs.join(" ")}`);
98
+ }
99
+ else {
100
+ ctx.logger.step("[dry-run] No install actions required.");
101
+ }
102
+ ctx.logger.step("No files were changed.");
103
+ }
104
+ else {
105
+ ctx.logger.section("Applying");
106
+ if (plannedSpecs.length > 0) {
107
+ ctx.logger.step(`${ctx.pmName} add ${plannedSpecs.join(" ")}`);
108
+ const confirmed = await ctx.logger.confirm(`Install ${plannedSpecs.length} package(s) in ${ctx.projectRoot}?`);
109
+ if (!confirmed) {
110
+ ctx.logger.section("Result");
111
+ ctx.logger.warn("Add command cancelled.");
112
+ ctx.logger.section("Next Steps");
113
+ ctx.logger.step(`${localLattice} doctor`);
114
+ return;
115
+ }
116
+ const spinner = ctx.logger.spinner(`Installing ${plannedSpecs.length} package(s)...`);
117
+ await ctx.pm.add(false, plannedSpecs, ctx.projectRoot);
118
+ spinner.succeed("Dependencies installed.");
119
+ }
120
+ else {
121
+ ctx.logger.step("No installation required.");
122
+ }
123
+ }
124
+ ctx.logger.section("Result");
125
+ if (plannedSpecs.length === 0) {
126
+ ctx.logger.success("No new packages were needed.");
127
+ }
128
+ else {
129
+ ctx.logger.success(`Added components: ${components.join(", ")}`);
130
+ }
131
+ ctx.logger.kv("Installed packages", String(plannedSpecs.length));
132
+ if (optionalProviders.size > 0) {
133
+ const optionalSummary = (0, output_1.summarizeItems)([...optionalProviders].sort((left, right) => left.localeCompare(right)));
134
+ ctx.logger.warn(`Optional providers available: ${optionalSummary.total}`);
135
+ ctx.logger.list(optionalSummary.visible);
136
+ if (optionalSummary.hidden > 0) {
137
+ ctx.logger.step(`...and ${optionalSummary.hidden} more`);
138
+ }
139
+ }
140
+ if (notes.length > 0) {
141
+ const noteSummary = (0, output_1.summarizeItems)(notes);
142
+ ctx.logger.info(`Notes: ${noteSummary.total}`);
143
+ ctx.logger.list(noteSummary.visible);
144
+ if (noteSummary.hidden > 0) {
145
+ ctx.logger.step(`...and ${noteSummary.hidden} more`);
146
+ }
147
+ }
148
+ ctx.logger.section("Next Steps");
149
+ ctx.logger.step(`${localLattice} doctor`);
150
+ if (optionalProviders.size > 0) {
151
+ ctx.logger.step(`${localLattice} add <component>`);
152
+ }
153
+ if (notes.length > 0) {
154
+ ctx.logger.step("Review notes above before integrating components.");
155
+ }
156
+ }
@@ -0,0 +1,23 @@
1
+ import { createLogger } from "../core/logger";
2
+ import { resolveLatestVersions } from "../core/npm/latest";
3
+ import { detectPackageManager } from "../core/pm/detect";
4
+ import { promptInput, promptSelect } from "../core/prompt";
5
+ export interface CreateCommandInput {
6
+ cwd: string;
7
+ projectPath?: string;
8
+ pm?: string;
9
+ yes: boolean;
10
+ git?: boolean;
11
+ template?: string;
12
+ lint?: boolean;
13
+ }
14
+ interface CreateCommandRuntimeOverrides {
15
+ detectPackageManagerFn?: typeof detectPackageManager;
16
+ resolveLatestVersionsFn?: typeof resolveLatestVersions;
17
+ runProcessFn?: (command: string, args: string[], cwd: string) => Promise<void>;
18
+ createLoggerFn?: typeof createLogger;
19
+ promptInputFn?: typeof promptInput;
20
+ promptSelectFn?: typeof promptSelect;
21
+ }
22
+ export declare function runCreateCommand(input: CreateCommandInput, runtimeOverrides?: CreateCommandRuntimeOverrides): Promise<void>;
23
+ export {};