@detergent-software/atk 9.2.0-dev.5 → 9.2.0-dev.6
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/CHANGELOG.md +15 -0
- package/build/command-manifest.json +3 -3
- package/build/commands/install.d.ts.map +1 -1
- package/build/commands/install.js +226 -74
- package/build/commands/install.js.map +1 -1
- package/build/commands/uninstall.d.ts.map +1 -1
- package/build/commands/uninstall.js +5 -3
- package/build/commands/uninstall.js.map +1 -1
- package/build/hooks/useUninstallState.d.ts.map +1 -1
- package/build/hooks/useUninstallState.js +23 -15
- package/build/hooks/useUninstallState.js.map +1 -1
- package/build/lib/installer.js +3 -2
- package/build/lib/installer.js.map +1 -1
- package/build/lib/resolver.d.ts +35 -0
- package/build/lib/resolver.d.ts.map +1 -1
- package/build/lib/resolver.js +65 -21
- package/build/lib/resolver.js.map +1 -1
- package/build/lib/uninstaller.d.ts +29 -1
- package/build/lib/uninstaller.d.ts.map +1 -1
- package/build/lib/uninstaller.js +63 -4
- package/build/lib/uninstaller.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [9.2.0-dev.6](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTv9.2.0-dev.5&targetVersion=GTv9.2.0-dev.6) (2026-10-23)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **cli:** install bundles and [@org](https://emergentcode.visualstudio.com/org) among several names as one plan ([47425f3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/47425f35d4986306dc024fca8ad6d767d064188f))
|
|
6
|
+
* **cli:** uninstall a bundle among several names in one pass ([8c75075](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/8c75075465bd210989baba237955a68dcce2ad73))
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **cli:** review fixes for bundles and [@org](https://emergentcode.visualstudio.com/org) among install names ([fc66d79](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/fc66d7994d704cf1ceebb6f2b551737e3233f671))
|
|
11
|
+
|
|
12
|
+
### Documentation
|
|
13
|
+
|
|
14
|
+
* **cli:** describe bundles and [@org](https://emergentcode.visualstudio.com/org) among several install names ([febfb7e](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/febfb7e6a81d6308c2df788fddfc2f1cb02e54f7))
|
|
15
|
+
|
|
1
16
|
## [9.2.0-dev.5](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTv9.2.0-dev.4&targetVersion=GTv9.2.0-dev.5) (2026-10-23)
|
|
2
17
|
|
|
3
18
|
### Features
|
|
@@ -515,7 +515,7 @@
|
|
|
515
515
|
"name": "[target]"
|
|
516
516
|
},
|
|
517
517
|
{
|
|
518
|
-
"description": "More assets to install with the first, as one plan (name@version
|
|
518
|
+
"description": "More assets, bundles or org scopes to install with the first, as one plan (name@version, @org/name, @org with --all)",
|
|
519
519
|
"name": "[targets...]"
|
|
520
520
|
}
|
|
521
521
|
],
|
|
@@ -529,7 +529,7 @@
|
|
|
529
529
|
},
|
|
530
530
|
{
|
|
531
531
|
"flags": "-a, --all",
|
|
532
|
-
"description": "Install every asset from an org scope without prompting (for `atk install @org`; required with --json or
|
|
532
|
+
"description": "Install every asset from an org scope without prompting (for `atk install @org`; required with --json, no terminal, or other names)",
|
|
533
533
|
"defaultValue": false
|
|
534
534
|
},
|
|
535
535
|
{
|
|
@@ -1331,7 +1331,7 @@
|
|
|
1331
1331
|
"name": "<name>"
|
|
1332
1332
|
},
|
|
1333
1333
|
{
|
|
1334
|
-
"description": "More assets to uninstall with the first",
|
|
1334
|
+
"description": "More assets or bundles to uninstall with the first, in one pass",
|
|
1335
1335
|
"name": "[names...]"
|
|
1336
1336
|
}
|
|
1337
1337
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqExB,eAAO,MAAM,WAAW,6DAA6D,CAAC;AAEtF,eAAO,MAAM,OAAO;;;;;;;;;;;;;iBA8GlB,CAAC;AAEH,eAAO,MAAM,IAAI,uDAoBd,CAAC;AAqIJ,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IAC3B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAC;CAClC,CAAC;AAQF,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,EAC9B,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,EACxB,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAC9F,EAAE,KAAK,+BAwDP"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useApp } from 'ink';
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { CrossScopeWarning, DryRunBanner, ErrorView, InstallSummary, MultiSelectList, Spinner, } from '../components/index.js';
|
|
6
6
|
import { useCommand } from '../hooks/useCommand.js';
|
|
@@ -11,6 +11,7 @@ import { loadConfig } from '../lib/config.js';
|
|
|
11
11
|
import { crossScopeNotice, findCrossScopeInstalls } from '../lib/cross-scope.js';
|
|
12
12
|
import { errorGuidance, errorMessage } from '../lib/errors.js';
|
|
13
13
|
import { isInstalledForTool } from '../lib/installed-for-tool.js';
|
|
14
|
+
import { InstallerError } from '../lib/installer.js';
|
|
14
15
|
import { addAssetToLockfile, findInstalledAsset, identityKey, readLockfile, upgradeToDirectInstall, withLockfileErrorHandling, writeLockfile, } from '../lib/lockfile.js';
|
|
15
16
|
import { executeInstallPlanForTools, withoutKeptDependencies } from '../lib/multi-tool-install.js';
|
|
16
17
|
import { addNotice } from '../lib/notices.js';
|
|
@@ -18,7 +19,7 @@ import { formatOrgName, isBareOrgScope, parseBundleSpec, resolveActiveOrg } from
|
|
|
18
19
|
import { renderJson, useNonInteractive } from '../lib/output.js';
|
|
19
20
|
import { argument, option } from '../lib/pastel/index.js';
|
|
20
21
|
import { fetchRegistry, findAsset, findAssetsByName, findBundle, findBundleMember, getAssetsByOrg, isDeprecated, isLatestAlias, } from '../lib/registry.js';
|
|
21
|
-
import { resolveBundleInstallPlan, resolveInstallPlan, resolveMultipleInstallPlans, ResolverError, } from '../lib/resolver.js';
|
|
22
|
+
import { resolveBundleInstallPlan, resolveInstallPlan, resolveMultipleInstallPlans, ResolverError, resolveTargetsInstallPlan, } from '../lib/resolver.js';
|
|
22
23
|
import { scopeLabel } from '../lib/scope.js';
|
|
23
24
|
import { resolvePlacingTools } from '../lib/tool-resolver.js';
|
|
24
25
|
import { parseNameVersion } from '../lib/version.js';
|
|
@@ -36,7 +37,7 @@ export const options = z.object({
|
|
|
36
37
|
.default(false)
|
|
37
38
|
.describe(option({
|
|
38
39
|
alias: 'a',
|
|
39
|
-
description: 'Install every asset from an org scope without prompting (for `atk install @org`; required with --json or
|
|
40
|
+
description: 'Install every asset from an org scope without prompting (for `atk install @org`; required with --json, no terminal, or other names)',
|
|
40
41
|
})),
|
|
41
42
|
dryRun: z
|
|
42
43
|
.boolean()
|
|
@@ -119,7 +120,7 @@ export const args = z
|
|
|
119
120
|
})),
|
|
120
121
|
])
|
|
121
122
|
.rest(z.string().describe(argument({
|
|
122
|
-
description: 'More assets to install with the first, as one plan (name@version
|
|
123
|
+
description: 'More assets, bundles or org scopes to install with the first, as one plan (name@version, @org/name, @org with --all)',
|
|
123
124
|
name: 'targets',
|
|
124
125
|
})));
|
|
125
126
|
export default function Install({ args: [rawName, ...more], options: { adopt, all, dryRun, force, json, org, pin, projectRoot, refresh, tool, user, yes }, }) {
|
|
@@ -128,7 +129,7 @@ export default function Install({ args: [rawName, ...more], options: { adopt, al
|
|
|
128
129
|
return _jsx(Sync, { options: { dryRun, force, json, projectRoot, refresh, tool, user } });
|
|
129
130
|
}
|
|
130
131
|
if (more.length > 0) {
|
|
131
|
-
return (_jsx(InstallAssets, { adopt: adopt, dryRun: dryRun, force: force, json: json, org: org, pin: pin, projectRoot: projectRoot, specs: [rawName, ...more], tool: tool, user: user }));
|
|
132
|
+
return (_jsx(InstallAssets, { adopt: adopt, all: all || yes, dryRun: dryRun, force: force, json: json, org: org, pin: pin, projectRoot: projectRoot, specs: [rawName, ...more], tool: tool, user: user }));
|
|
132
133
|
}
|
|
133
134
|
if (isBareOrgScope(rawName)) {
|
|
134
135
|
const orgName = rawName.slice(1);
|
|
@@ -273,16 +274,8 @@ function InstallAsset({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoo
|
|
|
273
274
|
// through to a misleading "asset not found".
|
|
274
275
|
if (!indexBundle && (registry.bundles ?? []).some((b) => b.name === name)) {
|
|
275
276
|
const assetExists = findAsset(registry, name, undefined, undefined, org);
|
|
276
|
-
if (!assetExists)
|
|
277
|
-
|
|
278
|
-
throw new Error(`Bundle '${name}' not found in org '${inlineOrg}'.`);
|
|
279
|
-
}
|
|
280
|
-
const orgScopes = (registry.bundles ?? [])
|
|
281
|
-
.filter((b) => b.name === name && b.org)
|
|
282
|
-
.map((b) => `@${b.org}/${name}`);
|
|
283
|
-
throw new Error(`Bundle '${name}' not found in the registry.` +
|
|
284
|
-
(orgScopes.length > 0 ? ` Did you mean ${orgScopes.join(' or ')}?` : ''));
|
|
285
|
-
}
|
|
277
|
+
if (!assetExists)
|
|
278
|
+
throw otherScopeBundleError(registry, name, inlineOrg);
|
|
286
279
|
}
|
|
287
280
|
// With no active org, `info` opens the only org asset of a bare name, but install doesn't
|
|
288
281
|
// switch scopes on its own: it names the spec that installs that asset.
|
|
@@ -360,26 +353,7 @@ function InstallAsset({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoo
|
|
|
360
353
|
};
|
|
361
354
|
// Bundle installation flow — check if already installed for ALL tools
|
|
362
355
|
if (!force) {
|
|
363
|
-
|
|
364
|
-
// When no configured tool takes any member, fall through so the installer says why.
|
|
365
|
-
const allInstalledForAllTools = tools.every(({ adapter: toolAdapter }) => members.length > 0 &&
|
|
366
|
-
bundleAssets.every((ref) => {
|
|
367
|
-
const found = findBundleMember(registry, bundle, ref);
|
|
368
|
-
if (!found)
|
|
369
|
-
return false;
|
|
370
|
-
// A tool that doesn't take this member (its type, or a hook for other tools) has
|
|
371
|
-
// nothing to check.
|
|
372
|
-
if (!placesAsset(toolAdapter, found.asset.type, found.versionData.tools))
|
|
373
|
-
return true;
|
|
374
|
-
const installed = findInstalledAsset(before, ref.name, ref.type, found.asset.org);
|
|
375
|
-
if (!installed)
|
|
376
|
-
return false;
|
|
377
|
-
if (requiredBundleVersion && !isFromBundleVersion(installed, bundle, requiredBundleVersion)) {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
return isInstalledForTool(installed, toolAdapter, projectDir);
|
|
381
|
-
}));
|
|
382
|
-
if (allInstalledForAllTools) {
|
|
356
|
+
if (isBundleInPlace(registry, bundle, before, { members, projectDir, requiredBundleVersion, tools })) {
|
|
383
357
|
// Adopting a bundle that is in place only records its origin on the members with none:
|
|
384
358
|
// their files, versions and pins stay as they are. Another bundle's member stays its.
|
|
385
359
|
const outside = membersOutsideBundle(before, members, bundle);
|
|
@@ -521,13 +495,15 @@ function InstallAsset({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoo
|
|
|
521
495
|
return (_jsxs(Box, { flexDirection: 'column', children: [data.toolResults.map(({ results, skipped, tool: toolName, warnings }) => (_jsx(InstallSummary, { results: results, skipped: skipped, tools: [toolName], warnings: warnings, where: label }, toolName))), _jsx(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
522
496
|
}
|
|
523
497
|
/**
|
|
524
|
-
* `atk install a b@1.2.0 @org/c`: the named assets
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* once the install succeeds.
|
|
498
|
+
* `atk install a b@1.2.0 @org/c my-bundle @org --all`: the named assets, the bundles' members and
|
|
499
|
+
* the orgs' assets resolved together (shared dependencies once, one version per asset) and
|
|
500
|
+
* installed as one plan, so a failure rolls the whole run back, as an `@org` install does. Every
|
|
501
|
+
* name is checked before anything is written. An asset already in place for every tool is left out
|
|
502
|
+
* of the plan, and one installed only as a dependency becomes direct once the install succeeds. A
|
|
503
|
+
* bundle is left out when all of it is in place, as when it is installed alone; its members get
|
|
504
|
+
* its origin unless they are named themselves ({@link resolveTargetsInstallPlan}).
|
|
529
505
|
*/
|
|
530
|
-
function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoot, specs, tool, user, }) {
|
|
506
|
+
function InstallAssets({ adopt, all, dryRun, force, json, org: cliOrg, pin, projectRoot, specs, tool, user, }) {
|
|
531
507
|
const { json: effectiveJson } = useNonInteractive(json);
|
|
532
508
|
const { fields, resolve, resolved } = useResolvedScope({ projectRoot, user });
|
|
533
509
|
const state = useCommand(async (progress) => {
|
|
@@ -535,11 +511,13 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
535
511
|
if (adopt) {
|
|
536
512
|
throw new Error('--adopt records a bundle as the owner of its members, so it works with one bundle only.');
|
|
537
513
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
514
|
+
// No picker opens halfway through a run, so an org among names takes all its assets, and
|
|
515
|
+
// only when asked to. A dry run shows them all, as `atk install @org --dry-run` does.
|
|
516
|
+
const orgScopes = [...new Set(specs.filter(isBareOrgScope))];
|
|
517
|
+
if (orgScopes.length > 0 && !all && !dryRun) {
|
|
518
|
+
throw new InstallerError(`${orgScopes.join(', ')} among other names installs every asset in ` +
|
|
519
|
+
`${orgScopes.length === 1 ? 'it' : 'them'}, so it needs --all (-a). ` +
|
|
520
|
+
`To choose ${orgScopes.length === 1 ? 'its' : 'their'} assets, install ${orgScopes.length === 1 ? 'it' : 'each'} alone.`, { code: 'selection_required', command: installCommandFor(`${specs.join(' ')} --all`, flags) });
|
|
543
521
|
}
|
|
544
522
|
const { root: projectDir, scope: installScope } = await resolve();
|
|
545
523
|
progress('Loading configuration...');
|
|
@@ -549,33 +527,65 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
549
527
|
progress('Fetching registry...');
|
|
550
528
|
const registry = await fetchRegistry({ force: true });
|
|
551
529
|
const currentLockfile = await readLockfile(projectDir);
|
|
552
|
-
// Every name is checked before anything is resolved or written.
|
|
553
|
-
//
|
|
554
|
-
const bundles = [];
|
|
530
|
+
// Every name is checked before anything is resolved or written. The targets keep the order
|
|
531
|
+
// they were named in, which decides the origin of a member two bundles share.
|
|
555
532
|
const deprecated = [];
|
|
556
|
-
const
|
|
557
|
-
|
|
533
|
+
const emptyOrgs = [];
|
|
534
|
+
const orgs = [];
|
|
535
|
+
const targets = [];
|
|
536
|
+
const deprecationOf = (name, deprecation) => {
|
|
537
|
+
if (deprecation.deprecated && !force) {
|
|
538
|
+
deprecated.push(`"${name}" (${deprecation.reason}${deprecation.replacement ? `; use ${deprecation.replacement}` : ''})`);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
for (const spec of new Set(specs)) {
|
|
542
|
+
if (isBareOrgScope(spec)) {
|
|
543
|
+
const orgName = spec.slice(1);
|
|
544
|
+
const orgAssets = getAssetsByOrg(registry, orgName);
|
|
545
|
+
if (orgAssets.length === 0)
|
|
546
|
+
emptyOrgs.push(spec);
|
|
547
|
+
orgs.push({ count: orgAssets.length, org: orgName });
|
|
548
|
+
for (const { name, type } of orgAssets) {
|
|
549
|
+
targets.push({
|
|
550
|
+
fromOrg: true,
|
|
551
|
+
kind: 'asset',
|
|
552
|
+
name,
|
|
553
|
+
org: orgName,
|
|
554
|
+
spec: formatOrgName(orgName, name),
|
|
555
|
+
type,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
558
560
|
const { name: parsedName, version } = parseNameVersion(spec);
|
|
559
561
|
const { name: nameFromOrg, org: inlineOrg } = parseBundleSpec(parsedName);
|
|
560
562
|
const name = inlineOrg ? nameFromOrg : parsedName;
|
|
561
563
|
const org = resolveActiveOrg(inlineOrg ?? cliOrg, currentLockfile.org, config.org);
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
564
|
+
const indexBundle = findBundle(registry, name, inlineOrg);
|
|
565
|
+
if (indexBundle) {
|
|
566
|
+
const bundle = await bundleAtRequestedVersion(indexBundle, version, progress);
|
|
567
|
+
const earlier = targets.find((t) => t.kind === 'bundle' && t.bundle.name === name && t.bundle.org === indexBundle.org);
|
|
568
|
+
if (earlier) {
|
|
569
|
+
// A bundle installs at one version, as an asset does.
|
|
570
|
+
if (earlier.bundle.version !== bundle.version) {
|
|
571
|
+
throw new ResolverError(`Bundle "${formatOrgName(bundle.org, name)}" is named at two versions: ${earlier.bundle.version} ` +
|
|
572
|
+
`(${earlier.spec}) and ${bundle.version} (${spec}). A bundle installs at one version.`);
|
|
573
|
+
}
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
deprecationOf(name, isDeprecated(registry, name, undefined, indexBundle.org));
|
|
577
|
+
targets.push({ bundle, kind: 'bundle', spec, ...(version ? { version } : {}) });
|
|
567
578
|
continue;
|
|
568
579
|
}
|
|
569
|
-
const
|
|
570
|
-
if (
|
|
571
|
-
|
|
580
|
+
const asset = findAsset(registry, name, undefined, undefined, org)?.asset;
|
|
581
|
+
if (!asset && (registry.bundles ?? []).some((b) => b.name === name)) {
|
|
582
|
+
throw otherScopeBundleError(registry, name, inlineOrg);
|
|
572
583
|
}
|
|
573
|
-
|
|
584
|
+
deprecationOf(name, isDeprecated(registry, name, asset?.type, asset ? asset.org : inlineOrg));
|
|
585
|
+
targets.push({ kind: 'asset', name, ...(org ? { org } : {}), spec, ...(version ? { version } : {}) });
|
|
574
586
|
}
|
|
575
|
-
if (
|
|
576
|
-
throw new ResolverError(
|
|
577
|
-
? `${bundles[0].spec} is a bundle, which installs on its own.`
|
|
578
|
-
: `${bundles.map((b) => b.spec).join(', ')} are bundles, which install on their own.`, { command: installCommandFor(bundles[0].install, flags) });
|
|
587
|
+
if (emptyOrgs.length > 0) {
|
|
588
|
+
throw new ResolverError(`No assets found for ${emptyOrgs.join(', ')}. Nothing was installed.`);
|
|
579
589
|
}
|
|
580
590
|
if (deprecated.length > 0) {
|
|
581
591
|
throw new ResolverError(`Deprecated: ${deprecated.join(', ')}. Nothing was installed.`, {
|
|
@@ -584,10 +594,40 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
584
594
|
}
|
|
585
595
|
// A name every targeted tool already has in place is left out of the plan, as it is for one name.
|
|
586
596
|
const assets = [];
|
|
597
|
+
const bundles = [];
|
|
587
598
|
const upgrades = [];
|
|
588
599
|
const toInstall = [];
|
|
589
|
-
|
|
590
|
-
|
|
600
|
+
const adoptCommandFor = ({ bundle, version }) => installCommandFor(formatOrgName(bundle.org, bundle.name) + (version ? `@${version}` : ''), {
|
|
601
|
+
adopt: true,
|
|
602
|
+
force: false,
|
|
603
|
+
pin: false,
|
|
604
|
+
projectRoot,
|
|
605
|
+
tool,
|
|
606
|
+
user,
|
|
607
|
+
});
|
|
608
|
+
for (const request of targets) {
|
|
609
|
+
if (request.kind === 'bundle') {
|
|
610
|
+
const { bundle, version } = request;
|
|
611
|
+
const members = bundleMembers(registry, bundle, tools);
|
|
612
|
+
const requiredBundleVersion = version && !isLatestAlias(version) ? bundle.version : undefined;
|
|
613
|
+
if (force ||
|
|
614
|
+
!isBundleInPlace(registry, bundle, currentLockfile, { members, projectDir, requiredBundleVersion, tools })) {
|
|
615
|
+
toInstall.push(request);
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
bundles.push({
|
|
619
|
+
name: bundle.name,
|
|
620
|
+
...(bundle.org ? { org: bundle.org } : {}),
|
|
621
|
+
outcome: 'already-installed',
|
|
622
|
+
version: bundle.version,
|
|
623
|
+
...bundleReport(membersOutsideBundle(currentLockfile, members, bundle), [], {
|
|
624
|
+
adoptCommand: adoptCommandFor(request),
|
|
625
|
+
bundle,
|
|
626
|
+
}),
|
|
627
|
+
});
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const match = findAsset(registry, request.name, request.type, request.version, request.org);
|
|
591
631
|
const existing = match
|
|
592
632
|
? findInstalledAsset(currentLockfile, request.name, match.asset.type, match.asset.org)
|
|
593
633
|
: undefined;
|
|
@@ -615,9 +655,25 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
615
655
|
});
|
|
616
656
|
}
|
|
617
657
|
progress('Resolving dependencies...');
|
|
618
|
-
const plan =
|
|
658
|
+
const plan = resolveTargetsInstallPlan(registry, toInstall.map((request) => request.kind === 'bundle'
|
|
659
|
+
? { bundle: request.bundle, kind: 'bundle' }
|
|
660
|
+
: {
|
|
661
|
+
...(request.fromOrg ? { fromOrg: true } : {}),
|
|
662
|
+
kind: 'asset',
|
|
663
|
+
name: request.name,
|
|
664
|
+
...(request.org ? { org: request.org } : {}),
|
|
665
|
+
...(request.type ? { type: request.type } : {}),
|
|
666
|
+
...(request.version ? { version: request.version } : {}),
|
|
667
|
+
}));
|
|
668
|
+
// The named and the org's assets the plan installs, in plan order.
|
|
669
|
+
const requested = new Set(toInstall.flatMap((request) => {
|
|
670
|
+
if (request.kind === 'bundle')
|
|
671
|
+
return [];
|
|
672
|
+
const match = findAsset(registry, request.name, request.type, request.version, request.org);
|
|
673
|
+
return match ? [identityKey(match.asset)] : [];
|
|
674
|
+
}));
|
|
619
675
|
const installed = plan.entries
|
|
620
|
-
.filter((entry) => entry.reason === 'direct')
|
|
676
|
+
.filter((entry) => entry.reason === 'direct' && requested.has(identityKey(entry.asset)))
|
|
621
677
|
.map((entry) => ({
|
|
622
678
|
name: entry.asset.name,
|
|
623
679
|
...(entry.asset.org ? { org: entry.asset.org } : {}),
|
|
@@ -627,10 +683,36 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
627
683
|
}));
|
|
628
684
|
const report = [...installed, ...assets];
|
|
629
685
|
const crossScope = (resolvedPlan, installedAt) => crossScopeOf(resolvedPlan, { installedAt, json: effectiveJson, scope: installScope, tools: toolNames });
|
|
686
|
+
// Each bundle in the plan, in the order named, then the ones left out; and the orgs.
|
|
687
|
+
const targetsField = (lockfile, planned) => {
|
|
688
|
+
const installedBundles = toInstall.flatMap((request) => {
|
|
689
|
+
if (request.kind !== 'bundle')
|
|
690
|
+
return [];
|
|
691
|
+
const { bundle } = request;
|
|
692
|
+
const members = bundleMembers(registry, bundle, tools);
|
|
693
|
+
// A dry run writes nothing, so the members it leaves outside the bundle are read from the plan.
|
|
694
|
+
const kept = [
|
|
695
|
+
...membersOutsideBundle(lockfile, members, bundle),
|
|
696
|
+
...(planned ? plannedOutsideBundle(plan, lockfile, members, bundle) : []),
|
|
697
|
+
];
|
|
698
|
+
return [
|
|
699
|
+
{
|
|
700
|
+
name: bundle.name,
|
|
701
|
+
...(bundle.org ? { org: bundle.org } : {}),
|
|
702
|
+
outcome: 'installed',
|
|
703
|
+
version: bundle.version,
|
|
704
|
+
...bundleReport(kept, [], { adoptCommand: adoptCommandFor(request), bundle }),
|
|
705
|
+
},
|
|
706
|
+
];
|
|
707
|
+
});
|
|
708
|
+
const named = [...installedBundles, ...bundles];
|
|
709
|
+
return { ...(named.length > 0 ? { bundles: named } : {}), ...(orgs.length > 0 ? { orgs } : {}) };
|
|
710
|
+
};
|
|
630
711
|
// A dry run says so even when every name is in place, and writes nothing (not even an upgrade).
|
|
631
712
|
if (dryRun) {
|
|
632
713
|
return {
|
|
633
714
|
assets: report,
|
|
715
|
+
...targetsField(currentLockfile, true),
|
|
634
716
|
outcome: 'dry-run-install',
|
|
635
717
|
plan: withoutKeptDependencies(plan, currentLockfile, tools, {
|
|
636
718
|
onProgress: progress,
|
|
@@ -659,11 +741,18 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
659
741
|
lockfile = upgradeToDirectInstall(lockfile, name, type, org);
|
|
660
742
|
await writeLockfile(projectDir, lockfile);
|
|
661
743
|
}
|
|
662
|
-
if (plan.entries.length === 0)
|
|
663
|
-
return { assets: report, outcome: 'already-installed' };
|
|
664
|
-
|
|
744
|
+
if (plan.entries.length === 0) {
|
|
745
|
+
return { assets: report, ...targetsField(currentLockfile, false), outcome: 'already-installed' };
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
assets: report,
|
|
749
|
+
...targetsField(await readLockfile(projectDir), false),
|
|
750
|
+
outcome: 'installed',
|
|
751
|
+
toolResults,
|
|
752
|
+
...(await crossScope(plan, projectDir)),
|
|
753
|
+
};
|
|
665
754
|
});
|
|
666
|
-
}, [adopt, cliOrg, dryRun, force, pin, specs.join(' '), tool]);
|
|
755
|
+
}, [adopt, all, cliOrg, dryRun, force, pin, specs.join(' '), tool]);
|
|
667
756
|
if (state.status === 'loading') {
|
|
668
757
|
return _jsx(Spinner, { message: state.message });
|
|
669
758
|
}
|
|
@@ -686,10 +775,10 @@ function InstallAssets({ adopt, dryRun, force, json, org: cliOrg, pin, projectRo
|
|
|
686
775
|
return renderJson({ ...data, ...fields });
|
|
687
776
|
}
|
|
688
777
|
if (data.outcome === 'dry-run-install') {
|
|
689
|
-
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: data.plan.totalCount, tools: data.tools, where: label }), _jsx(Text, { children: " " }), _jsx(PlanEntryLines, { plan: data.plan }), _jsx(NamedAssetLines, { assets: data.assets, dryRun: true, where: label }), _jsx(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
778
|
+
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: data.plan.totalCount, tools: data.tools, where: label }), _jsx(Text, { children: " " }), _jsx(PlanEntryLines, { plan: data.plan }), _jsx(NamedAssetLines, { assets: data.assets, dryRun: true, where: label }), _jsx(TargetLines, { bundles: data.bundles, dryRun: true, orgs: data.orgs, where: label }), _jsx(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
690
779
|
}
|
|
691
780
|
return (_jsxs(Box, { flexDirection: 'column', children: [data.outcome === 'installed' &&
|
|
692
|
-
data.toolResults.map(({ results, skipped, tool: toolName, warnings }) => (_jsx(InstallSummary, { results: results, skipped: skipped, tools: [toolName], warnings: warnings, where: label }, toolName))), _jsx(NamedAssetLines, { assets: data.assets, where: label }), data.outcome === 'installed' && _jsx(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
781
|
+
data.toolResults.map(({ results, skipped, tool: toolName, warnings }) => (_jsx(InstallSummary, { results: results, skipped: skipped, tools: [toolName], warnings: warnings, where: label }, toolName))), _jsx(NamedAssetLines, { assets: data.assets, where: label }), _jsx(TargetLines, { bundles: data.bundles, orgs: data.orgs, where: label }), data.outcome === 'installed' && _jsx(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
693
782
|
}
|
|
694
783
|
/** `atk install <spec>` with the run's own flags, for a command an error suggests. */
|
|
695
784
|
function installCommandFor(spec, { adopt, dryRun, force, pin, projectRoot, tool, user }) {
|
|
@@ -953,6 +1042,31 @@ function InstallOrg({ cliOrg, dryRun, force, json, orgName, pin, projectRoot, sk
|
|
|
953
1042
|
}
|
|
954
1043
|
return null;
|
|
955
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Whether every member of the bundle is in place for every tool that takes it (with
|
|
1047
|
+
* `requiredBundleVersion`, recorded as this bundle at that version). False when no tool takes any
|
|
1048
|
+
* member, so the installer says why.
|
|
1049
|
+
*/
|
|
1050
|
+
function isBundleInPlace(registry, bundle, lockfile, { members, projectDir, requiredBundleVersion, tools, }) {
|
|
1051
|
+
const bundleAssets = bundle.assets ?? [];
|
|
1052
|
+
return tools.every(({ adapter: toolAdapter }) => members.length > 0 &&
|
|
1053
|
+
bundleAssets.every((ref) => {
|
|
1054
|
+
const found = findBundleMember(registry, bundle, ref);
|
|
1055
|
+
if (!found)
|
|
1056
|
+
return false;
|
|
1057
|
+
// A tool that doesn't take this member (its type, or a hook for other tools) has
|
|
1058
|
+
// nothing to check.
|
|
1059
|
+
if (!placesAsset(toolAdapter, found.asset.type, found.versionData.tools))
|
|
1060
|
+
return true;
|
|
1061
|
+
const installed = findInstalledAsset(lockfile, ref.name, ref.type, found.asset.org);
|
|
1062
|
+
if (!installed)
|
|
1063
|
+
return false;
|
|
1064
|
+
if (requiredBundleVersion && !isFromBundleVersion(installed, bundle, requiredBundleVersion)) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
return isInstalledForTool(installed, toolAdapter, projectDir);
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
956
1070
|
/** Whether a lockfile asset was installed as part of this bundle (name, org) at this version. */
|
|
957
1071
|
function isFromBundleVersion(installed, bundle, version) {
|
|
958
1072
|
const origin = installed.bundleOrigin;
|
|
@@ -986,8 +1100,46 @@ function NamedAssetLines({ assets, dryRun = false, where, }) {
|
|
|
986
1100
|
? `${dryRun ? 'would become' : 'is now'} a direct install${inScope}.`
|
|
987
1101
|
: `is already installed${inScope}.`] }, identityKey(a)))) }));
|
|
988
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* The error for a bundle name that only another scope has, when no asset can claim the name
|
|
1105
|
+
* either, instead of a misleading "asset not found".
|
|
1106
|
+
*/
|
|
1107
|
+
function otherScopeBundleError(registry, name, inlineOrg) {
|
|
1108
|
+
if (inlineOrg) {
|
|
1109
|
+
return new Error(`Bundle '${name}' not found in org '${inlineOrg}'.`);
|
|
1110
|
+
}
|
|
1111
|
+
const orgScopes = (registry.bundles ?? []).filter((b) => b.name === name && b.org).map((b) => `@${b.org}/${name}`);
|
|
1112
|
+
return new Error(`Bundle '${name}' not found in the registry.` +
|
|
1113
|
+
(orgScopes.length > 0 ? ` Did you mean ${orgScopes.join(' or ')}?` : ''));
|
|
1114
|
+
}
|
|
989
1115
|
/** A plan's entries, each with its type and version, its dependencies marked. */
|
|
990
1116
|
function PlanEntryLines({ plan }) {
|
|
991
1117
|
return (_jsx(_Fragment, { children: plan.entries.map((entry) => (_jsxs(Text, { children: [' ', entry.reason === 'dependency' ? (_jsxs(Text, { color: 'cyan', children: [formatOrgName(entry.asset.org, entry.asset.name), ' ', _jsxs(Text, { dimColor: true, children: ["(", entry.asset.type, ") v", entry.version, " \u2014 dependency"] })] })) : (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: formatOrgName(entry.asset.org, entry.asset.name) }), _jsxs(Text, { dimColor: true, children: [' ', "(", entry.asset.type, ") v", entry.version] })] }))] }, identityKey(entry.asset)))) }));
|
|
992
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* The members not installed yet that the plan installs without the bundle's origin: named on their
|
|
1121
|
+
* own, or with another bundle named before it. A dry run reports them as an install would.
|
|
1122
|
+
*/
|
|
1123
|
+
function plannedOutsideBundle(plan, lockfile, members, bundle) {
|
|
1124
|
+
return members.flatMap((member) => {
|
|
1125
|
+
if (findInstalledAsset(lockfile, member.name, member.type, member.org))
|
|
1126
|
+
return [];
|
|
1127
|
+
const entry = plan.entries.find((e) => identityKey(e.asset) === identityKey(member));
|
|
1128
|
+
const origin = entry?.bundleOrigin;
|
|
1129
|
+
if (!entry || (origin?.name === bundle.name && origin.org === bundle.org))
|
|
1130
|
+
return [];
|
|
1131
|
+
return [{ ...(origin ? { bundle: origin } : {}), ...member }];
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* The bundles and orgs a run of several names had among them: what it did with each bundle, the
|
|
1136
|
+
* bundle's members outside it, and how many assets each org brought.
|
|
1137
|
+
*/
|
|
1138
|
+
function TargetLines({ bundles = [], dryRun = false, orgs = [], where, }) {
|
|
1139
|
+
const inScope = where ? ` in ${where}` : '';
|
|
1140
|
+
return (_jsxs(_Fragment, { children: [bundles.map((bundle) => {
|
|
1141
|
+
const spec = formatOrgName(bundle.org, bundle.name);
|
|
1142
|
+
return (_jsxs(Fragment, { children: [bundle.outcome === 'already-installed' ? (_jsxs(Text, { children: [_jsx(Text, { color: 'yellow', children: "!" }), " Bundle ", _jsx(Text, { bold: true, children: spec }), " is already fully installed (v", bundle.version, ")", inScope, "."] })) : (_jsxs(Text, { children: [_jsx(Text, { color: 'green', children: "\u2713" }), " Bundle ", _jsx(Text, { bold: true, children: spec }), " v", bundle.version, ' ', dryRun ? 'would be installed' : 'installed', inScope, "."] })), _jsx(BundleReportLines, { bundleName: spec, dryRun: dryRun, report: bundle })] }, spec));
|
|
1143
|
+
}), orgs.map(({ count, org }) => (_jsxs(Text, { children: [_jsxs(Text, { bold: true, color: 'magenta', children: ["Org: @", org] }), _jsxs(Text, { dimColor: true, children: [' ', "\u2014 all ", count, " asset", count !== 1 ? 's' : ''] })] }, org)))] }));
|
|
1144
|
+
}
|
|
993
1145
|
//# sourceMappingURL=install.js.map
|