@impulselab/cli 0.1.3 → 0.1.5
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 +285 -109
- 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
|
|
@@ -242,24 +246,40 @@ var ModuleManifestSchema = z5.object({
|
|
|
242
246
|
/** Names of sub-modules available under this parent module (e.g. ["quote-to-cash", "gocardless"]). */
|
|
243
247
|
subModules: z5.array(z5.string()).default([]),
|
|
244
248
|
dependencies: z5.array(ModuleDependencySchema).default([]),
|
|
245
|
-
moduleDependencies: z5.array(
|
|
249
|
+
moduleDependencies: z5.array(ModuleDependencySchema).default([]),
|
|
250
|
+
/** Optional module dependencies that enhance this module but are not required. */
|
|
251
|
+
optionalModuleDependencies: z5.array(ModuleDependencySchema).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
|
|
|
267
|
+
// src/registry/constants.ts
|
|
268
|
+
var registryConstants = {
|
|
269
|
+
githubOrg: "impulse-studio",
|
|
270
|
+
githubRepo: "impulse-modules",
|
|
271
|
+
githubBranch: "main",
|
|
272
|
+
modulesDir: "modules",
|
|
273
|
+
subModulesDirName: "sub-modules",
|
|
274
|
+
manifestFileName: "module.json"
|
|
275
|
+
};
|
|
276
|
+
|
|
253
277
|
// src/registry/github-urls.ts
|
|
254
278
|
import { execSync } from "child_process";
|
|
255
|
-
var GITHUB_ORG = "impulse-studio";
|
|
256
|
-
var GITHUB_REPO = "impulse-modules";
|
|
257
|
-
var GITHUB_BRANCH = "main";
|
|
258
|
-
var MODULES_DIR = "modules";
|
|
259
279
|
var githubUrls = {
|
|
260
|
-
rawFile: (registryPath, file) => `https://raw.githubusercontent.com/${
|
|
261
|
-
moduleList: () => `https://api.github.com/repos/${
|
|
262
|
-
subModulesList: (parentModule) => `https://api.github.com/repos/${
|
|
280
|
+
rawFile: (registryPath, file) => `https://raw.githubusercontent.com/${registryConstants.githubOrg}/${registryConstants.githubRepo}/${registryConstants.githubBranch}/${registryConstants.modulesDir}/${registryPath}/${file}`,
|
|
281
|
+
moduleList: () => `https://api.github.com/repos/${registryConstants.githubOrg}/${registryConstants.githubRepo}/contents/${registryConstants.modulesDir}`,
|
|
282
|
+
subModulesList: (parentModule) => `https://api.github.com/repos/${registryConstants.githubOrg}/${registryConstants.githubRepo}/contents/${registryConstants.modulesDir}/${parentModule}/${registryConstants.subModulesDirName}`
|
|
263
283
|
};
|
|
264
284
|
var _cachedToken;
|
|
265
285
|
function getGitHubToken() {
|
|
@@ -301,7 +321,7 @@ function parseModuleId(moduleId) {
|
|
|
301
321
|
function moduleRegistryPath(moduleId) {
|
|
302
322
|
const { parent, child } = parseModuleId(moduleId);
|
|
303
323
|
if (child === null) return parent;
|
|
304
|
-
return `${parent}
|
|
324
|
+
return `${parent}/${registryConstants.subModulesDirName}/${child}`;
|
|
305
325
|
}
|
|
306
326
|
|
|
307
327
|
// src/registry/fetch-module-manifest.ts
|
|
@@ -309,7 +329,7 @@ var { readJson: readJson3, pathExists: pathExists4 } = fsExtra5;
|
|
|
309
329
|
async function fetchModuleManifest(moduleId, localPath) {
|
|
310
330
|
const registryPath = moduleRegistryPath(moduleId);
|
|
311
331
|
if (localPath) {
|
|
312
|
-
const file = path3.join(localPath, registryPath,
|
|
332
|
+
const file = path3.join(localPath, registryPath, registryConstants.manifestFileName);
|
|
313
333
|
if (!await pathExists4(file)) {
|
|
314
334
|
throw new Error(`Local module not found: ${file}`);
|
|
315
335
|
}
|
|
@@ -320,7 +340,7 @@ async function fetchModuleManifest(moduleId, localPath) {
|
|
|
320
340
|
}
|
|
321
341
|
return parsed2.data;
|
|
322
342
|
}
|
|
323
|
-
const url = githubUrls.rawFile(registryPath,
|
|
343
|
+
const url = githubUrls.rawFile(registryPath, registryConstants.manifestFileName);
|
|
324
344
|
const res = await fetch(url, { headers: getGitHubHeaders() });
|
|
325
345
|
if (!res.ok) {
|
|
326
346
|
if (res.status === 404) {
|
|
@@ -348,14 +368,22 @@ async function listAvailableModules(localPath) {
|
|
|
348
368
|
const result = [];
|
|
349
369
|
for (const entry of entries2) {
|
|
350
370
|
if (!entry.isDirectory()) continue;
|
|
351
|
-
const manifestFile = path4.join(
|
|
371
|
+
const manifestFile = path4.join(
|
|
372
|
+
localPath,
|
|
373
|
+
entry.name,
|
|
374
|
+
registryConstants.manifestFileName
|
|
375
|
+
);
|
|
352
376
|
if (!await pathExists5(manifestFile)) continue;
|
|
353
377
|
try {
|
|
354
378
|
const raw = await readJson4(manifestFile);
|
|
355
379
|
const parsed = ModuleManifestSchema.safeParse(raw);
|
|
356
380
|
if (!parsed.success) continue;
|
|
357
381
|
let subModules;
|
|
358
|
-
const subModulesDir = path4.join(
|
|
382
|
+
const subModulesDir = path4.join(
|
|
383
|
+
localPath,
|
|
384
|
+
entry.name,
|
|
385
|
+
registryConstants.subModulesDirName
|
|
386
|
+
);
|
|
359
387
|
if (await pathExists5(subModulesDir)) {
|
|
360
388
|
const subEntries = await readdir(subModulesDir, { withFileTypes: true });
|
|
361
389
|
subModules = subEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -364,7 +392,8 @@ async function listAvailableModules(localPath) {
|
|
|
364
392
|
result.push({
|
|
365
393
|
name: parsed.data.name,
|
|
366
394
|
description: parsed.data.description,
|
|
367
|
-
...subModules ? { subModules } : {}
|
|
395
|
+
...subModules ? { subModules } : {},
|
|
396
|
+
...parsed.data.category ? { category: parsed.data.category } : {}
|
|
368
397
|
});
|
|
369
398
|
} catch {
|
|
370
399
|
}
|
|
@@ -413,10 +442,10 @@ async function listAvailableModules(localPath) {
|
|
|
413
442
|
// src/registry/fetch-module-file.ts
|
|
414
443
|
async function fetchModuleFile(moduleName, fileSrc, localPath) {
|
|
415
444
|
if (localPath) {
|
|
416
|
-
const { readFile:
|
|
445
|
+
const { readFile: readFile7 } = await import("fs/promises");
|
|
417
446
|
const { join } = await import("path");
|
|
418
447
|
const file = join(localPath, moduleName, fileSrc);
|
|
419
|
-
return
|
|
448
|
+
return readFile7(file, "utf-8");
|
|
420
449
|
}
|
|
421
450
|
const url = githubUrls.rawFile(moduleName, fileSrc);
|
|
422
451
|
const res = await fetch(url, { headers: getGitHubHeaders() });
|
|
@@ -428,28 +457,35 @@ async function fetchModuleFile(moduleName, fileSrc, localPath) {
|
|
|
428
457
|
|
|
429
458
|
// src/installer.ts
|
|
430
459
|
import fsExtra7 from "fs-extra";
|
|
460
|
+
import { readFile } from "fs/promises";
|
|
431
461
|
import path5 from "path";
|
|
432
462
|
import * as p2 from "@clack/prompts";
|
|
433
463
|
var { outputFile, pathExists: pathExists6 } = fsExtra7;
|
|
434
464
|
async function installFiles(options) {
|
|
435
|
-
const { moduleName, files, cwd, dryRun, localPath } = options;
|
|
465
|
+
const { moduleName, files, cwd, dryRun, localPath, installedModules = /* @__PURE__ */ new Set() } = options;
|
|
436
466
|
const results = [];
|
|
437
467
|
for (const file of files) {
|
|
468
|
+
if (file.when?.moduleInstalled !== void 0 && !installedModules.has(file.when.moduleInstalled)) {
|
|
469
|
+
results.push({
|
|
470
|
+
dest: file.dest,
|
|
471
|
+
action: "conditional-skip",
|
|
472
|
+
reason: `requires ${file.when.moduleInstalled}`
|
|
473
|
+
});
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
438
476
|
const destAbs = path5.join(cwd, file.dest);
|
|
439
477
|
const exists = await pathExists6(destAbs);
|
|
440
478
|
if (exists) {
|
|
479
|
+
if (dryRun) {
|
|
480
|
+
results.push({ dest: file.dest, action: "would-overwrite" });
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
441
483
|
const content = await fetchModuleFile(moduleName, file.src, localPath);
|
|
442
|
-
const existing = await
|
|
443
|
-
(fs) => fs.readFile(destAbs, "utf-8")
|
|
444
|
-
);
|
|
484
|
+
const existing = await readFile(destAbs, "utf-8");
|
|
445
485
|
if (existing === content) {
|
|
446
486
|
results.push({ dest: file.dest, action: "skipped" });
|
|
447
487
|
continue;
|
|
448
488
|
}
|
|
449
|
-
if (dryRun) {
|
|
450
|
-
results.push({ dest: file.dest, action: "would-overwrite" });
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
489
|
const answer = await p2.confirm({
|
|
454
490
|
message: `File already exists: ${file.dest} \u2014 overwrite?`,
|
|
455
491
|
initialValue: false
|
|
@@ -476,7 +512,7 @@ async function installFiles(options) {
|
|
|
476
512
|
// src/transforms/append-export.ts
|
|
477
513
|
import fsExtra8 from "fs-extra";
|
|
478
514
|
import path6 from "path";
|
|
479
|
-
var { readFile, outputFile: outputFile2, pathExists: pathExists7 } = fsExtra8;
|
|
515
|
+
var { readFile: readFile2, outputFile: outputFile2, pathExists: pathExists7 } = fsExtra8;
|
|
480
516
|
async function appendExport(target, value, cwd, dryRun) {
|
|
481
517
|
const file = path6.join(cwd, target);
|
|
482
518
|
if (!await pathExists7(file)) {
|
|
@@ -486,7 +522,7 @@ async function appendExport(target, value, cwd, dryRun) {
|
|
|
486
522
|
}
|
|
487
523
|
return;
|
|
488
524
|
}
|
|
489
|
-
const content = await
|
|
525
|
+
const content = await readFile2(file, "utf-8");
|
|
490
526
|
if (content.includes(value)) return;
|
|
491
527
|
if (!dryRun) {
|
|
492
528
|
const newContent = content.endsWith("\n") ? `${content}${value}
|
|
@@ -500,7 +536,7 @@ ${value}
|
|
|
500
536
|
// src/transforms/register-route.ts
|
|
501
537
|
import fsExtra9 from "fs-extra";
|
|
502
538
|
import path7 from "path";
|
|
503
|
-
var { readFile:
|
|
539
|
+
var { readFile: readFile3, outputFile: outputFile3, pathExists: pathExists8 } = fsExtra9;
|
|
504
540
|
async function registerRoute(target, value, cwd, dryRun) {
|
|
505
541
|
const file = path7.join(cwd, target);
|
|
506
542
|
if (!await pathExists8(file)) {
|
|
@@ -508,7 +544,7 @@ async function registerRoute(target, value, cwd, dryRun) {
|
|
|
508
544
|
`register-route: target file not found: ${target}. Please ensure your router file exists.`
|
|
509
545
|
);
|
|
510
546
|
}
|
|
511
|
-
const content = await
|
|
547
|
+
const content = await readFile3(file, "utf-8");
|
|
512
548
|
if (content.includes(value)) return;
|
|
513
549
|
const routerPattern = /^([ \t]*\})[ \t]*(?:satisfies|as)\s/m;
|
|
514
550
|
const match = routerPattern.exec(content);
|
|
@@ -528,7 +564,7 @@ async function registerRoute(target, value, cwd, dryRun) {
|
|
|
528
564
|
// src/transforms/add-nav-item.ts
|
|
529
565
|
import fsExtra10 from "fs-extra";
|
|
530
566
|
import path8 from "path";
|
|
531
|
-
var { readFile:
|
|
567
|
+
var { readFile: readFile4, outputFile: outputFile4, pathExists: pathExists9 } = fsExtra10;
|
|
532
568
|
async function addNavItem(target, value, cwd, dryRun) {
|
|
533
569
|
const file = path8.join(cwd, target);
|
|
534
570
|
if (!await pathExists9(file)) {
|
|
@@ -536,7 +572,7 @@ async function addNavItem(target, value, cwd, dryRun) {
|
|
|
536
572
|
`add-nav-item: target file not found: ${target}`
|
|
537
573
|
);
|
|
538
574
|
}
|
|
539
|
-
const content = await
|
|
575
|
+
const content = await readFile4(file, "utf-8");
|
|
540
576
|
if (content.includes(value)) return;
|
|
541
577
|
const openPattern = /(?:const\s+\w+(?:\s*:\s*[\w<>\[\], ]+)?\s*=\s*\[|items\s*:\s*\[)/;
|
|
542
578
|
const openMatch = openPattern.exec(content);
|
|
@@ -573,7 +609,7 @@ async function addNavItem(target, value, cwd, dryRun) {
|
|
|
573
609
|
// src/transforms/merge-schema.ts
|
|
574
610
|
import fsExtra11 from "fs-extra";
|
|
575
611
|
import path9 from "path";
|
|
576
|
-
var { readFile:
|
|
612
|
+
var { readFile: readFile5, outputFile: outputFile5, pathExists: pathExists10 } = fsExtra11;
|
|
577
613
|
async function mergeSchema(target, value, cwd, dryRun) {
|
|
578
614
|
const file = path9.join(cwd, target);
|
|
579
615
|
if (!await pathExists10(file)) {
|
|
@@ -583,7 +619,7 @@ async function mergeSchema(target, value, cwd, dryRun) {
|
|
|
583
619
|
}
|
|
584
620
|
return;
|
|
585
621
|
}
|
|
586
|
-
const content = await
|
|
622
|
+
const content = await readFile5(file, "utf-8");
|
|
587
623
|
if (content.includes(value)) return;
|
|
588
624
|
if (!dryRun) {
|
|
589
625
|
const newContent = content.endsWith("\n") ? `${content}${value}
|
|
@@ -598,12 +634,12 @@ ${value}
|
|
|
598
634
|
import fsExtra12 from "fs-extra";
|
|
599
635
|
import path10 from "path";
|
|
600
636
|
import * as p3 from "@clack/prompts";
|
|
601
|
-
var { readFile:
|
|
637
|
+
var { readFile: readFile6, outputFile: outputFile6, pathExists: pathExists11 } = fsExtra12;
|
|
602
638
|
async function addEnv(target, value, cwd, dryRun) {
|
|
603
639
|
const file = path10.join(cwd, target);
|
|
604
640
|
let content = "";
|
|
605
641
|
if (await pathExists11(file)) {
|
|
606
|
-
content = await
|
|
642
|
+
content = await readFile6(file, "utf-8");
|
|
607
643
|
const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
608
644
|
const keyPattern = new RegExp(`^${escaped}=`, "m");
|
|
609
645
|
if (keyPattern.test(content)) return;
|
|
@@ -669,9 +705,8 @@ function authPath() {
|
|
|
669
705
|
import { z as z6 } from "zod";
|
|
670
706
|
var AuthCredentialsSchema = z6.object({
|
|
671
707
|
token: z6.string(),
|
|
672
|
-
refreshToken: z6.string().optional(),
|
|
673
708
|
expiresAt: z6.string().optional(),
|
|
674
|
-
email: z6.string()
|
|
709
|
+
email: z6.string().optional()
|
|
675
710
|
});
|
|
676
711
|
|
|
677
712
|
// src/auth/read-auth.ts
|
|
@@ -701,21 +736,28 @@ async function requireAuth() {
|
|
|
701
736
|
}
|
|
702
737
|
|
|
703
738
|
// src/commands/add.ts
|
|
704
|
-
async function
|
|
739
|
+
async function getManifest(moduleId, localPath, manifests) {
|
|
740
|
+
const cached = manifests.get(moduleId);
|
|
741
|
+
if (cached) return cached;
|
|
742
|
+
const manifest = await fetchModuleManifest(moduleId, localPath);
|
|
743
|
+
manifests.set(moduleId, manifest);
|
|
744
|
+
return manifest;
|
|
745
|
+
}
|
|
746
|
+
async function resolveModuleDeps(moduleId, localPath, manifests, resolved, orderedModules) {
|
|
705
747
|
if (resolved.has(moduleId)) return;
|
|
706
748
|
resolved.add(moduleId);
|
|
707
|
-
const manifest = await
|
|
749
|
+
const manifest = await getManifest(moduleId, localPath, manifests);
|
|
708
750
|
for (const dep of manifest.moduleDependencies) {
|
|
709
|
-
await resolveModuleDeps(dep, localPath, resolved, orderedModules);
|
|
751
|
+
await resolveModuleDeps(dep, localPath, manifests, resolved, orderedModules);
|
|
710
752
|
}
|
|
711
753
|
orderedModules.push(moduleId);
|
|
712
754
|
}
|
|
713
|
-
async function resolveWithParent(moduleId, localPath, installedNames, resolved, orderedModules) {
|
|
755
|
+
async function resolveWithParent(moduleId, localPath, installedNames, manifests, resolved, orderedModules) {
|
|
714
756
|
const { parent, child } = parseModuleId(moduleId);
|
|
715
757
|
if (child !== null && !installedNames.has(parent)) {
|
|
716
|
-
await resolveModuleDeps(parent, localPath, resolved, orderedModules);
|
|
758
|
+
await resolveModuleDeps(parent, localPath, manifests, resolved, orderedModules);
|
|
717
759
|
}
|
|
718
|
-
await resolveModuleDeps(moduleId, localPath, resolved, orderedModules);
|
|
760
|
+
await resolveModuleDeps(moduleId, localPath, manifests, resolved, orderedModules);
|
|
719
761
|
}
|
|
720
762
|
function detectPackageManager(cwd) {
|
|
721
763
|
if (existsSync(path12.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
@@ -737,17 +779,24 @@ function installNpmDeps(deps, cwd, dryRun) {
|
|
|
737
779
|
p5.log.step(`Installing dependencies: ${deps.join(", ")}`);
|
|
738
780
|
execFileSync(pm, args, { cwd, stdio: "inherit" });
|
|
739
781
|
}
|
|
740
|
-
async function installModule(moduleId, manifest, cwd, dryRun, localPath) {
|
|
782
|
+
async function installModule(moduleId, manifest, cwd, dryRun, installedModules, localPath) {
|
|
741
783
|
p5.log.step(`Installing ${moduleId}@${manifest.version}...`);
|
|
784
|
+
const installedDests = [];
|
|
742
785
|
if (manifest.files.length > 0) {
|
|
743
786
|
const installed = await installFiles({
|
|
744
787
|
moduleName: moduleId,
|
|
745
788
|
files: manifest.files,
|
|
746
789
|
cwd,
|
|
747
790
|
dryRun,
|
|
748
|
-
localPath
|
|
791
|
+
localPath,
|
|
792
|
+
installedModules
|
|
749
793
|
});
|
|
750
794
|
for (const f of installed) {
|
|
795
|
+
if (f.action === "conditional-skip") {
|
|
796
|
+
p5.log.message(` \u25CB ${f.dest} [skipped: ${f.reason}]`);
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
installedDests.push(f.dest);
|
|
751
800
|
const icon = f.action === "created" || f.action === "would-create" ? "+" : f.action === "overwritten" || f.action === "would-overwrite" ? "~" : "=";
|
|
752
801
|
p5.log.message(` ${icon} ${f.dest}`);
|
|
753
802
|
}
|
|
@@ -756,14 +805,15 @@ async function installModule(moduleId, manifest, cwd, dryRun, localPath) {
|
|
|
756
805
|
p5.log.step(` transform: ${transform.type} \u2192 ${transform.target}`);
|
|
757
806
|
await runTransform(transform, cwd, dryRun);
|
|
758
807
|
}
|
|
808
|
+
return installedDests;
|
|
759
809
|
}
|
|
760
|
-
function recordModule(config, moduleId, manifest, now) {
|
|
810
|
+
function recordModule(config, moduleId, manifest, installedFiles, now) {
|
|
761
811
|
const existing = config.installedModules.findIndex((m) => m.name === moduleId);
|
|
762
812
|
const record = {
|
|
763
813
|
name: moduleId,
|
|
764
814
|
version: manifest.version,
|
|
765
815
|
installedAt: now,
|
|
766
|
-
files:
|
|
816
|
+
files: installedFiles
|
|
767
817
|
};
|
|
768
818
|
if (existing >= 0) {
|
|
769
819
|
config.installedModules[existing] = record;
|
|
@@ -820,7 +870,7 @@ async function pickModulesInteractively(localPath) {
|
|
|
820
870
|
return [...result];
|
|
821
871
|
}
|
|
822
872
|
async function runAdd(options) {
|
|
823
|
-
let { moduleNames, cwd, dryRun, localPath, withSubModules = [] } = options;
|
|
873
|
+
let { moduleNames, cwd, dryRun, localPath, withSubModules = [], allowScripts = false } = options;
|
|
824
874
|
let allTargets;
|
|
825
875
|
if (moduleNames.length === 0) {
|
|
826
876
|
p5.intro(`impulse add${dryRun ? " [dry-run]" : ""}`);
|
|
@@ -852,6 +902,7 @@ async function runAdd(options) {
|
|
|
852
902
|
process.exit(1);
|
|
853
903
|
}
|
|
854
904
|
const installedNames = new Set(config.installedModules.map((m) => m.name));
|
|
905
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
855
906
|
if (withSubModules.length === 0) {
|
|
856
907
|
const alreadyInstalled = allTargets.filter((id) => {
|
|
857
908
|
const { child } = parseModuleId(id);
|
|
@@ -873,7 +924,7 @@ async function runAdd(options) {
|
|
|
873
924
|
}
|
|
874
925
|
}
|
|
875
926
|
if (moduleNames.length === 1 && withSubModules.length > 0) {
|
|
876
|
-
const parentManifest = await
|
|
927
|
+
const parentManifest = await getManifest(moduleNames[0], localPath, manifests).catch(() => null);
|
|
877
928
|
if (parentManifest) {
|
|
878
929
|
if (parentManifest.subModules.length === 0) {
|
|
879
930
|
p5.cancel(`"${moduleNames[0]}" has no declared sub-modules.`);
|
|
@@ -895,7 +946,7 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
895
946
|
const orderedModules = [];
|
|
896
947
|
try {
|
|
897
948
|
for (const target of allTargets) {
|
|
898
|
-
await resolveWithParent(target, localPath, installedNames, resolved, orderedModules);
|
|
949
|
+
await resolveWithParent(target, localPath, installedNames, manifests, resolved, orderedModules);
|
|
899
950
|
}
|
|
900
951
|
} catch (err) {
|
|
901
952
|
s.stop("Dependency resolution failed.");
|
|
@@ -903,18 +954,34 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
903
954
|
process.exit(1);
|
|
904
955
|
}
|
|
905
956
|
s.stop(`Resolved: ${orderedModules.join(" \u2192 ")}`);
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
957
|
+
const orderedManifests = orderedModules.map((id) => {
|
|
958
|
+
const manifest = manifests.get(id);
|
|
959
|
+
if (!manifest) {
|
|
960
|
+
throw new Error(`Missing manifest for resolved module "${id}".`);
|
|
961
|
+
}
|
|
962
|
+
return [id, manifest];
|
|
963
|
+
});
|
|
964
|
+
for (const [id, manifest] of orderedManifests) {
|
|
965
|
+
if (installedNames.has(id)) continue;
|
|
966
|
+
for (const incompatible of manifest.incompatibleWith) {
|
|
967
|
+
if (installedNames.has(incompatible)) {
|
|
968
|
+
p5.cancel(`Module "${id}" is incompatible with installed module "${incompatible}".`);
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
if (manifests.has(incompatible) && !installedNames.has(incompatible)) {
|
|
972
|
+
p5.cancel(`Module "${id}" is incompatible with module "${incompatible}" (also being installed).`);
|
|
973
|
+
process.exit(1);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
909
976
|
}
|
|
910
977
|
const allDeps = /* @__PURE__ */ new Set();
|
|
911
|
-
for (const manifest of
|
|
978
|
+
for (const [, manifest] of orderedManifests) {
|
|
912
979
|
for (const dep of manifest.dependencies) {
|
|
913
980
|
allDeps.add(dep);
|
|
914
981
|
}
|
|
915
982
|
}
|
|
916
983
|
p5.log.message("\nSummary of changes:");
|
|
917
|
-
for (const [id, manifest] of
|
|
984
|
+
for (const [id, manifest] of orderedManifests) {
|
|
918
985
|
p5.log.message(`
|
|
919
986
|
Module: ${id}@${manifest.version}`);
|
|
920
987
|
for (const file of manifest.files) {
|
|
@@ -925,7 +992,7 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
925
992
|
}
|
|
926
993
|
}
|
|
927
994
|
const allEnvVars = /* @__PURE__ */ new Set();
|
|
928
|
-
for (const manifest of
|
|
995
|
+
for (const [, manifest] of orderedManifests) {
|
|
929
996
|
for (const envVar of manifest.envVars) {
|
|
930
997
|
allEnvVars.add(envVar);
|
|
931
998
|
}
|
|
@@ -951,13 +1018,16 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
951
1018
|
const primaryTargetSet = new Set(allTargets);
|
|
952
1019
|
const depModules = orderedModules.filter((id) => !primaryTargetSet.has(id) && !installedNames.has(id));
|
|
953
1020
|
const targetModules = orderedModules.filter((id) => primaryTargetSet.has(id));
|
|
1021
|
+
const installedFilesMap = /* @__PURE__ */ new Map();
|
|
954
1022
|
const depPostInstallHooks = [];
|
|
955
1023
|
if (depModules.length > 0) {
|
|
956
1024
|
p5.log.step(`Installing module dependencies: ${depModules.join(", ")}`);
|
|
957
1025
|
for (const dep of depModules) {
|
|
958
1026
|
const depManifest = manifests.get(dep);
|
|
959
1027
|
if (!depManifest) continue;
|
|
960
|
-
await installModule(dep, depManifest, cwd, dryRun, localPath);
|
|
1028
|
+
const dests = await installModule(dep, depManifest, cwd, dryRun, installedNames, localPath);
|
|
1029
|
+
installedFilesMap.set(dep, dests);
|
|
1030
|
+
installedNames.add(dep);
|
|
961
1031
|
if (depManifest.postInstall && depManifest.postInstall.length > 0) {
|
|
962
1032
|
depPostInstallHooks.push({ name: dep, hooks: depManifest.postInstall });
|
|
963
1033
|
}
|
|
@@ -966,26 +1036,69 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
966
1036
|
for (const targetId of targetModules) {
|
|
967
1037
|
const targetManifest = manifests.get(targetId);
|
|
968
1038
|
if (!targetManifest) continue;
|
|
969
|
-
await installModule(targetId, targetManifest, cwd, dryRun, localPath);
|
|
1039
|
+
const dests = await installModule(targetId, targetManifest, cwd, dryRun, installedNames, localPath);
|
|
1040
|
+
installedFilesMap.set(targetId, dests);
|
|
1041
|
+
installedNames.add(targetId);
|
|
970
1042
|
}
|
|
971
1043
|
installNpmDeps([...allDeps], cwd, dryRun);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1044
|
+
const allPostInstallHooks = [];
|
|
1045
|
+
for (const { name, hooks } of depPostInstallHooks) {
|
|
1046
|
+
if (hooks.length > 0) allPostInstallHooks.push({ name, hooks });
|
|
1047
|
+
}
|
|
1048
|
+
for (const targetId of targetModules) {
|
|
1049
|
+
const targetManifest = manifests.get(targetId);
|
|
1050
|
+
if (targetManifest?.postInstall?.length) {
|
|
1051
|
+
allPostInstallHooks.push({ name: targetId, hooks: targetManifest.postInstall });
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (allPostInstallHooks.length > 0) {
|
|
1055
|
+
const allHookLines = [];
|
|
1056
|
+
for (const { name, hooks } of allPostInstallHooks) {
|
|
975
1057
|
for (const hook of hooks) {
|
|
976
|
-
|
|
977
|
-
execSync2(hook, { cwd, stdio: "inherit" });
|
|
1058
|
+
allHookLines.push(` [${name}] $ ${hook}`);
|
|
978
1059
|
}
|
|
979
1060
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1061
|
+
if (dryRun) {
|
|
1062
|
+
p5.log.message("\nPost-install hooks that would run:");
|
|
1063
|
+
for (const line of allHookLines) {
|
|
1064
|
+
p5.log.message(line);
|
|
1065
|
+
}
|
|
1066
|
+
} else if (!allowScripts && process.stdin.isTTY === false) {
|
|
1067
|
+
p5.log.warn(
|
|
1068
|
+
`Skipping ${allHookLines.length} post-install hook(s) \u2014 non-interactive mode detected. Pass --allow-scripts to run them.`
|
|
1069
|
+
);
|
|
1070
|
+
for (const line of allHookLines) {
|
|
1071
|
+
p5.log.message(line);
|
|
1072
|
+
}
|
|
1073
|
+
} else {
|
|
1074
|
+
p5.log.message("\nPost-install hooks to run:");
|
|
1075
|
+
for (const line of allHookLines) {
|
|
1076
|
+
p5.log.message(line);
|
|
1077
|
+
}
|
|
1078
|
+
let runHooks;
|
|
1079
|
+
if (allowScripts) {
|
|
1080
|
+
runHooks = true;
|
|
1081
|
+
} else {
|
|
1082
|
+
const confirmed = await p5.confirm({
|
|
1083
|
+
message: "Run post-install hooks?",
|
|
1084
|
+
initialValue: true
|
|
1085
|
+
});
|
|
1086
|
+
if (p5.isCancel(confirmed)) {
|
|
1087
|
+
p5.outro("Cancelled.");
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
runHooks = confirmed;
|
|
1091
|
+
}
|
|
1092
|
+
if (runHooks) {
|
|
1093
|
+
for (const { name, hooks } of allPostInstallHooks) {
|
|
1094
|
+
p5.log.step(`Running post-install hooks for ${name}...`);
|
|
1095
|
+
for (const hook of hooks) {
|
|
1096
|
+
p5.log.message(` $ ${hook}`);
|
|
1097
|
+
execSync2(hook, { cwd, stdio: "inherit" });
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
p5.log.warn("Post-install hooks skipped.");
|
|
989
1102
|
}
|
|
990
1103
|
}
|
|
991
1104
|
}
|
|
@@ -993,11 +1106,13 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
993
1106
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
994
1107
|
for (const dep of depModules) {
|
|
995
1108
|
const depManifest = manifests.get(dep);
|
|
996
|
-
|
|
1109
|
+
const dests = installedFilesMap.get(dep) ?? [];
|
|
1110
|
+
if (depManifest) recordModule(config, dep, depManifest, dests, now);
|
|
997
1111
|
}
|
|
998
1112
|
for (const targetId of targetModules) {
|
|
999
1113
|
const targetManifest = manifests.get(targetId);
|
|
1000
|
-
|
|
1114
|
+
const dests = installedFilesMap.get(targetId) ?? [];
|
|
1115
|
+
if (targetManifest) recordModule(config, targetId, targetManifest, dests, now);
|
|
1001
1116
|
}
|
|
1002
1117
|
await writeConfig(config, cwd);
|
|
1003
1118
|
}
|
|
@@ -1009,6 +1124,24 @@ Available: ${parentManifest.subModules.join(", ")}`
|
|
|
1009
1124
|
|
|
1010
1125
|
// src/commands/list.ts
|
|
1011
1126
|
import * as p6 from "@clack/prompts";
|
|
1127
|
+
function printModule(mod, installedNames, installedModules) {
|
|
1128
|
+
const installed = installedNames.has(mod.name);
|
|
1129
|
+
const installedInfo = installed ? installedModules?.find((m) => m.name === mod.name) : null;
|
|
1130
|
+
const status = installed ? `[installed v${installedInfo?.version ?? "?"}]` : "[available]";
|
|
1131
|
+
const desc = mod.description ? ` \u2014 ${mod.description}` : "";
|
|
1132
|
+
p6.log.message(` ${installed ? "\u2713" : "\u25CB"} ${mod.name} ${status}${desc}`);
|
|
1133
|
+
if (mod.subModules && mod.subModules.length > 0) {
|
|
1134
|
+
const last = mod.subModules.length - 1;
|
|
1135
|
+
mod.subModules.forEach((sub, i) => {
|
|
1136
|
+
const subId = `${mod.name}/${sub}`;
|
|
1137
|
+
const subInstalled = installedNames.has(subId);
|
|
1138
|
+
const subInfo = subInstalled ? installedModules?.find((m) => m.name === subId) : null;
|
|
1139
|
+
const subStatus = subInstalled ? `[installed v${subInfo?.version ?? "?"}]` : "[not installed]";
|
|
1140
|
+
const connector = i === last ? "\u2514\u2500" : "\u251C\u2500";
|
|
1141
|
+
p6.log.message(` ${connector} ${sub} ${subStatus}`);
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1012
1145
|
async function runList(options) {
|
|
1013
1146
|
const { cwd, localPath } = options;
|
|
1014
1147
|
p6.intro("impulse list");
|
|
@@ -1034,22 +1167,33 @@ async function runList(options) {
|
|
|
1034
1167
|
return;
|
|
1035
1168
|
}
|
|
1036
1169
|
p6.log.message("\nAvailable modules:\n");
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1170
|
+
const hasCategories = available.some((m) => m.category !== void 0);
|
|
1171
|
+
if (hasCategories) {
|
|
1172
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1173
|
+
for (const mod of available) {
|
|
1174
|
+
const key = mod.category ?? "other";
|
|
1175
|
+
const existing = groups.get(key);
|
|
1176
|
+
if (existing) {
|
|
1177
|
+
existing.push(mod);
|
|
1178
|
+
} else {
|
|
1179
|
+
groups.set(key, [mod]);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
const categoryOrder = ["core", "feature", "integration", "dx", "other"];
|
|
1183
|
+
const sortedKeys = [
|
|
1184
|
+
...categoryOrder.filter((k) => groups.has(k)),
|
|
1185
|
+
...[...groups.keys()].filter((k) => !categoryOrder.includes(k))
|
|
1186
|
+
];
|
|
1187
|
+
for (const key of sortedKeys) {
|
|
1188
|
+
p6.log.message(`${key.toUpperCase()}`);
|
|
1189
|
+
for (const mod of groups.get(key)) {
|
|
1190
|
+
printModule(mod, installedNames, config?.installedModules);
|
|
1191
|
+
}
|
|
1192
|
+
p6.log.message("");
|
|
1193
|
+
}
|
|
1194
|
+
} else {
|
|
1195
|
+
for (const mod of available) {
|
|
1196
|
+
printModule(mod, installedNames, config?.installedModules);
|
|
1053
1197
|
}
|
|
1054
1198
|
}
|
|
1055
1199
|
p6.log.message(
|
|
@@ -1067,19 +1211,20 @@ import * as p7 from "@clack/prompts";
|
|
|
1067
1211
|
|
|
1068
1212
|
// src/auth/device-flow.ts
|
|
1069
1213
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1070
|
-
var IMPULSE_BASE_URL = process.env.IMPULSE_BASE_URL ?? "https://
|
|
1214
|
+
var IMPULSE_BASE_URL = process.env.IMPULSE_BASE_URL ?? "https://impulselab.ai";
|
|
1215
|
+
var CLIENT_ID = "impulse-cli";
|
|
1071
1216
|
async function requestDeviceCode() {
|
|
1072
1217
|
const res = await fetch(`${IMPULSE_BASE_URL}/api/auth/device/code`, {
|
|
1073
1218
|
method: "POST",
|
|
1074
1219
|
headers: { "Content-Type": "application/json" },
|
|
1075
|
-
body: JSON.stringify({
|
|
1220
|
+
body: JSON.stringify({ client_id: CLIENT_ID })
|
|
1076
1221
|
});
|
|
1077
1222
|
if (!res.ok) {
|
|
1078
1223
|
throw new Error(`Failed to initiate device flow: ${res.status} ${res.statusText}`);
|
|
1079
1224
|
}
|
|
1080
1225
|
const data = await res.json();
|
|
1081
1226
|
assertDeviceCodeResponse(data);
|
|
1082
|
-
return data;
|
|
1227
|
+
return mapDeviceCodeResponse(data);
|
|
1083
1228
|
}
|
|
1084
1229
|
async function pollDeviceToken(deviceCode) {
|
|
1085
1230
|
let res;
|
|
@@ -1087,7 +1232,11 @@ async function pollDeviceToken(deviceCode) {
|
|
|
1087
1232
|
res = await fetch(`${IMPULSE_BASE_URL}/api/auth/device/token`, {
|
|
1088
1233
|
method: "POST",
|
|
1089
1234
|
headers: { "Content-Type": "application/json" },
|
|
1090
|
-
body: JSON.stringify({
|
|
1235
|
+
body: JSON.stringify({
|
|
1236
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1237
|
+
device_code: deviceCode,
|
|
1238
|
+
client_id: CLIENT_ID
|
|
1239
|
+
})
|
|
1091
1240
|
});
|
|
1092
1241
|
} catch {
|
|
1093
1242
|
return { status: "error", message: "Network error while polling for token" };
|
|
@@ -1095,10 +1244,7 @@ async function pollDeviceToken(deviceCode) {
|
|
|
1095
1244
|
if (res.status === 200) {
|
|
1096
1245
|
const data = await res.json();
|
|
1097
1246
|
assertDeviceTokenResponse(data);
|
|
1098
|
-
return { status: "authorized", data };
|
|
1099
|
-
}
|
|
1100
|
-
if (res.status === 202) {
|
|
1101
|
-
return { status: "pending" };
|
|
1247
|
+
return { status: "authorized", data: mapDeviceTokenResponse(data) };
|
|
1102
1248
|
}
|
|
1103
1249
|
if (res.status === 400) {
|
|
1104
1250
|
let body;
|
|
@@ -1108,8 +1254,10 @@ async function pollDeviceToken(deviceCode) {
|
|
|
1108
1254
|
body = {};
|
|
1109
1255
|
}
|
|
1110
1256
|
const error = typeof body === "object" && body !== null && "error" in body ? String(body.error) : "";
|
|
1257
|
+
if (error === "authorization_pending") return { status: "pending" };
|
|
1111
1258
|
if (error === "slow_down") return { status: "slow_down" };
|
|
1112
|
-
if (error === "
|
|
1259
|
+
if (error === "expired_token") return { status: "expired" };
|
|
1260
|
+
if (error === "access_denied") return { status: "denied" };
|
|
1113
1261
|
return { status: "error", message: error || "Device code error" };
|
|
1114
1262
|
}
|
|
1115
1263
|
return { status: "error", message: `Unexpected response: ${res.status}` };
|
|
@@ -1128,15 +1276,32 @@ function openBrowser(url) {
|
|
|
1128
1276
|
}
|
|
1129
1277
|
}
|
|
1130
1278
|
function assertDeviceCodeResponse(data) {
|
|
1131
|
-
if (typeof data !== "object" || data === null || typeof data.
|
|
1279
|
+
if (typeof data !== "object" || data === null || typeof data.device_code !== "string" || typeof data.user_code !== "string" || typeof data.verification_uri !== "string") {
|
|
1132
1280
|
throw new Error("Invalid device code response from server");
|
|
1133
1281
|
}
|
|
1134
1282
|
}
|
|
1135
1283
|
function assertDeviceTokenResponse(data) {
|
|
1136
|
-
if (typeof data !== "object" || data === null || typeof data.
|
|
1284
|
+
if (typeof data !== "object" || data === null || typeof data.access_token !== "string") {
|
|
1137
1285
|
throw new Error("Invalid token response from server");
|
|
1138
1286
|
}
|
|
1139
1287
|
}
|
|
1288
|
+
function mapDeviceCodeResponse(raw) {
|
|
1289
|
+
return {
|
|
1290
|
+
deviceCode: raw.device_code,
|
|
1291
|
+
userCode: raw.user_code,
|
|
1292
|
+
verificationUri: raw.verification_uri,
|
|
1293
|
+
...raw.verification_uri_complete !== void 0 ? { verificationUriComplete: raw.verification_uri_complete } : {},
|
|
1294
|
+
expiresIn: raw.expires_in,
|
|
1295
|
+
interval: raw.interval
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
function mapDeviceTokenResponse(raw) {
|
|
1299
|
+
return {
|
|
1300
|
+
token: raw.access_token,
|
|
1301
|
+
...raw.expires_in !== void 0 ? { expiresIn: raw.expires_in } : {},
|
|
1302
|
+
...raw.email !== void 0 ? { email: raw.email } : {}
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1140
1305
|
|
|
1141
1306
|
// src/auth/write-auth.ts
|
|
1142
1307
|
import fsExtra14 from "fs-extra";
|
|
@@ -1156,7 +1321,7 @@ async function runLogin() {
|
|
|
1156
1321
|
const existing = await readAuth();
|
|
1157
1322
|
if (existing) {
|
|
1158
1323
|
const reauth = await p7.confirm({
|
|
1159
|
-
message: `Already logged in as ${existing.email}. Log in again?`,
|
|
1324
|
+
message: `Already logged in as ${existing.email ?? "current session"}. Log in again?`,
|
|
1160
1325
|
initialValue: false
|
|
1161
1326
|
});
|
|
1162
1327
|
if (p7.isCancel(reauth) || !reauth) {
|
|
@@ -1199,13 +1364,14 @@ Base URL: ${IMPULSE_BASE_URL}`);
|
|
|
1199
1364
|
const result = await pollDeviceToken(deviceCode.deviceCode);
|
|
1200
1365
|
if (result.status === "authorized") {
|
|
1201
1366
|
pollSpinner.stop("Authentication successful!");
|
|
1367
|
+
const expiresAt2 = result.data.expiresIn !== void 0 ? new Date(Date.now() + result.data.expiresIn * 1e3).toISOString() : void 0;
|
|
1202
1368
|
await writeAuth({
|
|
1203
1369
|
token: result.data.token,
|
|
1204
|
-
|
|
1205
|
-
expiresAt: result.data.expiresAt,
|
|
1370
|
+
expiresAt: expiresAt2,
|
|
1206
1371
|
email: result.data.email
|
|
1207
1372
|
});
|
|
1208
|
-
|
|
1373
|
+
const identity = result.data.email ?? "your account";
|
|
1374
|
+
p7.outro(`Logged in as ${identity}`);
|
|
1209
1375
|
return;
|
|
1210
1376
|
}
|
|
1211
1377
|
if (result.status === "slow_down") {
|
|
@@ -1217,6 +1383,11 @@ Base URL: ${IMPULSE_BASE_URL}`);
|
|
|
1217
1383
|
p7.cancel("Authentication timed out. Run `impulse login` again.");
|
|
1218
1384
|
process.exit(1);
|
|
1219
1385
|
}
|
|
1386
|
+
if (result.status === "denied") {
|
|
1387
|
+
pollSpinner.stop("Access denied.");
|
|
1388
|
+
p7.cancel("You denied the authorization request.");
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1220
1391
|
if (result.status === "error") {
|
|
1221
1392
|
pollSpinner.stop("Authentication failed.");
|
|
1222
1393
|
p7.cancel(result.message);
|
|
@@ -1253,7 +1424,7 @@ async function runLogout() {
|
|
|
1253
1424
|
return;
|
|
1254
1425
|
}
|
|
1255
1426
|
const confirm6 = await p8.confirm({
|
|
1256
|
-
message: `Log out ${credentials.email}?`,
|
|
1427
|
+
message: `Log out ${credentials.email ?? "current session"}?`,
|
|
1257
1428
|
initialValue: true
|
|
1258
1429
|
});
|
|
1259
1430
|
if (p8.isCancel(confirm6) || !confirm6) {
|
|
@@ -1273,7 +1444,7 @@ async function runWhoami() {
|
|
|
1273
1444
|
p9.cancel("Not authenticated. Run `impulse login` first.");
|
|
1274
1445
|
process.exit(1);
|
|
1275
1446
|
}
|
|
1276
|
-
p9.log.message(`Logged in as: ${credentials.email}`);
|
|
1447
|
+
p9.log.message(`Logged in as: ${credentials.email ?? "(unknown)"}`);
|
|
1277
1448
|
if (credentials.expiresAt) {
|
|
1278
1449
|
const expires = new Date(credentials.expiresAt);
|
|
1279
1450
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1305,11 +1476,16 @@ program.command("add [modules...]").description(
|
|
|
1305
1476
|
).option(
|
|
1306
1477
|
"--with <submodules>",
|
|
1307
1478
|
"Comma-separated sub-modules to install alongside the parent (e.g. --with quote-to-cash,gocardless)"
|
|
1479
|
+
).option(
|
|
1480
|
+
"--allow-scripts",
|
|
1481
|
+
"Run post-install hooks without prompting for confirmation (required in non-interactive environments)",
|
|
1482
|
+
false
|
|
1308
1483
|
).action(async (modules, options) => {
|
|
1309
1484
|
const addOpts = {
|
|
1310
1485
|
moduleNames: modules ?? [],
|
|
1311
1486
|
cwd: process.cwd(),
|
|
1312
|
-
dryRun: options.dryRun
|
|
1487
|
+
dryRun: options.dryRun,
|
|
1488
|
+
allowScripts: options.allowScripts
|
|
1313
1489
|
};
|
|
1314
1490
|
if (options.local !== void 0) addOpts.localPath = options.local;
|
|
1315
1491
|
if (options.with !== void 0) {
|