@impulselab/cli 0.1.3 → 0.1.4
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/index.js +110 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -211,7 +211,11 @@ import { z as z5 } from "zod";
|
|
|
211
211
|
import { z as z2 } from "zod";
|
|
212
212
|
var ModuleFileSchema = z2.object({
|
|
213
213
|
src: z2.string(),
|
|
214
|
-
dest: z2.string()
|
|
214
|
+
dest: z2.string(),
|
|
215
|
+
/** If set, this file is only installed when the named module is already installed. */
|
|
216
|
+
when: z2.object({
|
|
217
|
+
moduleInstalled: z2.string()
|
|
218
|
+
}).optional()
|
|
215
219
|
});
|
|
216
220
|
|
|
217
221
|
// src/schemas/module-dependency.ts
|
|
@@ -243,11 +247,21 @@ var ModuleManifestSchema = z5.object({
|
|
|
243
247
|
subModules: z5.array(z5.string()).default([]),
|
|
244
248
|
dependencies: z5.array(ModuleDependencySchema).default([]),
|
|
245
249
|
moduleDependencies: z5.array(z5.string()).default([]),
|
|
250
|
+
/** Optional module dependencies that enhance this module but are not required. */
|
|
251
|
+
optionalModuleDependencies: z5.array(z5.string()).default([]),
|
|
246
252
|
files: z5.array(ModuleFileSchema).default([]),
|
|
247
253
|
transforms: z5.array(ModuleTransformSchema).default([]),
|
|
248
254
|
/** Documentation metadata listing env vars this module requires (displayed in install summary). */
|
|
249
255
|
envVars: z5.array(z5.string()).default([]),
|
|
250
|
-
postInstall: z5.array(z5.string()).optional()
|
|
256
|
+
postInstall: z5.array(z5.string()).optional(),
|
|
257
|
+
/** Logical category for grouping in `impulse list`. */
|
|
258
|
+
category: z5.enum(["core", "feature", "integration", "dx"]).optional(),
|
|
259
|
+
/** Module names that cannot be installed alongside this module. */
|
|
260
|
+
incompatibleWith: z5.array(z5.string()).default([]),
|
|
261
|
+
/** Capability tokens this module provides (for future dependency resolution). */
|
|
262
|
+
provides: z5.array(z5.string()).default([]),
|
|
263
|
+
/** Which package in a monorepo this module targets. */
|
|
264
|
+
targetPackage: z5.enum(["database", "server", "web", "root"]).default("root")
|
|
251
265
|
});
|
|
252
266
|
|
|
253
267
|
// src/registry/github-urls.ts
|
|
@@ -364,7 +378,8 @@ async function listAvailableModules(localPath) {
|
|
|
364
378
|
result.push({
|
|
365
379
|
name: parsed.data.name,
|
|
366
380
|
description: parsed.data.description,
|
|
367
|
-
...subModules ? { subModules } : {}
|
|
381
|
+
...subModules ? { subModules } : {},
|
|
382
|
+
...parsed.data.category ? { category: parsed.data.category } : {}
|
|
368
383
|
});
|
|
369
384
|
} catch {
|
|
370
385
|
}
|
|
@@ -432,9 +447,17 @@ import path5 from "path";
|
|
|
432
447
|
import * as p2 from "@clack/prompts";
|
|
433
448
|
var { outputFile, pathExists: pathExists6 } = fsExtra7;
|
|
434
449
|
async function installFiles(options) {
|
|
435
|
-
const { moduleName, files, cwd, dryRun, localPath } = options;
|
|
450
|
+
const { moduleName, files, cwd, dryRun, localPath, installedModules = /* @__PURE__ */ new Set() } = options;
|
|
436
451
|
const results = [];
|
|
437
452
|
for (const file of files) {
|
|
453
|
+
if (file.when?.moduleInstalled !== void 0 && !installedModules.has(file.when.moduleInstalled)) {
|
|
454
|
+
results.push({
|
|
455
|
+
dest: file.dest,
|
|
456
|
+
action: "conditional-skip",
|
|
457
|
+
reason: `requires ${file.when.moduleInstalled}`
|
|
458
|
+
});
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
438
461
|
const destAbs = path5.join(cwd, file.dest);
|
|
439
462
|
const exists = await pathExists6(destAbs);
|
|
440
463
|
if (exists) {
|
|
@@ -737,17 +760,26 @@ function installNpmDeps(deps, cwd, dryRun) {
|
|
|
737
760
|
p5.log.step(`Installing dependencies: ${deps.join(", ")}`);
|
|
738
761
|
execFileSync(pm, args, { cwd, stdio: "inherit" });
|
|
739
762
|
}
|
|
740
|
-
async function installModule(moduleId, manifest, cwd, dryRun, localPath) {
|
|
763
|
+
async function installModule(moduleId, manifest, cwd, dryRun, installedModules, localPath) {
|
|
741
764
|
p5.log.step(`Installing ${moduleId}@${manifest.version}...`);
|
|
765
|
+
const installedDests = [];
|
|
742
766
|
if (manifest.files.length > 0) {
|
|
743
767
|
const installed = await installFiles({
|
|
744
768
|
moduleName: moduleId,
|
|
745
769
|
files: manifest.files,
|
|
746
770
|
cwd,
|
|
747
771
|
dryRun,
|
|
748
|
-
localPath
|
|
772
|
+
localPath,
|
|
773
|
+
installedModules
|
|
749
774
|
});
|
|
750
775
|
for (const f of installed) {
|
|
776
|
+
if (f.action === "conditional-skip") {
|
|
777
|
+
if (dryRun) {
|
|
778
|
+
p5.log.message(` \u25CB ${f.dest} [skipped: ${f.reason}]`);
|
|
779
|
+
}
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
installedDests.push(f.dest);
|
|
751
783
|
const icon = f.action === "created" || f.action === "would-create" ? "+" : f.action === "overwritten" || f.action === "would-overwrite" ? "~" : "=";
|
|
752
784
|
p5.log.message(` ${icon} ${f.dest}`);
|
|
753
785
|
}
|
|
@@ -756,14 +788,15 @@ async function installModule(moduleId, manifest, cwd, dryRun, localPath) {
|
|
|
756
788
|
p5.log.step(` transform: ${transform.type} \u2192 ${transform.target}`);
|
|
757
789
|
await runTransform(transform, cwd, dryRun);
|
|
758
790
|
}
|
|
791
|
+
return installedDests;
|
|
759
792
|
}
|
|
760
|
-
function recordModule(config, moduleId, manifest, now) {
|
|
793
|
+
function recordModule(config, moduleId, manifest, installedFiles, now) {
|
|
761
794
|
const existing = config.installedModules.findIndex((m) => m.name === moduleId);
|
|
762
795
|
const record = {
|
|
763
796
|
name: moduleId,
|
|
764
797
|
version: manifest.version,
|
|
765
798
|
installedAt: now,
|
|
766
|
-
files:
|
|
799
|
+
files: installedFiles
|
|
767
800
|
};
|
|
768
801
|
if (existing >= 0) {
|
|
769
802
|
config.installedModules[existing] = record;
|
|
@@ -907,6 +940,19 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
907
940
|
for (const id of orderedModules) {
|
|
908
941
|
manifests.set(id, await fetchModuleManifest(id, localPath));
|
|
909
942
|
}
|
|
943
|
+
for (const [id, manifest] of manifests) {
|
|
944
|
+
if (installedNames.has(id)) continue;
|
|
945
|
+
for (const incompatible of manifest.incompatibleWith) {
|
|
946
|
+
if (installedNames.has(incompatible)) {
|
|
947
|
+
p5.cancel(`Module "${id}" is incompatible with installed module "${incompatible}".`);
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
if (manifests.has(incompatible) && !installedNames.has(incompatible)) {
|
|
951
|
+
p5.cancel(`Module "${id}" is incompatible with module "${incompatible}" (also being installed).`);
|
|
952
|
+
process.exit(1);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
910
956
|
const allDeps = /* @__PURE__ */ new Set();
|
|
911
957
|
for (const manifest of manifests.values()) {
|
|
912
958
|
for (const dep of manifest.dependencies) {
|
|
@@ -951,13 +997,16 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
951
997
|
const primaryTargetSet = new Set(allTargets);
|
|
952
998
|
const depModules = orderedModules.filter((id) => !primaryTargetSet.has(id) && !installedNames.has(id));
|
|
953
999
|
const targetModules = orderedModules.filter((id) => primaryTargetSet.has(id));
|
|
1000
|
+
const installedFilesMap = /* @__PURE__ */ new Map();
|
|
954
1001
|
const depPostInstallHooks = [];
|
|
955
1002
|
if (depModules.length > 0) {
|
|
956
1003
|
p5.log.step(`Installing module dependencies: ${depModules.join(", ")}`);
|
|
957
1004
|
for (const dep of depModules) {
|
|
958
1005
|
const depManifest = manifests.get(dep);
|
|
959
1006
|
if (!depManifest) continue;
|
|
960
|
-
await installModule(dep, depManifest, cwd, dryRun, localPath);
|
|
1007
|
+
const dests = await installModule(dep, depManifest, cwd, dryRun, installedNames, localPath);
|
|
1008
|
+
installedFilesMap.set(dep, dests);
|
|
1009
|
+
installedNames.add(dep);
|
|
961
1010
|
if (depManifest.postInstall && depManifest.postInstall.length > 0) {
|
|
962
1011
|
depPostInstallHooks.push({ name: dep, hooks: depManifest.postInstall });
|
|
963
1012
|
}
|
|
@@ -966,7 +1015,9 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
966
1015
|
for (const targetId of targetModules) {
|
|
967
1016
|
const targetManifest = manifests.get(targetId);
|
|
968
1017
|
if (!targetManifest) continue;
|
|
969
|
-
await installModule(targetId, targetManifest, cwd, dryRun, localPath);
|
|
1018
|
+
const dests = await installModule(targetId, targetManifest, cwd, dryRun, installedNames, localPath);
|
|
1019
|
+
installedFilesMap.set(targetId, dests);
|
|
1020
|
+
installedNames.add(targetId);
|
|
970
1021
|
}
|
|
971
1022
|
installNpmDeps([...allDeps], cwd, dryRun);
|
|
972
1023
|
if (!dryRun) {
|
|
@@ -993,11 +1044,13 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
993
1044
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
994
1045
|
for (const dep of depModules) {
|
|
995
1046
|
const depManifest = manifests.get(dep);
|
|
996
|
-
|
|
1047
|
+
const dests = installedFilesMap.get(dep) ?? [];
|
|
1048
|
+
if (depManifest) recordModule(config, dep, depManifest, dests, now);
|
|
997
1049
|
}
|
|
998
1050
|
for (const targetId of targetModules) {
|
|
999
1051
|
const targetManifest = manifests.get(targetId);
|
|
1000
|
-
|
|
1052
|
+
const dests = installedFilesMap.get(targetId) ?? [];
|
|
1053
|
+
if (targetManifest) recordModule(config, targetId, targetManifest, dests, now);
|
|
1001
1054
|
}
|
|
1002
1055
|
await writeConfig(config, cwd);
|
|
1003
1056
|
}
|
|
@@ -1009,6 +1062,24 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
1009
1062
|
|
|
1010
1063
|
// src/commands/list.ts
|
|
1011
1064
|
import * as p6 from "@clack/prompts";
|
|
1065
|
+
function printModule(mod, installedNames, installedModules) {
|
|
1066
|
+
const installed = installedNames.has(mod.name);
|
|
1067
|
+
const installedInfo = installed ? installedModules?.find((m) => m.name === mod.name) : null;
|
|
1068
|
+
const status = installed ? `[installed v${installedInfo?.version ?? "?"}]` : "[available]";
|
|
1069
|
+
const desc = mod.description ? ` \u2014 ${mod.description}` : "";
|
|
1070
|
+
p6.log.message(` ${installed ? "\u2713" : "\u25CB"} ${mod.name} ${status}${desc}`);
|
|
1071
|
+
if (mod.subModules && mod.subModules.length > 0) {
|
|
1072
|
+
const last = mod.subModules.length - 1;
|
|
1073
|
+
mod.subModules.forEach((sub, i) => {
|
|
1074
|
+
const subId = `${mod.name}/${sub}`;
|
|
1075
|
+
const subInstalled = installedNames.has(subId);
|
|
1076
|
+
const subInfo = subInstalled ? installedModules?.find((m) => m.name === subId) : null;
|
|
1077
|
+
const subStatus = subInstalled ? `[installed v${subInfo?.version ?? "?"}]` : "[not installed]";
|
|
1078
|
+
const connector = i === last ? "\u2514\u2500" : "\u251C\u2500";
|
|
1079
|
+
p6.log.message(` ${connector} ${sub} ${subStatus}`);
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1012
1083
|
async function runList(options) {
|
|
1013
1084
|
const { cwd, localPath } = options;
|
|
1014
1085
|
p6.intro("impulse list");
|
|
@@ -1034,22 +1105,33 @@ async function runList(options) {
|
|
|
1034
1105
|
return;
|
|
1035
1106
|
}
|
|
1036
1107
|
p6.log.message("\nAvailable modules:\n");
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1108
|
+
const hasCategories = available.some((m) => m.category !== void 0);
|
|
1109
|
+
if (hasCategories) {
|
|
1110
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1111
|
+
for (const mod of available) {
|
|
1112
|
+
const key = mod.category ?? "other";
|
|
1113
|
+
const existing = groups.get(key);
|
|
1114
|
+
if (existing) {
|
|
1115
|
+
existing.push(mod);
|
|
1116
|
+
} else {
|
|
1117
|
+
groups.set(key, [mod]);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
const categoryOrder = ["core", "feature", "integration", "dx", "other"];
|
|
1121
|
+
const sortedKeys = [
|
|
1122
|
+
...categoryOrder.filter((k) => groups.has(k)),
|
|
1123
|
+
...[...groups.keys()].filter((k) => !categoryOrder.includes(k))
|
|
1124
|
+
];
|
|
1125
|
+
for (const key of sortedKeys) {
|
|
1126
|
+
p6.log.message(`${key.toUpperCase()}`);
|
|
1127
|
+
for (const mod of groups.get(key)) {
|
|
1128
|
+
printModule(mod, installedNames, config?.installedModules);
|
|
1129
|
+
}
|
|
1130
|
+
p6.log.message("");
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
for (const mod of available) {
|
|
1134
|
+
printModule(mod, installedNames, config?.installedModules);
|
|
1053
1135
|
}
|
|
1054
1136
|
}
|
|
1055
1137
|
p6.log.message(
|