@detergent-software/atk 9.2.0-dev.4 → 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 +29 -0
- package/README.md +10 -10
- package/build/command-manifest.json +18 -1
- package/build/commands/install.d.ts +2 -2
- package/build/commands/install.d.ts.map +1 -1
- package/build/commands/install.js +383 -39
- package/build/commands/install.js.map +1 -1
- package/build/commands/uninstall.d.ts +2 -2
- package/build/commands/uninstall.d.ts.map +1 -1
- package/build/commands/uninstall.js +35 -11
- package/build/commands/uninstall.js.map +1 -1
- package/build/commands/update.d.ts +3 -2
- package/build/commands/update.d.ts.map +1 -1
- package/build/commands/update.js +270 -74
- package/build/commands/update.js.map +1 -1
- package/build/hooks/useUninstallState.d.ts +22 -2
- package/build/hooks/useUninstallState.d.ts.map +1 -1
- package/build/hooks/useUninstallState.js +161 -16
- 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 +51 -3
- package/build/lib/uninstaller.d.ts.map +1 -1
- package/build/lib/uninstaller.js +134 -39
- package/build/lib/uninstaller.js.map +1 -1
- package/build/lib/updater.d.ts +3 -2
- package/build/lib/updater.d.ts.map +1 -1
- package/build/lib/updater.js +16 -3
- package/build/lib/updater.js.map +1 -1
- package/package.json +1 -1
|
@@ -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()
|
|
@@ -108,7 +109,8 @@ export const options = z.object({
|
|
|
108
109
|
description: 'Alias for --all: install every asset from an org scope without prompting',
|
|
109
110
|
})),
|
|
110
111
|
});
|
|
111
|
-
export const args = z
|
|
112
|
+
export const args = z
|
|
113
|
+
.tuple([
|
|
112
114
|
z
|
|
113
115
|
.string()
|
|
114
116
|
.optional()
|
|
@@ -116,12 +118,19 @@ export const args = z.tuple([
|
|
|
116
118
|
description: 'Asset, bundle, or org scope to install (supports name@version and @org)',
|
|
117
119
|
name: 'target',
|
|
118
120
|
})),
|
|
119
|
-
])
|
|
120
|
-
|
|
121
|
+
])
|
|
122
|
+
.rest(z.string().describe(argument({
|
|
123
|
+
description: 'More assets, bundles or org scopes to install with the first, as one plan (name@version, @org/name, @org with --all)',
|
|
124
|
+
name: 'targets',
|
|
125
|
+
})));
|
|
126
|
+
export default function Install({ args: [rawName, ...more], options: { adopt, all, dryRun, force, json, org, pin, projectRoot, refresh, tool, user, yes }, }) {
|
|
121
127
|
// No argument provided — delegate to sync behavior
|
|
122
128
|
if (!rawName) {
|
|
123
129
|
return _jsx(Sync, { options: { dryRun, force, json, projectRoot, refresh, tool, user } });
|
|
124
130
|
}
|
|
131
|
+
if (more.length > 0) {
|
|
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 }));
|
|
133
|
+
}
|
|
125
134
|
if (isBareOrgScope(rawName)) {
|
|
126
135
|
const orgName = rawName.slice(1);
|
|
127
136
|
return (_jsx(InstallOrg, { cliOrg: org, dryRun: dryRun, force: force, json: json, orgName: orgName, pin: pin, projectRoot: projectRoot, skipPicker: all || yes, tool: tool, user: user }));
|
|
@@ -265,16 +274,8 @@ function InstallAsset({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoo
|
|
|
265
274
|
// through to a misleading "asset not found".
|
|
266
275
|
if (!indexBundle && (registry.bundles ?? []).some((b) => b.name === name)) {
|
|
267
276
|
const assetExists = findAsset(registry, name, undefined, undefined, org);
|
|
268
|
-
if (!assetExists)
|
|
269
|
-
|
|
270
|
-
throw new Error(`Bundle '${name}' not found in org '${inlineOrg}'.`);
|
|
271
|
-
}
|
|
272
|
-
const orgScopes = (registry.bundles ?? [])
|
|
273
|
-
.filter((b) => b.name === name && b.org)
|
|
274
|
-
.map((b) => `@${b.org}/${name}`);
|
|
275
|
-
throw new Error(`Bundle '${name}' not found in the registry.` +
|
|
276
|
-
(orgScopes.length > 0 ? ` Did you mean ${orgScopes.join(' or ')}?` : ''));
|
|
277
|
-
}
|
|
277
|
+
if (!assetExists)
|
|
278
|
+
throw otherScopeBundleError(registry, name, inlineOrg);
|
|
278
279
|
}
|
|
279
280
|
// With no active org, `info` opens the only org asset of a bare name, but install doesn't
|
|
280
281
|
// switch scopes on its own: it names the spec that installs that asset.
|
|
@@ -352,26 +353,7 @@ function InstallAsset({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoo
|
|
|
352
353
|
};
|
|
353
354
|
// Bundle installation flow — check if already installed for ALL tools
|
|
354
355
|
if (!force) {
|
|
355
|
-
|
|
356
|
-
// When no configured tool takes any member, fall through so the installer says why.
|
|
357
|
-
const allInstalledForAllTools = tools.every(({ adapter: toolAdapter }) => members.length > 0 &&
|
|
358
|
-
bundleAssets.every((ref) => {
|
|
359
|
-
const found = findBundleMember(registry, bundle, ref);
|
|
360
|
-
if (!found)
|
|
361
|
-
return false;
|
|
362
|
-
// A tool that doesn't take this member (its type, or a hook for other tools) has
|
|
363
|
-
// nothing to check.
|
|
364
|
-
if (!placesAsset(toolAdapter, found.asset.type, found.versionData.tools))
|
|
365
|
-
return true;
|
|
366
|
-
const installed = findInstalledAsset(before, ref.name, ref.type, found.asset.org);
|
|
367
|
-
if (!installed)
|
|
368
|
-
return false;
|
|
369
|
-
if (requiredBundleVersion && !isFromBundleVersion(installed, bundle, requiredBundleVersion)) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
return isInstalledForTool(installed, toolAdapter, projectDir);
|
|
373
|
-
}));
|
|
374
|
-
if (allInstalledForAllTools) {
|
|
356
|
+
if (isBundleInPlace(registry, bundle, before, { members, projectDir, requiredBundleVersion, tools })) {
|
|
375
357
|
// Adopting a bundle that is in place only records its origin on the members with none:
|
|
376
358
|
// their files, versions and pins stay as they are. Another bundle's member stays its.
|
|
377
359
|
const outside = membersOutsideBundle(before, members, bundle);
|
|
@@ -502,16 +484,302 @@ function InstallAsset({ adopt, dryRun, force, json, org: cliOrg, pin, projectRoo
|
|
|
502
484
|
return (_jsxs(Box, { flexDirection: 'column', children: [_jsxs(Text, { color: 'green', children: ["\u2713 ", _jsx(Text, { bold: true, children: data.name }), " (v", data.version, ") is now a direct install", where, "."] }), _jsx(Text, { dimColor: true, children: "It will no longer be auto-removed when its parent is uninstalled." })] }));
|
|
503
485
|
}
|
|
504
486
|
if (data.outcome === 'dry-run-install') {
|
|
505
|
-
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: data.plan.totalCount, tools: data.tools, where: label }), _jsx(Text, { children: " " }),
|
|
487
|
+
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(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
506
488
|
}
|
|
507
489
|
if (data.outcome === 'dry-run-bundle') {
|
|
508
|
-
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: data.plan.totalCount, tools: data.tools, where: label }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsxs(Text, { bold: true, color: 'magenta', children: ["Bundle: ", data.bundleName] }), _jsxs(Text, { dimColor: true, children: [" v", data.version] })] }), _jsx(Text, { children: " " }),
|
|
490
|
+
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: data.plan.totalCount, tools: data.tools, where: label }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsxs(Text, { bold: true, color: 'magenta', children: ["Bundle: ", data.bundleName] }), _jsxs(Text, { dimColor: true, children: [" v", data.version] })] }), _jsx(Text, { children: " " }), _jsx(PlanEntryLines, { plan: data.plan }), _jsx(BundleReportLines, { bundleName: data.bundleName, dryRun: true, report: data }), _jsx(CrossScopeWarning, { installs: data.crossScope ?? [] })] }));
|
|
509
491
|
}
|
|
510
492
|
if (data.outcome === 'deprecated') {
|
|
511
493
|
return (_jsxs(Box, { flexDirection: 'column', children: [_jsxs(Text, { color: 'yellow', children: ["Warning: \"", data.name, "\" is deprecated \u2014 ", data.reason] }), data.replacement && (_jsxs(Text, { children: ["Consider using", ' ', _jsx(Text, { bold: true, color: 'cyan', children: data.replacement }), ' ', "instead."] })), _jsx(Text, { dimColor: true, children: "Use --force to install anyway." })] }));
|
|
512
494
|
}
|
|
513
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 ?? [] })] }));
|
|
514
496
|
}
|
|
497
|
+
/**
|
|
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}).
|
|
505
|
+
*/
|
|
506
|
+
function InstallAssets({ adopt, all, dryRun, force, json, org: cliOrg, pin, projectRoot, specs, tool, user, }) {
|
|
507
|
+
const { json: effectiveJson } = useNonInteractive(json);
|
|
508
|
+
const { fields, resolve, resolved } = useResolvedScope({ projectRoot, user });
|
|
509
|
+
const state = useCommand(async (progress) => {
|
|
510
|
+
const flags = { dryRun, force, pin, projectRoot, tool, user };
|
|
511
|
+
if (adopt) {
|
|
512
|
+
throw new Error('--adopt records a bundle as the owner of its members, so it works with one bundle only.');
|
|
513
|
+
}
|
|
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) });
|
|
521
|
+
}
|
|
522
|
+
const { root: projectDir, scope: installScope } = await resolve();
|
|
523
|
+
progress('Loading configuration...');
|
|
524
|
+
const config = await loadConfig();
|
|
525
|
+
const tools = await resolvePlacingTools(tool, projectDir);
|
|
526
|
+
const toolNames = tools.map((t) => t.tool);
|
|
527
|
+
progress('Fetching registry...');
|
|
528
|
+
const registry = await fetchRegistry({ force: true });
|
|
529
|
+
const currentLockfile = await readLockfile(projectDir);
|
|
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.
|
|
532
|
+
const deprecated = [];
|
|
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
|
+
}
|
|
560
|
+
const { name: parsedName, version } = parseNameVersion(spec);
|
|
561
|
+
const { name: nameFromOrg, org: inlineOrg } = parseBundleSpec(parsedName);
|
|
562
|
+
const name = inlineOrg ? nameFromOrg : parsedName;
|
|
563
|
+
const org = resolveActiveOrg(inlineOrg ?? cliOrg, currentLockfile.org, config.org);
|
|
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 } : {}) });
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
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);
|
|
583
|
+
}
|
|
584
|
+
deprecationOf(name, isDeprecated(registry, name, asset?.type, asset ? asset.org : inlineOrg));
|
|
585
|
+
targets.push({ kind: 'asset', name, ...(org ? { org } : {}), spec, ...(version ? { version } : {}) });
|
|
586
|
+
}
|
|
587
|
+
if (emptyOrgs.length > 0) {
|
|
588
|
+
throw new ResolverError(`No assets found for ${emptyOrgs.join(', ')}. Nothing was installed.`);
|
|
589
|
+
}
|
|
590
|
+
if (deprecated.length > 0) {
|
|
591
|
+
throw new ResolverError(`Deprecated: ${deprecated.join(', ')}. Nothing was installed.`, {
|
|
592
|
+
hint: 'Use --force to install them anyway.',
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
// A name every targeted tool already has in place is left out of the plan, as it is for one name.
|
|
596
|
+
const assets = [];
|
|
597
|
+
const bundles = [];
|
|
598
|
+
const upgrades = [];
|
|
599
|
+
const toInstall = [];
|
|
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);
|
|
631
|
+
const existing = match
|
|
632
|
+
? findInstalledAsset(currentLockfile, request.name, match.asset.type, match.asset.org)
|
|
633
|
+
: undefined;
|
|
634
|
+
const supportingTools = existing && match ? tools.filter((t) => placesAsset(t.adapter, existing.type, match.versionData.tools)) : [];
|
|
635
|
+
const inPlace = !force &&
|
|
636
|
+
existing !== undefined &&
|
|
637
|
+
supportingTools.length > 0 &&
|
|
638
|
+
supportingTools.every((t) => isInstalledForTool(existing, t.adapter, projectDir)) &&
|
|
639
|
+
(!request.version || request.version === existing.version);
|
|
640
|
+
if (!inPlace) {
|
|
641
|
+
toInstall.push(request);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (assets.some((a) => identityKey(a) === identityKey(existing)))
|
|
645
|
+
continue;
|
|
646
|
+
const dependency = (existing.installReason ?? 'direct') === 'dependency';
|
|
647
|
+
if (dependency)
|
|
648
|
+
upgrades.push(existing);
|
|
649
|
+
assets.push({
|
|
650
|
+
name: existing.name,
|
|
651
|
+
...(existing.org ? { org: existing.org } : {}),
|
|
652
|
+
outcome: dependency ? 'upgraded-to-direct' : 'already-installed',
|
|
653
|
+
type: existing.type,
|
|
654
|
+
version: existing.version,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
progress('Resolving dependencies...');
|
|
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
|
+
}));
|
|
675
|
+
const installed = plan.entries
|
|
676
|
+
.filter((entry) => entry.reason === 'direct' && requested.has(identityKey(entry.asset)))
|
|
677
|
+
.map((entry) => ({
|
|
678
|
+
name: entry.asset.name,
|
|
679
|
+
...(entry.asset.org ? { org: entry.asset.org } : {}),
|
|
680
|
+
outcome: 'installed',
|
|
681
|
+
type: entry.asset.type,
|
|
682
|
+
version: entry.version,
|
|
683
|
+
}));
|
|
684
|
+
const report = [...installed, ...assets];
|
|
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
|
+
};
|
|
711
|
+
// A dry run says so even when every name is in place, and writes nothing (not even an upgrade).
|
|
712
|
+
if (dryRun) {
|
|
713
|
+
return {
|
|
714
|
+
assets: report,
|
|
715
|
+
...targetsField(currentLockfile, true),
|
|
716
|
+
outcome: 'dry-run-install',
|
|
717
|
+
plan: withoutKeptDependencies(plan, currentLockfile, tools, {
|
|
718
|
+
onProgress: progress,
|
|
719
|
+
projectDir,
|
|
720
|
+
scope: installScope,
|
|
721
|
+
}),
|
|
722
|
+
tools: toolNames,
|
|
723
|
+
...(await crossScope(plan)),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
return await withLockfileErrorHandling(projectDir, async () => {
|
|
727
|
+
const toolResults = plan.entries.length > 0
|
|
728
|
+
? await executeInstallPlanForTools(plan, tools, {
|
|
729
|
+
force,
|
|
730
|
+
onProgress: progress,
|
|
731
|
+
onTool: (toolName) => progress(`Installing for ${toolName}...`),
|
|
732
|
+
pin,
|
|
733
|
+
projectDir,
|
|
734
|
+
scope: installScope,
|
|
735
|
+
})
|
|
736
|
+
: [];
|
|
737
|
+
// After the install, so a failed one leaves even the install reasons as they were.
|
|
738
|
+
if (upgrades.length > 0) {
|
|
739
|
+
let lockfile = await readLockfile(projectDir);
|
|
740
|
+
for (const { name, org, type } of upgrades)
|
|
741
|
+
lockfile = upgradeToDirectInstall(lockfile, name, type, org);
|
|
742
|
+
await writeLockfile(projectDir, lockfile);
|
|
743
|
+
}
|
|
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
|
+
};
|
|
754
|
+
});
|
|
755
|
+
}, [adopt, all, cliOrg, dryRun, force, pin, specs.join(' '), tool]);
|
|
756
|
+
if (state.status === 'loading') {
|
|
757
|
+
return _jsx(Spinner, { message: state.message });
|
|
758
|
+
}
|
|
759
|
+
if (state.status === 'error') {
|
|
760
|
+
if (effectiveJson) {
|
|
761
|
+
return renderJson({
|
|
762
|
+
...(state.code ? { code: state.code } : {}),
|
|
763
|
+
...(state.command ? { command: state.command } : {}),
|
|
764
|
+
...(state.hint ? { hint: state.hint } : {}),
|
|
765
|
+
message: state.message,
|
|
766
|
+
outcome: 'error',
|
|
767
|
+
...fields,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
return _jsx(ErrorView, { command: state.command, hint: state.hint, message: state.message });
|
|
771
|
+
}
|
|
772
|
+
const { data } = state;
|
|
773
|
+
const label = resolved && scopeLabel(resolved);
|
|
774
|
+
if (effectiveJson) {
|
|
775
|
+
return renderJson({ ...data, ...fields });
|
|
776
|
+
}
|
|
777
|
+
if (data.outcome === 'dry-run-install') {
|
|
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 ?? [] })] }));
|
|
779
|
+
}
|
|
780
|
+
return (_jsxs(Box, { flexDirection: 'column', children: [data.outcome === 'installed' &&
|
|
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 ?? [] })] }));
|
|
782
|
+
}
|
|
515
783
|
/** `atk install <spec>` with the run's own flags, for a command an error suggests. */
|
|
516
784
|
function installCommandFor(spec, { adopt, dryRun, force, pin, projectRoot, tool, user }) {
|
|
517
785
|
return [
|
|
@@ -758,7 +1026,7 @@ function InstallOrg({ cliOrg, dryRun, force, json, orgName, pin, projectRoot, sk
|
|
|
758
1026
|
...fields,
|
|
759
1027
|
});
|
|
760
1028
|
}
|
|
761
|
-
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: state.plan.totalCount, tools: state.tools, where: label }), _jsx(Text, { children: " " }), _jsx(Text, { children: _jsxs(Text, { bold: true, color: 'magenta', children: ["Org: @", orgName] }) }), _jsx(Text, { children: " " }),
|
|
1029
|
+
return (_jsxs(Box, { flexDirection: 'column', children: [_jsx(DryRunBanner, { action: 'install', count: state.plan.totalCount, tools: state.tools, where: label }), _jsx(Text, { children: " " }), _jsx(Text, { children: _jsxs(Text, { bold: true, color: 'magenta', children: ["Org: @", orgName] }) }), _jsx(Text, { children: " " }), _jsx(PlanEntryLines, { plan: state.plan }), _jsx(CrossScopeWarning, { installs: state.crossScope ?? [] })] }));
|
|
762
1030
|
}
|
|
763
1031
|
if (state.phase === 'done') {
|
|
764
1032
|
if (effectiveJson) {
|
|
@@ -774,6 +1042,31 @@ function InstallOrg({ cliOrg, dryRun, force, json, orgName, pin, projectRoot, sk
|
|
|
774
1042
|
}
|
|
775
1043
|
return null;
|
|
776
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
|
+
}
|
|
777
1070
|
/** Whether a lockfile asset was installed as part of this bundle (name, org) at this version. */
|
|
778
1071
|
function isFromBundleVersion(installed, bundle, version) {
|
|
779
1072
|
const origin = installed.bundleOrigin;
|
|
@@ -798,4 +1091,55 @@ function membersOutsideBundle(lockfile, members, bundle) {
|
|
|
798
1091
|
return [{ ...(origin ? { bundle: origin } : {}), ...member }];
|
|
799
1092
|
});
|
|
800
1093
|
}
|
|
1094
|
+
/** The names a run of several left out of its plan: already installed, or made a direct install. */
|
|
1095
|
+
function NamedAssetLines({ assets, dryRun = false, where, }) {
|
|
1096
|
+
const inScope = where ? ` in ${where}` : '';
|
|
1097
|
+
return (_jsx(_Fragment, { children: assets
|
|
1098
|
+
.filter((a) => a.outcome !== 'installed')
|
|
1099
|
+
.map((a) => (_jsxs(Text, { children: [a.outcome === 'upgraded-to-direct' ? _jsx(Text, { color: 'green', children: "\u2713" }) : _jsx(Text, { color: 'yellow', children: "!" }), ' ', _jsx(Text, { bold: true, children: formatOrgName(a.org, a.name) }), " (v", a.version, ")", ' ', a.outcome === 'upgraded-to-direct'
|
|
1100
|
+
? `${dryRun ? 'would become' : 'is now'} a direct install${inScope}.`
|
|
1101
|
+
: `is already installed${inScope}.`] }, identityKey(a)))) }));
|
|
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
|
+
}
|
|
1115
|
+
/** A plan's entries, each with its type and version, its dependencies marked. */
|
|
1116
|
+
function PlanEntryLines({ plan }) {
|
|
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)))) }));
|
|
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
|
+
}
|
|
801
1145
|
//# sourceMappingURL=install.js.map
|