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
@@ -0,0 +1,547 @@
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.runInitCommand = runInitCommand;
37
+ const node_fs_1 = require("node:fs");
38
+ const path = __importStar(require("node:path"));
39
+ const errors_1 = require("../core/errors");
40
+ const copy_1 = require("../core/fs/copy");
41
+ const logger_1 = require("../core/logger");
42
+ const latest_1 = require("../core/npm/latest");
43
+ const output_1 = require("../core/output");
44
+ const detect_1 = require("../core/pm/detect");
45
+ const findRoot_1 = require("../core/project/findRoot");
46
+ const readPackageJson_1 = require("../core/project/readPackageJson");
47
+ const writePackageJson_1 = require("../core/project/writePackageJson");
48
+ const prompt_1 = require("../core/prompt");
49
+ const SUPPORTED_TEMPLATE = "rbxts";
50
+ const GITIGNORE_ENTRIES = [
51
+ "node_modules",
52
+ "out",
53
+ "include",
54
+ "*.rbxl",
55
+ "*.rbxlx",
56
+ "*.rbxm",
57
+ "*.rbxmx",
58
+ "*.rbxl.lock",
59
+ "*.rbxlx.lock",
60
+ "*.rbxm.lock",
61
+ "*.rbxmx.lock",
62
+ "*.tsbuildinfo",
63
+ ".pnpm-store",
64
+ ".DS_Store",
65
+ ];
66
+ const PROJECT_DIRECTORIES = ["include", "out/shared", "out/server", "out/client"];
67
+ const PNPM_NPMRC_PATH = ".npmrc";
68
+ const PNPM_NODE_LINKER_LINE = "node-linker=hoisted";
69
+ const CORE_VERSION_PACKAGES = {
70
+ latticeStyle: "@lattice-ui/style",
71
+ latticeCli: "@lattice-ui/cli",
72
+ rbxtsReact: "@rbxts/react",
73
+ rbxtsReactRoblox: "@rbxts/react-roblox",
74
+ rbxtsCompilerTypes: "@rbxts/compiler-types",
75
+ rbxtsTypes: "@rbxts/types",
76
+ robloxTs: "roblox-ts",
77
+ typescript: "typescript",
78
+ };
79
+ const LINT_VERSION_PACKAGES = {
80
+ eslint: "eslint",
81
+ eslintEslintrc: "@eslint/eslintrc",
82
+ eslintJs: "@eslint/js",
83
+ eslintConfigPrettier: "eslint-config-prettier",
84
+ eslintPluginPrettier: "eslint-plugin-prettier",
85
+ eslintPluginRobloxTs: "eslint-plugin-roblox-ts",
86
+ typescriptEslintPlugin: "@typescript-eslint/eslint-plugin",
87
+ typescriptEslintParser: "@typescript-eslint/parser",
88
+ prettier: "prettier",
89
+ };
90
+ function normalizeTemplate(template) {
91
+ const value = template?.trim();
92
+ if (!value || value.length === 0) {
93
+ return SUPPORTED_TEMPLATE;
94
+ }
95
+ return value;
96
+ }
97
+ async function selectTemplate(providedTemplate) {
98
+ const normalized = normalizeTemplate(providedTemplate);
99
+ if (normalized !== SUPPORTED_TEMPLATE) {
100
+ throw (0, errors_1.usageError)(`Unknown template: ${normalized}. Supported template: ${SUPPORTED_TEMPLATE}.`);
101
+ }
102
+ return normalized;
103
+ }
104
+ async function selectLintEnabled(runtime, providedLint, promptConfirmFn) {
105
+ if (providedLint !== undefined) {
106
+ return providedLint;
107
+ }
108
+ if (runtime.yes) {
109
+ return false;
110
+ }
111
+ return promptConfirmFn(runtime, "Set up ESLint + Prettier?", { defaultValue: false });
112
+ }
113
+ async function readTemplateJson(templateDir, fileName, replacements) {
114
+ const filePath = path.join(templateDir, fileName);
115
+ const raw = await node_fs_1.promises.readFile(filePath, "utf8");
116
+ let content = raw;
117
+ for (const [from, to] of Object.entries(replacements)) {
118
+ content = content.split(from).join(to);
119
+ }
120
+ return JSON.parse(content);
121
+ }
122
+ function sortStringRecord(record) {
123
+ return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
124
+ }
125
+ function mergeMissingRecord(current, incoming, options) {
126
+ if (!incoming || Object.keys(incoming).length === 0) {
127
+ return {
128
+ changed: false,
129
+ next: current,
130
+ added: [],
131
+ };
132
+ }
133
+ const next = { ...(current ?? {}) };
134
+ const added = [];
135
+ for (const [key, value] of Object.entries(incoming)) {
136
+ if (next[key] !== undefined) {
137
+ continue;
138
+ }
139
+ next[key] = value;
140
+ added.push(key);
141
+ }
142
+ if (added.length === 0) {
143
+ return {
144
+ changed: false,
145
+ next: current,
146
+ added,
147
+ };
148
+ }
149
+ return {
150
+ changed: true,
151
+ next: options?.sortKeys ? sortStringRecord(next) : next,
152
+ added,
153
+ };
154
+ }
155
+ function planManifestChanges(currentManifest, templates) {
156
+ let nextManifest = { ...currentManifest };
157
+ const addedScripts = [];
158
+ const addedDependencies = [];
159
+ const addedDevDependencies = [];
160
+ let changed = false;
161
+ for (const template of templates) {
162
+ const scripts = mergeMissingRecord(nextManifest.scripts, template.scripts);
163
+ if (scripts.changed) {
164
+ nextManifest = {
165
+ ...nextManifest,
166
+ scripts: scripts.next,
167
+ };
168
+ addedScripts.push(...scripts.added);
169
+ changed = true;
170
+ }
171
+ const dependencies = mergeMissingRecord(nextManifest.dependencies, template.dependencies, { sortKeys: true });
172
+ if (dependencies.changed) {
173
+ nextManifest = {
174
+ ...nextManifest,
175
+ dependencies: dependencies.next,
176
+ };
177
+ addedDependencies.push(...dependencies.added);
178
+ changed = true;
179
+ }
180
+ const devDependencies = mergeMissingRecord(nextManifest.devDependencies, template.devDependencies, {
181
+ sortKeys: true,
182
+ });
183
+ if (devDependencies.changed) {
184
+ nextManifest = {
185
+ ...nextManifest,
186
+ devDependencies: devDependencies.next,
187
+ };
188
+ addedDevDependencies.push(...devDependencies.added);
189
+ changed = true;
190
+ }
191
+ }
192
+ return {
193
+ changed,
194
+ nextManifest,
195
+ addedScripts: [...new Set(addedScripts)],
196
+ addedDependencies: [...new Set(addedDependencies)],
197
+ addedDevDependencies: [...new Set(addedDevDependencies)],
198
+ };
199
+ }
200
+ async function planGitignore(projectRoot) {
201
+ const gitignorePath = path.join(projectRoot, ".gitignore");
202
+ let currentContent = "";
203
+ let exists = true;
204
+ try {
205
+ currentContent = await node_fs_1.promises.readFile(gitignorePath, "utf8");
206
+ }
207
+ catch (error) {
208
+ const nodeError = error;
209
+ if (nodeError.code !== "ENOENT") {
210
+ throw error;
211
+ }
212
+ exists = false;
213
+ }
214
+ const existingEntries = new Set(currentContent
215
+ .split(/\r?\n/)
216
+ .map((line) => line.trim())
217
+ .filter((line) => line.length > 0));
218
+ const addedEntries = GITIGNORE_ENTRIES.filter((entry) => !existingEntries.has(entry));
219
+ if (addedEntries.length === 0) {
220
+ return {
221
+ changed: false,
222
+ created: false,
223
+ addedEntries: [],
224
+ nextContent: currentContent,
225
+ };
226
+ }
227
+ let nextContent = currentContent;
228
+ if (nextContent.length > 0 && !nextContent.endsWith("\n")) {
229
+ nextContent += "\n";
230
+ }
231
+ nextContent += `${addedEntries.join("\n")}\n`;
232
+ return {
233
+ changed: true,
234
+ created: !exists,
235
+ addedEntries,
236
+ nextContent,
237
+ };
238
+ }
239
+ async function ensureProjectDirectories(projectRoot) {
240
+ await Promise.all(PROJECT_DIRECTORIES.map((directory) => node_fs_1.promises.mkdir(path.join(projectRoot, directory), { recursive: true })));
241
+ }
242
+ function inferProjectName(projectRoot, manifest) {
243
+ const packageName = manifest.name?.trim();
244
+ if (packageName && packageName.length > 0) {
245
+ return packageName;
246
+ }
247
+ return path.basename(projectRoot);
248
+ }
249
+ function buildVersionReplacements(projectName, versions) {
250
+ return {
251
+ __PROJECT_NAME__: projectName,
252
+ __LATTICE_STYLE_VERSION__: versions[CORE_VERSION_PACKAGES.latticeStyle],
253
+ __LATTICE_CLI_VERSION__: versions[CORE_VERSION_PACKAGES.latticeCli],
254
+ __RBXTS_REACT_VERSION__: versions[CORE_VERSION_PACKAGES.rbxtsReact],
255
+ __RBXTS_REACT_ROBLOX_VERSION__: versions[CORE_VERSION_PACKAGES.rbxtsReactRoblox],
256
+ __RBXTS_COMPILER_TYPES_VERSION__: versions[CORE_VERSION_PACKAGES.rbxtsCompilerTypes],
257
+ __RBXTS_TYPES_VERSION__: versions[CORE_VERSION_PACKAGES.rbxtsTypes],
258
+ __ROBLOX_TS_VERSION__: versions[CORE_VERSION_PACKAGES.robloxTs],
259
+ __TYPESCRIPT_VERSION__: versions[CORE_VERSION_PACKAGES.typescript],
260
+ __ESLINT_VERSION__: versions[LINT_VERSION_PACKAGES.eslint] ?? "",
261
+ __ESLINT_ESLINTRC_VERSION__: versions[LINT_VERSION_PACKAGES.eslintEslintrc] ?? "",
262
+ __ESLINT_JS_VERSION__: versions[LINT_VERSION_PACKAGES.eslintJs] ?? "",
263
+ __ESLINT_CONFIG_PRETTIER_VERSION__: versions[LINT_VERSION_PACKAGES.eslintConfigPrettier] ?? "",
264
+ __ESLINT_PLUGIN_PRETTIER_VERSION__: versions[LINT_VERSION_PACKAGES.eslintPluginPrettier] ?? "",
265
+ __ESLINT_PLUGIN_ROBLOX_TS_VERSION__: versions[LINT_VERSION_PACKAGES.eslintPluginRobloxTs] ?? "",
266
+ __TYPESCRIPT_ESLINT_PLUGIN_VERSION__: versions[LINT_VERSION_PACKAGES.typescriptEslintPlugin] ?? "",
267
+ __TYPESCRIPT_ESLINT_PARSER_VERSION__: versions[LINT_VERSION_PACKAGES.typescriptEslintParser] ?? "",
268
+ __PRETTIER_VERSION__: versions[LINT_VERSION_PACKAGES.prettier] ?? "",
269
+ };
270
+ }
271
+ async function planPnpmNpmrc(projectRoot, packageManager) {
272
+ if (packageManager !== "pnpm") {
273
+ return {
274
+ changed: false,
275
+ created: false,
276
+ nextContent: "",
277
+ };
278
+ }
279
+ const npmrcPath = path.join(projectRoot, PNPM_NPMRC_PATH);
280
+ let currentContent = "";
281
+ let exists = true;
282
+ try {
283
+ currentContent = await node_fs_1.promises.readFile(npmrcPath, "utf8");
284
+ }
285
+ catch (error) {
286
+ const nodeError = error;
287
+ if (nodeError.code !== "ENOENT") {
288
+ throw error;
289
+ }
290
+ exists = false;
291
+ }
292
+ const eol = currentContent.includes("\r\n") ? "\r\n" : "\n";
293
+ const lines = currentContent.length > 0 ? currentContent.split(/\r?\n/) : [];
294
+ const nodeLinkerIndex = lines.findIndex((line) => /^\s*node-linker\s*=/.test(line));
295
+ if (nodeLinkerIndex >= 0) {
296
+ if (lines[nodeLinkerIndex].trim() === PNPM_NODE_LINKER_LINE) {
297
+ return {
298
+ changed: false,
299
+ created: false,
300
+ nextContent: currentContent,
301
+ };
302
+ }
303
+ lines[nodeLinkerIndex] = PNPM_NODE_LINKER_LINE;
304
+ return {
305
+ changed: true,
306
+ created: false,
307
+ nextContent: lines.join(eol),
308
+ };
309
+ }
310
+ if (!exists) {
311
+ return {
312
+ changed: true,
313
+ created: true,
314
+ nextContent: `${PNPM_NODE_LINKER_LINE}\n`,
315
+ };
316
+ }
317
+ return {
318
+ changed: true,
319
+ created: false,
320
+ nextContent: currentContent.endsWith("\n")
321
+ ? `${currentContent}${PNPM_NODE_LINKER_LINE}${eol}`
322
+ : `${currentContent}${eol}${PNPM_NODE_LINKER_LINE}${eol}`,
323
+ };
324
+ }
325
+ function collectChangedFiles(templateReport, lintReport, manifestPlan, gitignorePlan, npmrcPlan) {
326
+ const files = [
327
+ ...templateReport.created,
328
+ ...templateReport.merged,
329
+ ...(lintReport ? [...lintReport.created, ...lintReport.merged] : []),
330
+ ];
331
+ if (manifestPlan.changed) {
332
+ files.push("package.json");
333
+ }
334
+ if (gitignorePlan.changed) {
335
+ files.push(".gitignore");
336
+ }
337
+ if (npmrcPlan.changed) {
338
+ files.push(PNPM_NPMRC_PATH);
339
+ }
340
+ return [...new Set(files)].sort((left, right) => left.localeCompare(right));
341
+ }
342
+ function countCreatedFiles(templateReport, lintReport, gitignorePlan, npmrcPlan) {
343
+ return (templateReport.created.length +
344
+ (lintReport?.created.length ?? 0) +
345
+ (gitignorePlan.created ? 1 : 0) +
346
+ (npmrcPlan.created ? 1 : 0));
347
+ }
348
+ function countMergedFiles(templateReport, lintReport, manifestPlan, gitignorePlan, npmrcPlan) {
349
+ return (templateReport.merged.length +
350
+ (lintReport?.merged.length ?? 0) +
351
+ (manifestPlan.changed ? 1 : 0) +
352
+ (gitignorePlan.changed && !gitignorePlan.created ? 1 : 0) +
353
+ (npmrcPlan.changed && !npmrcPlan.created ? 1 : 0));
354
+ }
355
+ async function runInitCommand(input, runtimeOverrides) {
356
+ const detectPackageManagerFn = runtimeOverrides?.detectPackageManagerFn ?? detect_1.detectPackageManager;
357
+ const resolveLatestVersionsFn = runtimeOverrides?.resolveLatestVersionsFn ?? latest_1.resolveLatestVersions;
358
+ const createLoggerFn = runtimeOverrides?.createLoggerFn ?? logger_1.createLogger;
359
+ const promptSelectFn = runtimeOverrides?.promptSelectFn ?? prompt_1.promptSelect;
360
+ const promptConfirmFn = runtimeOverrides?.promptConfirmFn ?? prompt_1.promptConfirm;
361
+ const runtime = { yes: input.yes };
362
+ const cwd = path.resolve(input.cwd);
363
+ const projectRoot = await (0, findRoot_1.findRoot)(cwd);
364
+ if (!projectRoot) {
365
+ throw (0, errors_1.projectNotFoundError)(cwd);
366
+ }
367
+ const template = await selectTemplate(input.template);
368
+ const lintEnabled = await selectLintEnabled(runtime, input.lint, promptConfirmFn);
369
+ const resolvedPm = await detectPackageManagerFn(projectRoot, input.pm, {
370
+ runtime,
371
+ promptSelectFn,
372
+ });
373
+ let packageManagerSourceLabel;
374
+ switch (resolvedPm.source) {
375
+ case "override":
376
+ packageManagerSourceLabel = "explicit --pm";
377
+ break;
378
+ case "lockfile":
379
+ packageManagerSourceLabel = "lockfile";
380
+ break;
381
+ case "installed":
382
+ packageManagerSourceLabel = "only installed package manager";
383
+ break;
384
+ case "prompt":
385
+ packageManagerSourceLabel = "interactive selection";
386
+ break;
387
+ }
388
+ const logger = createLoggerFn({
389
+ verbose: false,
390
+ yes: input.yes,
391
+ });
392
+ logger.section("Inspecting");
393
+ logger.kv("Project", projectRoot);
394
+ logger.kv("Template", template);
395
+ logger.kv("Resolved package manager", resolvedPm.name);
396
+ logger.kv("Package manager source", packageManagerSourceLabel);
397
+ logger.kv("Lint/format", lintEnabled ? "enabled" : "disabled");
398
+ const currentManifest = await (0, readPackageJson_1.readPackageJson)(projectRoot);
399
+ const packagesToResolve = [
400
+ ...Object.values(CORE_VERSION_PACKAGES),
401
+ ...(lintEnabled ? Object.values(LINT_VERSION_PACKAGES) : []),
402
+ ];
403
+ const versions = await resolveLatestVersionsFn(packagesToResolve);
404
+ const replacements = buildVersionReplacements(inferProjectName(projectRoot, currentManifest), versions);
405
+ const templateDir = path.resolve(__dirname, "../../templates/init");
406
+ const lintTemplateDir = path.resolve(__dirname, "../../templates/init-lint");
407
+ const templateManifest = await readTemplateJson(templateDir, "package.json", replacements);
408
+ const lintManifest = lintEnabled
409
+ ? await readTemplateJson(lintTemplateDir, "package.json", replacements)
410
+ : undefined;
411
+ const templateReport = await (0, copy_1.copyTemplateSafe)(templateDir, projectRoot, {
412
+ dryRun: true,
413
+ logger,
414
+ replacements,
415
+ shouldIncludeFile: (relativePath) => relativePath !== "package.json",
416
+ });
417
+ const lintReport = lintEnabled
418
+ ? await (0, copy_1.copyTemplateSafe)(lintTemplateDir, projectRoot, {
419
+ dryRun: true,
420
+ logger,
421
+ replacements,
422
+ shouldIncludeFile: (relativePath) => relativePath !== "package.json",
423
+ })
424
+ : undefined;
425
+ const manifestPlan = planManifestChanges(currentManifest, lintManifest ? [templateManifest, lintManifest] : [templateManifest]);
426
+ const gitignorePlan = await planGitignore(projectRoot);
427
+ const npmrcPlan = await planPnpmNpmrc(projectRoot, resolvedPm.name);
428
+ const changedFiles = collectChangedFiles(templateReport, lintReport, manifestPlan, gitignorePlan, npmrcPlan);
429
+ const addedPackages = [...new Set([...manifestPlan.addedDependencies, ...manifestPlan.addedDevDependencies])].sort((left, right) => left.localeCompare(right));
430
+ const createdCount = countCreatedFiles(templateReport, lintReport, gitignorePlan, npmrcPlan);
431
+ const mergedCount = countMergedFiles(templateReport, lintReport, manifestPlan, gitignorePlan, npmrcPlan);
432
+ const localLattice = (0, output_1.resolveLocalLatticeCommand)(resolvedPm.name);
433
+ const installRequired = manifestPlan.changed || npmrcPlan.changed;
434
+ logger.section("Planning");
435
+ logger.kv("Files to create", String(createdCount));
436
+ logger.kv("Files to merge", String(mergedCount));
437
+ logger.kv("Scripts to add", String(manifestPlan.addedScripts.length));
438
+ logger.kv("Packages to add", String(addedPackages.length));
439
+ const changedFileSummary = (0, output_1.summarizeItems)(changedFiles);
440
+ if (changedFileSummary.total > 0) {
441
+ logger.list(changedFileSummary.visible);
442
+ if (changedFileSummary.hidden > 0) {
443
+ logger.step(`...and ${changedFileSummary.hidden} more`);
444
+ }
445
+ }
446
+ if (manifestPlan.addedScripts.length > 0) {
447
+ const scriptSummary = (0, output_1.summarizeItems)(manifestPlan.addedScripts);
448
+ logger.kv("New scripts", String(scriptSummary.total));
449
+ logger.list(scriptSummary.visible);
450
+ if (scriptSummary.hidden > 0) {
451
+ logger.step(`...and ${scriptSummary.hidden} more`);
452
+ }
453
+ }
454
+ if (addedPackages.length > 0) {
455
+ const packageSummary = (0, output_1.summarizeItems)(addedPackages);
456
+ logger.kv("New dependencies", String(packageSummary.total));
457
+ logger.list(packageSummary.visible);
458
+ if (packageSummary.hidden > 0) {
459
+ logger.step(`...and ${packageSummary.hidden} more`);
460
+ }
461
+ }
462
+ if (input.dryRun) {
463
+ logger.section("Dry Run");
464
+ if (changedFiles.length === 0) {
465
+ logger.step("[dry-run] No file changes required.");
466
+ }
467
+ else {
468
+ for (const filePath of changedFileSummary.visible) {
469
+ logger.step(`[dry-run] update ${filePath}`);
470
+ }
471
+ if (changedFileSummary.hidden > 0) {
472
+ logger.step(`[dry-run] ...and ${changedFileSummary.hidden} more file changes`);
473
+ }
474
+ }
475
+ if (installRequired) {
476
+ logger.step(`[dry-run] ${resolvedPm.name} install`);
477
+ }
478
+ else {
479
+ logger.step("[dry-run] No install required.");
480
+ }
481
+ }
482
+ else {
483
+ logger.section("Applying");
484
+ if (changedFiles.length === 0) {
485
+ logger.step("Project already has the required Lattice bootstrap files.");
486
+ }
487
+ else {
488
+ const confirmed = await logger.confirm(`Apply ${changedFiles.length} planned change(s) in ${projectRoot}?`);
489
+ if (!confirmed) {
490
+ logger.section("Result");
491
+ logger.warn("Init command cancelled.");
492
+ logger.section("Next Steps");
493
+ logger.step(`${localLattice} doctor`);
494
+ return;
495
+ }
496
+ await (0, copy_1.copyTemplateSafe)(templateDir, projectRoot, {
497
+ dryRun: false,
498
+ logger,
499
+ replacements,
500
+ shouldIncludeFile: (relativePath) => relativePath !== "package.json",
501
+ });
502
+ if (lintEnabled) {
503
+ await (0, copy_1.copyTemplateSafe)(lintTemplateDir, projectRoot, {
504
+ dryRun: false,
505
+ logger,
506
+ replacements,
507
+ shouldIncludeFile: (relativePath) => relativePath !== "package.json",
508
+ });
509
+ }
510
+ if (manifestPlan.changed) {
511
+ await (0, writePackageJson_1.writePackageJson)(projectRoot, manifestPlan.nextManifest);
512
+ }
513
+ await ensureProjectDirectories(projectRoot);
514
+ if (gitignorePlan.changed) {
515
+ await node_fs_1.promises.writeFile(path.join(projectRoot, ".gitignore"), gitignorePlan.nextContent, "utf8");
516
+ }
517
+ if (npmrcPlan.changed) {
518
+ await node_fs_1.promises.writeFile(path.join(projectRoot, PNPM_NPMRC_PATH), npmrcPlan.nextContent, "utf8");
519
+ }
520
+ if (installRequired) {
521
+ const installSpinner = logger.spinner(`Installing dependencies with ${resolvedPm.name}...`);
522
+ await resolvedPm.manager.install(projectRoot);
523
+ installSpinner.succeed("Dependencies installed.");
524
+ }
525
+ else {
526
+ logger.step("No dependency installation required.");
527
+ }
528
+ }
529
+ }
530
+ logger.section("Result");
531
+ if (changedFiles.length === 0) {
532
+ logger.success("Project already matches the Lattice init template.");
533
+ }
534
+ else if (input.dryRun) {
535
+ logger.success(`Dry run complete. Planned ${changedFiles.length} file change(s).`);
536
+ }
537
+ else {
538
+ logger.success(`Initialized Lattice in ${path.basename(projectRoot)}.`);
539
+ }
540
+ logger.kv("Files created", String(createdCount));
541
+ logger.kv("Files merged", String(mergedCount));
542
+ logger.kv("Dependencies added", String(addedPackages.length));
543
+ logger.section("Next Steps");
544
+ logger.step(`${localLattice} doctor`);
545
+ logger.step(`${resolvedPm.name} run build`);
546
+ logger.step(`${localLattice} add --preset form`);
547
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliContext } from "../ctx";
2
+ import { type SelectionInput } from "./selection";
3
+ export declare function runRemoveCommand(ctx: CliContext, input: SelectionInput): Promise<void>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRemoveCommand = runRemoveCommand;
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 selection_1 = require("./selection");
10
+ function normalizeList(values) {
11
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
12
+ }
13
+ async function runRemoveCommand(ctx, input) {
14
+ const localLattice = (0, output_1.resolveLocalLatticeCommand)(ctx.pmName);
15
+ ctx.logger.section("Selecting");
16
+ ctx.logger.kv("Project", ctx.projectRoot);
17
+ const packageJson = await (0, readPackageJson_1.readPackageJson)(ctx.projectRoot);
18
+ const installedDependencies = (0, patch_1.getDependencyNames)(packageJson);
19
+ let components;
20
+ if (input.names.length > 0 || input.presets.length > 0) {
21
+ components = (0, selection_1.resolveComponentSelection)(ctx, input);
22
+ }
23
+ else {
24
+ if (ctx.options.yes) {
25
+ throw (0, errors_1.usageError)("No components selected. Provide component names or --preset when using --yes.");
26
+ }
27
+ const installedComponents = Object.keys(ctx.registry.packages)
28
+ .filter((componentName) => installedDependencies.has(ctx.registry.packages[componentName].npm))
29
+ .sort((left, right) => left.localeCompare(right));
30
+ if (installedComponents.length === 0) {
31
+ ctx.logger.section("Result");
32
+ ctx.logger.warn("No installed registry components found to remove.");
33
+ ctx.logger.section("Next Steps");
34
+ ctx.logger.step(`${localLattice} doctor`);
35
+ return;
36
+ }
37
+ components = await (0, prompt_1.promptMultiSelect)({ yes: ctx.options.yes }, "Select installed components to remove", installedComponents.map((componentName) => ({
38
+ label: componentName,
39
+ value: componentName,
40
+ })), {
41
+ allowEmpty: false,
42
+ });
43
+ }
44
+ const selectedSummary = (0, output_1.summarizeItems)(components);
45
+ const specs = normalizeList(components.map((component) => ctx.registry.packages[component].npm));
46
+ const plannedSpecs = specs.filter((spec) => installedDependencies.has(spec));
47
+ const missingComponents = normalizeList(components.filter((component) => !installedDependencies.has(ctx.registry.packages[component].npm)));
48
+ const removedComponents = normalizeList(components.filter((component) => installedDependencies.has(ctx.registry.packages[component].npm)));
49
+ ctx.logger.section("Planning");
50
+ ctx.logger.kv("Selected components", String(selectedSummary.total));
51
+ if (selectedSummary.total > 0) {
52
+ ctx.logger.list(selectedSummary.visible);
53
+ if (selectedSummary.hidden > 0) {
54
+ ctx.logger.step(`...and ${selectedSummary.hidden} more`);
55
+ }
56
+ }
57
+ const plannedSummary = (0, output_1.summarizeItems)(plannedSpecs);
58
+ ctx.logger.kv("Packages to remove", String(plannedSummary.total));
59
+ if (plannedSummary.total > 0) {
60
+ ctx.logger.list(plannedSummary.visible);
61
+ if (plannedSummary.hidden > 0) {
62
+ ctx.logger.step(`...and ${plannedSummary.hidden} more`);
63
+ }
64
+ }
65
+ if (missingComponents.length > 0) {
66
+ const missingSummary = (0, output_1.summarizeItems)(missingComponents);
67
+ ctx.logger.kv("Missing selected", String(missingSummary.total));
68
+ ctx.logger.list(missingSummary.visible);
69
+ if (missingSummary.hidden > 0) {
70
+ ctx.logger.step(`...and ${missingSummary.hidden} more`);
71
+ }
72
+ }
73
+ if (ctx.options.dryRun) {
74
+ ctx.logger.section("Dry Run");
75
+ if (plannedSpecs.length > 0) {
76
+ ctx.logger.step(`[dry-run] ${ctx.pmName} remove ${plannedSpecs.join(" ")}`);
77
+ }
78
+ else {
79
+ ctx.logger.step("[dry-run] No remove actions required.");
80
+ }
81
+ ctx.logger.step("No files were changed.");
82
+ }
83
+ else {
84
+ ctx.logger.section("Applying");
85
+ if (plannedSpecs.length > 0) {
86
+ ctx.logger.step(`${ctx.pmName} remove ${plannedSpecs.join(" ")}`);
87
+ const confirmed = await ctx.logger.confirm(`Remove ${plannedSpecs.length} package(s) in ${ctx.projectRoot}?`);
88
+ if (!confirmed) {
89
+ ctx.logger.section("Result");
90
+ ctx.logger.warn("Remove command cancelled.");
91
+ ctx.logger.section("Next Steps");
92
+ ctx.logger.step(`${localLattice} doctor`);
93
+ return;
94
+ }
95
+ const spinner = ctx.logger.spinner(`Removing ${plannedSpecs.length} package(s)...`);
96
+ await ctx.pm.remove(plannedSpecs, ctx.projectRoot);
97
+ spinner.succeed("Dependencies removed.");
98
+ }
99
+ else {
100
+ ctx.logger.step("No removal required.");
101
+ }
102
+ }
103
+ ctx.logger.section("Result");
104
+ if (plannedSpecs.length === 0) {
105
+ ctx.logger.warn("No installed package matched remove selection.");
106
+ }
107
+ else {
108
+ ctx.logger.success(`Removed components: ${removedComponents.join(", ")}`);
109
+ }
110
+ ctx.logger.kv("Removed packages", String(plannedSpecs.length));
111
+ ctx.logger.section("Next Steps");
112
+ ctx.logger.step(`${localLattice} doctor`);
113
+ }
@@ -0,0 +1,6 @@
1
+ import type { CliContext } from "../ctx";
2
+ export interface SelectionInput {
3
+ names: string[];
4
+ presets: string[];
5
+ }
6
+ export declare function resolveComponentSelection(ctx: CliContext, input: SelectionInput): string[];