opkg 0.5.0 → 0.6.0
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/README.md +49 -8
- package/dist/commands/add.js +11 -276
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/init.js +73 -145
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.js +26 -668
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/pack.js +10 -137
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/push.js +14 -8
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/save.js +12 -167
- package/dist/commands/save.js.map +1 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/uninstall.js +5 -5
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/constants/index.js +18 -45
- package/dist/constants/index.js.map +1 -1
- package/dist/constants/workspace.js +9 -0
- package/dist/constants/workspace.js.map +1 -0
- package/dist/core/add/add-conflict-handler.js +68 -0
- package/dist/core/add/add-conflict-handler.js.map +1 -0
- package/dist/core/add/add-pipeline.js +137 -0
- package/dist/core/add/add-pipeline.js.map +1 -0
- package/dist/core/add/package-index-updater.js +62 -33
- package/dist/core/add/package-index-updater.js.map +1 -1
- package/dist/core/add/platform-path-transformer.js +47 -0
- package/dist/core/add/platform-path-transformer.js.map +1 -0
- package/dist/core/add/source-collector.js +57 -0
- package/dist/core/add/source-collector.js.map +1 -0
- package/dist/core/dependency-resolver.js +3 -1
- package/dist/core/dependency-resolver.js.map +1 -1
- package/dist/core/directory.js +2 -2
- package/dist/core/directory.js.map +1 -1
- package/dist/core/discovery/file-discovery.js +55 -54
- package/dist/core/discovery/file-discovery.js.map +1 -1
- package/dist/core/discovery/platform-files-discovery.js +32 -17
- package/dist/core/discovery/platform-files-discovery.js.map +1 -1
- package/dist/core/install/bulk-install-pipeline.js +199 -0
- package/dist/core/install/bulk-install-pipeline.js.map +1 -0
- package/dist/core/install/canonical-plan.js +123 -0
- package/dist/core/install/canonical-plan.js.map +1 -0
- package/dist/core/install/dry-run.js +2 -2
- package/dist/core/install/dry-run.js.map +1 -1
- package/dist/core/install/index.js +3 -0
- package/dist/core/install/index.js.map +1 -0
- package/dist/core/install/install-errors.js +41 -0
- package/dist/core/install/install-errors.js.map +1 -0
- package/dist/core/install/install-flow.js +2 -5
- package/dist/core/install/install-flow.js.map +1 -1
- package/dist/core/install/install-pipeline.js +228 -0
- package/dist/core/install/install-pipeline.js.map +1 -0
- package/dist/core/install/install-reporting.js +99 -0
- package/dist/core/install/install-reporting.js.map +1 -0
- package/dist/core/install/platform-resolution.js +6 -6
- package/dist/core/install/platform-resolution.js.map +1 -1
- package/dist/core/install/remote-flow.js +67 -1
- package/dist/core/install/remote-flow.js.map +1 -1
- package/dist/core/openpackage.js +16 -8
- package/dist/core/openpackage.js.map +1 -1
- package/dist/core/package-context.js +246 -0
- package/dist/core/package-context.js.map +1 -0
- package/dist/core/package.js +3 -2
- package/dist/core/package.js.map +1 -1
- package/dist/core/platforms.js +126 -217
- package/dist/core/platforms.js.map +1 -1
- package/dist/core/registry/registry-rename.js +2 -1
- package/dist/core/registry/registry-rename.js.map +1 -1
- package/dist/core/registry.js +10 -3
- package/dist/core/registry.js.map +1 -1
- package/dist/core/remote-pull.js +2 -1
- package/dist/core/remote-pull.js.map +1 -1
- package/dist/core/save/constants.js +4 -0
- package/dist/core/save/constants.js.map +1 -1
- package/dist/core/save/name-resolution.js +31 -0
- package/dist/core/save/name-resolution.js.map +1 -0
- package/dist/core/save/package-detection.js +147 -0
- package/dist/core/save/package-detection.js.map +1 -0
- package/dist/core/save/package-saver.js +46 -43
- package/dist/core/save/package-saver.js.map +1 -1
- package/dist/core/save/package-yml-generator.js +50 -71
- package/dist/core/save/package-yml-generator.js.map +1 -1
- package/dist/core/save/root-save-candidates.js.map +1 -1
- package/dist/core/save/save-candidate-loader.js +89 -0
- package/dist/core/save/save-candidate-loader.js.map +1 -0
- package/dist/core/save/save-conflict-resolution.js +72 -410
- package/dist/core/save/save-conflict-resolution.js.map +1 -1
- package/dist/core/save/save-conflict-resolver.js +277 -0
- package/dist/core/save/save-conflict-resolver.js.map +1 -0
- package/dist/core/save/save-pipeline.js +151 -0
- package/dist/core/save/save-pipeline.js.map +1 -0
- package/dist/core/save/save-types.js +2 -0
- package/dist/core/save/save-types.js.map +1 -0
- package/dist/core/save/save-yml-resolution.js +77 -39
- package/dist/core/save/save-yml-resolution.js.map +1 -1
- package/dist/core/save/workspace-rename.js +6 -6
- package/dist/core/save/workspace-rename.js.map +1 -1
- package/dist/core/scoping/package-scoping.js +13 -1
- package/dist/core/scoping/package-scoping.js.map +1 -1
- package/dist/core/status/status-file-discovery.js +12 -30
- package/dist/core/status/status-file-discovery.js.map +1 -1
- package/dist/core/sync/platform-sync.js +7 -4
- package/dist/core/sync/platform-sync.js.map +1 -1
- package/dist/core/sync/root-files-sync.js +59 -1
- package/dist/core/sync/root-files-sync.js.map +1 -1
- package/dist/core/uninstall/uninstall-file-discovery.js +1 -2
- package/dist/core/uninstall/uninstall-file-discovery.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/file-processing.js +5 -58
- package/dist/utils/file-processing.js.map +1 -1
- package/dist/utils/index-based-installer.js +15 -33
- package/dist/utils/index-based-installer.js.map +1 -1
- package/dist/utils/install-file-discovery.js +3 -3
- package/dist/utils/install-file-discovery.js.map +1 -1
- package/dist/utils/install-orchestrator.js +21 -21
- package/dist/utils/install-orchestrator.js.map +1 -1
- package/dist/utils/jsonc.js +44 -0
- package/dist/utils/jsonc.js.map +1 -0
- package/dist/utils/package-copy.js +199 -0
- package/dist/utils/package-copy.js.map +1 -0
- package/dist/utils/package-filters.js +125 -0
- package/dist/utils/package-filters.js.map +1 -0
- package/dist/utils/package-index-yml.js +15 -10
- package/dist/utils/package-index-yml.js.map +1 -1
- package/dist/utils/package-installation.js +4 -113
- package/dist/utils/package-installation.js.map +1 -1
- package/dist/utils/package-local-files.js +2 -35
- package/dist/utils/package-local-files.js.map +1 -1
- package/dist/utils/package-management.js +59 -37
- package/dist/utils/package-management.js.map +1 -1
- package/dist/utils/package-yml.js +24 -0
- package/dist/utils/package-yml.js.map +1 -1
- package/dist/utils/path-normalization.js +8 -53
- package/dist/utils/path-normalization.js.map +1 -1
- package/dist/utils/paths.js +17 -9
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-file.js +16 -59
- package/dist/utils/platform-file.js.map +1 -1
- package/dist/utils/platform-mapper.js +29 -41
- package/dist/utils/platform-mapper.js.map +1 -1
- package/dist/utils/platform-specific-paths.js.map +1 -1
- package/dist/utils/platform-utils.js +28 -139
- package/dist/utils/platform-utils.js.map +1 -1
- package/dist/utils/platform-yaml-merge.js +13 -6
- package/dist/utils/platform-yaml-merge.js.map +1 -1
- package/dist/utils/registry-entry-filter.js +38 -24
- package/dist/utils/registry-entry-filter.js.map +1 -1
- package/dist/utils/registry-paths.js +10 -0
- package/dist/utils/registry-paths.js.map +1 -0
- package/dist/utils/root-file-installer.js +19 -0
- package/dist/utils/root-file-installer.js.map +1 -1
- package/dist/utils/root-file-registry.js.map +1 -1
- package/package.json +3 -2
- package/platforms.jsonc +178 -0
- package/specs/package/README.md +60 -0
- package/specs/package/nested-packages-and-parent-packages.md +79 -0
- package/specs/package/package-index-yml.md +171 -0
- package/specs/package/package-root-layout.md +78 -0
- package/specs/package/registry-payload-and-copy.md +77 -0
- package/specs/package/universal-content.md +144 -0
- package/specs/platforms.md +193 -0
- package/specs/save/README.md +40 -0
- package/specs/save/save-conflict-resolution.md +146 -0
- package/specs/save/save-file-discovery.md +101 -0
- package/specs/save/save-frontmatter-overrides.md +81 -0
- package/specs/save/save-modes-inputs.md +53 -0
- package/specs/save/save-naming-scoping.md +93 -0
- package/specs/save/save-package-detection.md +60 -0
- package/specs/save/save-registry-sync.md +126 -0
- package/dist/commands/release.js +0 -33
- package/dist/commands/release.js.map +0 -1
- package/dist/commands/tag.js +0 -311
- package/dist/commands/tag.js.map +0 -1
- package/dist/commands/update.js +0 -30
- package/dist/commands/update.js.map +0 -1
- package/dist/core/add/formula-index-updater.js +0 -290
- package/dist/core/add/formula-index-updater.js.map +0 -1
- package/dist/core/discovery/ai-files-discovery.js +0 -2
- package/dist/core/discovery/ai-files-discovery.js.map +0 -1
- package/dist/core/discovery/formula-files-discovery.js +0 -14
- package/dist/core/discovery/formula-files-discovery.js.map +0 -1
- package/dist/core/discovery/index-files-discovery.js +0 -91
- package/dist/core/discovery/index-files-discovery.js.map +0 -1
- package/dist/core/discovery/md-files-discovery.js +0 -82
- package/dist/core/discovery/md-files-discovery.js.map +0 -1
- package/dist/core/discovery/package-files-discovery.js +0 -14
- package/dist/core/discovery/package-files-discovery.js.map +0 -1
- package/dist/core/discovery/platform-discovery.js +0 -84
- package/dist/core/discovery/platform-discovery.js.map +0 -1
- package/dist/core/discovery/root-files-discovery.js +0 -2
- package/dist/core/discovery/root-files-discovery.js.map +0 -1
- package/dist/core/formula.js +0 -170
- package/dist/core/formula.js.map +0 -1
- package/dist/core/git-registry.js +0 -46
- package/dist/core/git-registry.js.map +0 -1
- package/dist/core/groundzero.js +0 -277
- package/dist/core/groundzero.js.map +0 -1
- package/dist/core/install/scenario.js +0 -11
- package/dist/core/install/scenario.js.map +0 -1
- package/dist/core/package-sync.js +0 -219
- package/dist/core/package-sync.js.map +0 -1
- package/dist/core/save/formula-file-generator.js +0 -167
- package/dist/core/save/formula-file-generator.js.map +0 -1
- package/dist/core/save/formula-saver.js +0 -52
- package/dist/core/save/formula-saver.js.map +0 -1
- package/dist/core/save/formula-yml-generator.js +0 -89
- package/dist/core/save/formula-yml-generator.js.map +0 -1
- package/dist/core/save/formula-yml-versioning.js +0 -108
- package/dist/core/save/formula-yml-versioning.js.map +0 -1
- package/dist/core/save/generic-file-sync.js +0 -38
- package/dist/core/save/generic-file-sync.js.map +0 -1
- package/dist/core/save/md-files-sync.js +0 -33
- package/dist/core/save/md-files-sync.js.map +0 -1
- package/dist/core/save/package-yml-versioning.js +0 -108
- package/dist/core/save/package-yml-versioning.js.map +0 -1
- package/dist/core/save/platform-sync.js +0 -95
- package/dist/core/save/platform-sync.js.map +0 -1
- package/dist/core/save/root-files-sync.js +0 -140
- package/dist/core/save/root-files-sync.js.map +0 -1
- package/dist/core/save/save-candidate-types.js +0 -2
- package/dist/core/save/save-candidate-types.js.map +0 -1
- package/dist/core/save/save-conflict-types.js +0 -2
- package/dist/core/save/save-conflict-types.js.map +0 -1
- package/dist/core/save/save-file-discovery.js +0 -5
- package/dist/core/save/save-file-discovery.js.map +0 -1
- package/dist/core/status-file-discovery.js +0 -175
- package/dist/core/status-file-discovery.js.map +0 -1
- package/dist/utils/discovery/file-processing.js +0 -156
- package/dist/utils/discovery/file-processing.js.map +0 -1
- package/dist/utils/discovery/formula-discovery.js +0 -211
- package/dist/utils/discovery/formula-discovery.js.map +0 -1
- package/dist/utils/discovery/platform-discovery.js +0 -2
- package/dist/utils/discovery/platform-discovery.js.map +0 -1
- package/dist/utils/formula-discovery.js +0 -102
- package/dist/utils/formula-discovery.js.map +0 -1
- package/dist/utils/formula-index-yml.js +0 -122
- package/dist/utils/formula-index-yml.js.map +0 -1
- package/dist/utils/formula-installation.js +0 -110
- package/dist/utils/formula-installation.js.map +0 -1
- package/dist/utils/formula-local-files.js +0 -38
- package/dist/utils/formula-local-files.js.map +0 -1
- package/dist/utils/formula-management.js +0 -191
- package/dist/utils/formula-management.js.map +0 -1
- package/dist/utils/formula-name.js +0 -97
- package/dist/utils/formula-name.js.map +0 -1
- package/dist/utils/formula-versioning.js +0 -109
- package/dist/utils/formula-versioning.js.map +0 -1
- package/dist/utils/formula-yml.js +0 -82
- package/dist/utils/formula-yml.js.map +0 -1
- package/dist/utils/git.js +0 -54
- package/dist/utils/git.js.map +0 -1
- package/dist/utils/id-based-discovery.js +0 -126
- package/dist/utils/id-based-discovery.js.map +0 -1
- package/dist/utils/id-based-installer.js +0 -249
- package/dist/utils/id-based-installer.js.map +0 -1
- package/dist/utils/index-yml-based-installer.js +0 -375
- package/dist/utils/index-yml-based-installer.js.map +0 -1
- package/dist/utils/index-yml.js +0 -124
- package/dist/utils/index-yml.js.map +0 -1
- package/dist/utils/md-frontmatter.js +0 -3
- package/dist/utils/md-frontmatter.js.map +0 -1
- package/dist/utils/package-link-yml.js +0 -92
- package/dist/utils/package-link-yml.js.map +0 -1
- package/dist/utils/platform-discovery.js +0 -2
- package/dist/utils/platform-discovery.js.map +0 -1
- package/dist/utils/platform-frontmatter-split.js +0 -15
- package/dist/utils/platform-frontmatter-split.js.map +0 -1
- package/dist/utils/timestamp-encoder.js +0 -13
- package/dist/utils/timestamp-encoder.js.map +0 -1
- package/dist/utils/wip-versioning.js +0 -24
- package/dist/utils/wip-versioning.js.map +0 -1
package/dist/commands/install.js
CHANGED
|
@@ -1,688 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { logger } from '../utils/logger.js';
|
|
6
|
-
import { withErrorHandling, UserCancellationError, PackageNotFoundError } from '../utils/errors.js';
|
|
7
|
-
import { createPlatformDirectories } from '../core/platforms.js';
|
|
1
|
+
import { DIR_PATTERNS, PACKAGE_PATHS } from '../constants/index.js';
|
|
2
|
+
import { runBulkInstallPipeline } from '../core/install/bulk-install-pipeline.js';
|
|
3
|
+
import { runInstallPipeline, determineResolutionMode } from '../core/install/install-pipeline.js';
|
|
4
|
+
import { withErrorHandling } from '../utils/errors.js';
|
|
8
5
|
import { normalizePlatforms } from '../utils/platform-mapper.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import { extractPackagesFromConfig } from '../utils/install-helpers.js';
|
|
17
|
-
import { parsePackageYml } from '../utils/package-yml.js';
|
|
18
|
-
import { parsePackageInput, arePackageNamesEquivalent } from '../utils/package-name.js';
|
|
19
|
-
import { safePrompts } from '../utils/prompts.js';
|
|
20
|
-
import { createCaretRange, isExactVersion, parseVersionRange, resolveVersionRange } from '../utils/version-ranges.js';
|
|
21
|
-
import { aggregateRecursiveDownloads } from '../core/remote-pull.js';
|
|
22
|
-
import { computeMissingDownloadKeys } from '../core/install/download-keys.js';
|
|
23
|
-
import { fetchMissingDependencyMetadata, pullMissingDependencies } from '../core/install/remote-flow.js';
|
|
24
|
-
import { recordBatchOutcome } from '../core/install/remote-reporting.js';
|
|
25
|
-
import { handleDryRunMode } from '../core/install/dry-run.js';
|
|
26
|
-
import { extractRemoteErrorReason } from '../utils/error-reasons.js';
|
|
27
|
-
import { selectInstallVersionUnified } from '../core/install/version-selection.js';
|
|
28
|
-
export function determineResolutionMode(options) {
|
|
29
|
-
if (options.resolutionMode) {
|
|
30
|
-
return options.resolutionMode;
|
|
31
|
-
}
|
|
32
|
-
if (options.remote) {
|
|
33
|
-
return 'remote-primary';
|
|
6
|
+
import { parsePackageInput } from '../utils/package-name.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { normalizePathForProcessing } from '../utils/path-normalization.js';
|
|
9
|
+
function assertTargetDirOutsideMetadata(targetDir) {
|
|
10
|
+
const normalized = normalizePathForProcessing(targetDir ?? '.');
|
|
11
|
+
if (!normalized || normalized === '.') {
|
|
12
|
+
return; // default install root
|
|
34
13
|
}
|
|
35
|
-
if (
|
|
36
|
-
|
|
14
|
+
if (normalized === DIR_PATTERNS.OPENPACKAGE ||
|
|
15
|
+
normalized.startsWith(`${DIR_PATTERNS.OPENPACKAGE}/`)) {
|
|
16
|
+
throw new Error(`Installation target '${targetDir}' cannot point inside ${DIR_PATTERNS.OPENPACKAGE} (reserved for metadata like ${PACKAGE_PATHS.INDEX_RELATIVE}). Choose a workspace path outside metadata.`);
|
|
37
17
|
}
|
|
38
|
-
return 'default';
|
|
39
18
|
}
|
|
40
19
|
export function validateResolutionFlags(options) {
|
|
41
20
|
if (options.remote && options.local) {
|
|
42
21
|
throw new Error('--remote and --local cannot be used together. Choose one resolution mode.');
|
|
43
22
|
}
|
|
44
23
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Install all packages from CWD package.yml file
|
|
47
|
-
* @param targetDir - Target directory for installation
|
|
48
|
-
* @param options - Installation options including dev flag
|
|
49
|
-
* @returns Command result with installation summary
|
|
50
|
-
*/
|
|
51
|
-
async function installAllPackagesCommand(targetDir, options) {
|
|
52
|
-
const cwd = process.cwd();
|
|
53
|
-
logger.info(`Installing all packages from package.yml to: ${getAIDir(cwd)}`, { options });
|
|
54
|
-
await ensureRegistryDirectories();
|
|
55
|
-
// Auto-create basic package.yml if it doesn't exist
|
|
56
|
-
await createBasicPackageYml(cwd);
|
|
57
|
-
const packageYmlPath = getLocalPackageYmlPath(cwd);
|
|
58
|
-
const cwdConfig = await withOperationErrorHandling(() => parsePackageYml(packageYmlPath), 'parse package.yml', packageYmlPath);
|
|
59
|
-
const allPackagesToInstall = extractPackagesFromConfig(cwdConfig);
|
|
60
|
-
// Filter out any packages that match the root package name
|
|
61
|
-
const packagesToInstall = [];
|
|
62
|
-
const skippedRootPackages = [];
|
|
63
|
-
for (const pkg of allPackagesToInstall) {
|
|
64
|
-
if (await isRootPackage(cwd, pkg.name)) {
|
|
65
|
-
skippedRootPackages.push(pkg);
|
|
66
|
-
console.log(`⚠️ Skipping ${pkg.name} - it matches your project's root package name`);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
packagesToInstall.push(pkg);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (packagesToInstall.length === 0) {
|
|
73
|
-
if (skippedRootPackages.length > 0) {
|
|
74
|
-
console.log('✓ All packages in package.yml were skipped (matched root package)');
|
|
75
|
-
console.log('\nTips:');
|
|
76
|
-
console.log('• Root packages cannot be installed as dependencies');
|
|
77
|
-
console.log('• Use "opkg install <package-name>" to install external packages');
|
|
78
|
-
console.log('• Use "opkg save" to create a WIP copy of your root package in the registry');
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
console.log('⚠️ No packages found in package.yml');
|
|
82
|
-
console.log('\nTips:');
|
|
83
|
-
console.log('• Add packages to the "packages" array in package.yml');
|
|
84
|
-
console.log('• Add development packages to the "dev-packages" array in package.yml');
|
|
85
|
-
console.log('• Use "opkg install <package-name>" to install a specific package');
|
|
86
|
-
}
|
|
87
|
-
return { success: true, data: { installed: 0, skipped: skippedRootPackages.length } };
|
|
88
|
-
}
|
|
89
|
-
console.log(`✓ Installing ${packagesToInstall.length} packages from package.yml:`);
|
|
90
|
-
packagesToInstall.forEach(pkg => {
|
|
91
|
-
const prefix = pkg.isDev ? '[dev] ' : '';
|
|
92
|
-
const label = pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
|
|
93
|
-
console.log(` • ${prefix}${label}`);
|
|
94
|
-
});
|
|
95
|
-
if (skippedRootPackages.length > 0) {
|
|
96
|
-
console.log(` • ${skippedRootPackages.length} packages skipped (matched root package)`);
|
|
97
|
-
}
|
|
98
|
-
console.log('');
|
|
99
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
100
|
-
const normalizedPlatforms = normalizePlatforms(options.platforms);
|
|
101
|
-
const resolvedPlatforms = await resolvePlatforms(cwd, normalizedPlatforms, { interactive });
|
|
102
|
-
// Install packages sequentially to avoid conflicts
|
|
103
|
-
let totalInstalled = 0;
|
|
104
|
-
let totalSkipped = 0;
|
|
105
|
-
const results = [];
|
|
106
|
-
const aggregateWarnings = new Set();
|
|
107
|
-
for (const pkg of packagesToInstall) {
|
|
108
|
-
try {
|
|
109
|
-
const label = pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
|
|
110
|
-
const baseConflictDecisions = options.conflictDecisions
|
|
111
|
-
? { ...options.conflictDecisions }
|
|
112
|
-
: undefined;
|
|
113
|
-
const installOptions = {
|
|
114
|
-
...options,
|
|
115
|
-
dev: pkg.isDev,
|
|
116
|
-
resolvedPlatforms,
|
|
117
|
-
conflictDecisions: baseConflictDecisions
|
|
118
|
-
};
|
|
119
|
-
let conflictPlanningVersion = pkg.version;
|
|
120
|
-
if (pkg.version && !isExactVersion(pkg.version)) {
|
|
121
|
-
try {
|
|
122
|
-
const localVersions = await listPackageVersions(pkg.name);
|
|
123
|
-
conflictPlanningVersion = resolveVersionRange(pkg.version, localVersions) ?? undefined;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
conflictPlanningVersion = undefined;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (conflictPlanningVersion) {
|
|
130
|
-
try {
|
|
131
|
-
const conflicts = await planConflictsForPackage(cwd, pkg.name, conflictPlanningVersion, resolvedPlatforms);
|
|
132
|
-
if (conflicts.length > 0) {
|
|
133
|
-
const shouldPrompt = interactive && (!installOptions.conflictStrategy || installOptions.conflictStrategy === 'ask');
|
|
134
|
-
if (shouldPrompt) {
|
|
135
|
-
console.log(`\n⚠️ Detected ${conflicts.length} potential file conflict${conflicts.length === 1 ? '' : 's'} for ${label}.`);
|
|
136
|
-
const preview = conflicts.slice(0, 5);
|
|
137
|
-
for (const conflict of preview) {
|
|
138
|
-
const ownerInfo = conflict.ownerPackage ? `owned by ${conflict.ownerPackage}` : 'already exists locally';
|
|
139
|
-
console.log(` • ${conflict.relPath} (${ownerInfo})`);
|
|
140
|
-
}
|
|
141
|
-
if (conflicts.length > preview.length) {
|
|
142
|
-
console.log(` • ... and ${conflicts.length - preview.length} more`);
|
|
143
|
-
}
|
|
144
|
-
const selection = await safePrompts({
|
|
145
|
-
type: 'select',
|
|
146
|
-
name: 'strategy',
|
|
147
|
-
message: `Choose conflict handling for ${label}:`,
|
|
148
|
-
choices: [
|
|
149
|
-
{ title: 'Keep both (rename existing files)', value: 'keep-both' },
|
|
150
|
-
{ title: 'Overwrite existing files', value: 'overwrite' },
|
|
151
|
-
{ title: 'Skip conflicting files', value: 'skip' },
|
|
152
|
-
{ title: 'Review individually', value: 'ask' }
|
|
153
|
-
],
|
|
154
|
-
initial: installOptions.conflictStrategy === 'ask' ? 3 : 0
|
|
155
|
-
});
|
|
156
|
-
const chosenStrategy = selection.strategy;
|
|
157
|
-
installOptions.conflictStrategy = chosenStrategy;
|
|
158
|
-
if (chosenStrategy === 'ask') {
|
|
159
|
-
const decisions = {};
|
|
160
|
-
for (const conflict of conflicts) {
|
|
161
|
-
const detail = await safePrompts({
|
|
162
|
-
type: 'select',
|
|
163
|
-
name: 'decision',
|
|
164
|
-
message: `${conflict.relPath}${conflict.ownerPackage ? ` (owned by ${conflict.ownerPackage})` : ''}:`,
|
|
165
|
-
choices: [
|
|
166
|
-
{ title: 'Keep both (rename existing)', value: 'keep-both' },
|
|
167
|
-
{ title: 'Overwrite existing', value: 'overwrite' },
|
|
168
|
-
{ title: 'Skip (keep existing)', value: 'skip' }
|
|
169
|
-
],
|
|
170
|
-
initial: 0
|
|
171
|
-
});
|
|
172
|
-
const decisionValue = detail.decision;
|
|
173
|
-
decisions[conflict.relPath] = decisionValue;
|
|
174
|
-
}
|
|
175
|
-
installOptions.conflictDecisions = decisions;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else if (!interactive && (!installOptions.conflictStrategy || installOptions.conflictStrategy === 'ask')) {
|
|
179
|
-
logger.warn(`Detected ${conflicts.length} potential conflict${conflicts.length === 1 ? '' : 's'} for ${label}, but running in non-interactive mode. Conflicting files will be skipped unless '--conflicts' is provided.`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
catch (planError) {
|
|
184
|
-
logger.warn(`Failed to evaluate conflicts for ${label}: ${planError}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
console.log(`\n🔧 Installing ${pkg.isDev ? '[dev] ' : ''}${label}...`);
|
|
188
|
-
const result = await installPackageCommand(pkg.name, targetDir, installOptions, pkg.version);
|
|
189
|
-
if (result.success) {
|
|
190
|
-
totalInstalled++;
|
|
191
|
-
results.push({ name: pkg.name, success: true });
|
|
192
|
-
console.log(`✓ Successfully installed ${pkg.name}`);
|
|
193
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
194
|
-
result.warnings.forEach(warning => aggregateWarnings.add(warning));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
totalSkipped++;
|
|
199
|
-
results.push({ name: pkg.name, success: false, error: result.error });
|
|
200
|
-
console.log(`❌ Failed to install ${pkg.name}: ${result.error}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
if (error instanceof UserCancellationError) {
|
|
205
|
-
throw error; // Re-throw to allow clean exit
|
|
206
|
-
}
|
|
207
|
-
totalSkipped++;
|
|
208
|
-
results.push({ name: pkg.name, success: false, error: String(error) });
|
|
209
|
-
console.log(`❌ Failed to install ${pkg.name}: ${error}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
displayInstallationSummary(totalInstalled, totalSkipped, packagesToInstall.length, results);
|
|
213
|
-
if (aggregateWarnings.size > 0) {
|
|
214
|
-
console.log('\n⚠️ Warnings during installation:');
|
|
215
|
-
aggregateWarnings.forEach(warning => {
|
|
216
|
-
console.log(` • ${warning}`);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
const allSuccessful = totalSkipped === 0;
|
|
220
|
-
return {
|
|
221
|
-
success: allSuccessful,
|
|
222
|
-
data: {
|
|
223
|
-
installed: totalInstalled,
|
|
224
|
-
skipped: totalSkipped,
|
|
225
|
-
results
|
|
226
|
-
},
|
|
227
|
-
error: allSuccessful ? undefined : `${totalSkipped} packages failed to install`,
|
|
228
|
-
warnings: totalSkipped > 0 ? [`${totalSkipped} packages failed to install`] : undefined
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Install package command implementation with recursive dependency resolution
|
|
233
|
-
* @param packageName - Name of the package to install
|
|
234
|
-
* @param targetDir - Target directory for installation
|
|
235
|
-
* @param options - Installation options including force, dry-run, and dev flags
|
|
236
|
-
* @param version - Specific version to install (optional)
|
|
237
|
-
* @returns Command result with detailed installation information
|
|
238
|
-
*/
|
|
239
|
-
async function installPackageCommand(packageName, targetDir, options, version) {
|
|
240
|
-
const cwd = process.cwd();
|
|
241
|
-
const resolutionMode = options.resolutionMode ?? determineResolutionMode(options);
|
|
242
|
-
// 1) Validate root package and early return
|
|
243
|
-
if (await isRootPackage(cwd, packageName)) {
|
|
244
|
-
console.log(`⚠️ Cannot install ${packageName} - it matches your project's root package name`);
|
|
245
|
-
console.log(` This would create a circular dependency.`);
|
|
246
|
-
console.log(`💡 Tip: Use 'opkg install' without specifying a package name to install all packages`);
|
|
247
|
-
console.log(` referenced in your .openpackage/package.yml file.`);
|
|
248
|
-
return {
|
|
249
|
-
success: true,
|
|
250
|
-
data: { skipped: true, reason: 'root package' }
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
logger.debug(`Installing package '${packageName}' with dependencies to: ${getAIDir(cwd)}`, { options });
|
|
254
|
-
const dryRun = !!options.dryRun;
|
|
255
|
-
const forceRemote = resolutionMode === 'remote-primary';
|
|
256
|
-
const warnings = [];
|
|
257
|
-
const warnedPackages = new Set();
|
|
258
|
-
const canonicalPlan = await determineCanonicalInstallPlan({
|
|
259
|
-
cwd,
|
|
260
|
-
packageName,
|
|
261
|
-
cliSpec: version,
|
|
262
|
-
devFlag: options.dev ?? false
|
|
263
|
-
});
|
|
264
|
-
if (canonicalPlan.compatibilityMessage) {
|
|
265
|
-
console.log(`ℹ️ ${canonicalPlan.compatibilityMessage}`);
|
|
266
|
-
}
|
|
267
|
-
let versionConstraint = canonicalPlan.effectiveRange;
|
|
268
|
-
const selectionOptions = options.stable ? { preferStable: true } : undefined;
|
|
269
|
-
const preselection = await selectInstallVersionUnified({
|
|
270
|
-
packageName,
|
|
271
|
-
constraint: versionConstraint,
|
|
272
|
-
mode: resolutionMode,
|
|
273
|
-
selectionOptions,
|
|
274
|
-
profile: options.profile,
|
|
275
|
-
apiKey: options.apiKey
|
|
276
|
-
});
|
|
277
|
-
if (preselection.sources.warnings.length > 0) {
|
|
278
|
-
preselection.sources.warnings.forEach(message => {
|
|
279
|
-
warnings.push(message);
|
|
280
|
-
console.log(`⚠️ ${message}`);
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
const selectedRootVersion = preselection.selectedVersion;
|
|
284
|
-
if (!selectedRootVersion) {
|
|
285
|
-
const constraintLabel = canonicalPlan.dependencyState === 'existing' && canonicalPlan.canonicalRange
|
|
286
|
-
? canonicalPlan.canonicalRange
|
|
287
|
-
: versionConstraint;
|
|
288
|
-
throw buildNoVersionFoundError(packageName, constraintLabel, preselection.selection, resolutionMode);
|
|
289
|
-
}
|
|
290
|
-
const source = preselection.resolutionSource ?? 'local';
|
|
291
|
-
console.log(formatSelectionSummary(source, packageName, selectedRootVersion));
|
|
292
|
-
// 2) Prepare env via prepareInstallEnvironment
|
|
293
|
-
const { specifiedPlatforms } = await prepareInstallEnvironment(cwd, options);
|
|
294
|
-
let resolvedPackages = [];
|
|
295
|
-
let missingPackages = [];
|
|
296
|
-
const resolveDependenciesOutcome = async () => {
|
|
297
|
-
try {
|
|
298
|
-
const data = await resolveDependenciesForInstall(packageName, cwd, versionConstraint, options);
|
|
299
|
-
if (data.warnings && data.warnings.length > 0) {
|
|
300
|
-
data.warnings.forEach(message => {
|
|
301
|
-
warnings.push(message);
|
|
302
|
-
// Surface resolver warnings (including circular dependency notices)
|
|
303
|
-
// directly to the user for better visibility.
|
|
304
|
-
console.log(`⚠️ ${message}`);
|
|
305
|
-
const match = message.match(/Remote pull failed for `([^`]+)`/);
|
|
306
|
-
if (match) {
|
|
307
|
-
warnedPackages.add(match[1]);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
return { success: true, data };
|
|
312
|
-
}
|
|
313
|
-
catch (error) {
|
|
314
|
-
if (error instanceof VersionResolutionAbortError) {
|
|
315
|
-
return { success: false, commandResult: { success: false, error: error.message } };
|
|
316
|
-
}
|
|
317
|
-
if (error instanceof PackageNotFoundError ||
|
|
318
|
-
(error instanceof Error && (error.message.includes('not available in local registry') ||
|
|
319
|
-
(error.message.includes('Package') && error.message.includes('not found'))))) {
|
|
320
|
-
console.log('❌ Package not found');
|
|
321
|
-
return { success: false, commandResult: { success: false, error: 'Package not found' } };
|
|
322
|
-
}
|
|
323
|
-
throw error;
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
// 3) Resolve dependencies
|
|
327
|
-
const initialResolution = await resolveDependenciesOutcome();
|
|
328
|
-
if (!initialResolution.success) {
|
|
329
|
-
return initialResolution.commandResult;
|
|
330
|
-
}
|
|
331
|
-
resolvedPackages = initialResolution.data.resolvedPackages;
|
|
332
|
-
missingPackages = initialResolution.data.missingPackages;
|
|
333
|
-
const remoteOutcomes = {
|
|
334
|
-
...(initialResolution.data.remoteOutcomes ?? {})
|
|
335
|
-
};
|
|
336
|
-
const computeRetryEligibleMissing = (names) => {
|
|
337
|
-
return names.filter(name => {
|
|
338
|
-
const outcome = remoteOutcomes[name];
|
|
339
|
-
if (!outcome) {
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
return outcome.reason !== 'not-found' && outcome.reason !== 'access-denied';
|
|
343
|
-
});
|
|
344
|
-
};
|
|
345
|
-
let retryEligibleMissing = computeRetryEligibleMissing(missingPackages);
|
|
346
|
-
// Track packages that were already warned about during resolution
|
|
347
|
-
// to avoid duplicate warnings when fetching metadata
|
|
348
|
-
const pullMissingFromRemote = async () => {
|
|
349
|
-
if (retryEligibleMissing.length === 0) {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
const metadataResults = await fetchMissingDependencyMetadata(retryEligibleMissing, resolvedPackages, {
|
|
353
|
-
dryRun,
|
|
354
|
-
profile: options.profile,
|
|
355
|
-
apiKey: options.apiKey,
|
|
356
|
-
alreadyWarnedPackages: warnedPackages,
|
|
357
|
-
onFailure: (name, failure) => {
|
|
358
|
-
remoteOutcomes[name] = {
|
|
359
|
-
name,
|
|
360
|
-
reason: failure.reason,
|
|
361
|
-
message: failure.message
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
if (metadataResults.length === 0) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
const keysToDownload = new Set();
|
|
369
|
-
for (const metadata of metadataResults) {
|
|
370
|
-
const aggregated = aggregateRecursiveDownloads([metadata.response]);
|
|
371
|
-
const missingKeys = await computeMissingDownloadKeys(aggregated);
|
|
372
|
-
missingKeys.forEach((key) => keysToDownload.add(key));
|
|
373
|
-
}
|
|
374
|
-
const batchResults = await pullMissingDependencies(metadataResults, keysToDownload, {
|
|
375
|
-
dryRun,
|
|
376
|
-
profile: options.profile,
|
|
377
|
-
apiKey: options.apiKey
|
|
378
|
-
});
|
|
379
|
-
for (const batchResult of batchResults) {
|
|
380
|
-
recordBatchOutcome('Pulled dependencies', batchResult, warnings, dryRun);
|
|
381
|
-
updateRemoteOutcomesFromBatch(batchResult, remoteOutcomes);
|
|
382
|
-
}
|
|
383
|
-
const refreshedResolution = await resolveDependenciesOutcome();
|
|
384
|
-
if (!refreshedResolution.success) {
|
|
385
|
-
return refreshedResolution.commandResult;
|
|
386
|
-
}
|
|
387
|
-
resolvedPackages = refreshedResolution.data.resolvedPackages;
|
|
388
|
-
missingPackages = refreshedResolution.data.missingPackages;
|
|
389
|
-
if (refreshedResolution.data.remoteOutcomes) {
|
|
390
|
-
Object.assign(remoteOutcomes, refreshedResolution.data.remoteOutcomes);
|
|
391
|
-
}
|
|
392
|
-
retryEligibleMissing = computeRetryEligibleMissing(missingPackages);
|
|
393
|
-
return null;
|
|
394
|
-
};
|
|
395
|
-
if (missingPackages.length > 0) {
|
|
396
|
-
if (resolutionMode === 'local-only') {
|
|
397
|
-
logger.info('Local-only mode: missing dependencies will not be pulled from remote', {
|
|
398
|
-
missingPackages: Array.from(new Set(missingPackages))
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
else if (retryEligibleMissing.length > 0) {
|
|
402
|
-
const pullResult = await pullMissingFromRemote();
|
|
403
|
-
if (pullResult) {
|
|
404
|
-
return pullResult;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
// 7) Warn if still missing
|
|
409
|
-
if (missingPackages.length > 0) {
|
|
410
|
-
const missingSummary = `Missing packages: ${Array.from(new Set(missingPackages)).join(', ')}`;
|
|
411
|
-
console.log(`⚠️ ${missingSummary}`);
|
|
412
|
-
warnings.push(missingSummary);
|
|
413
|
-
}
|
|
414
|
-
// 8) Process conflicts
|
|
415
|
-
const conflictProcessing = await processConflictResolution(resolvedPackages, options);
|
|
416
|
-
if ('cancelled' in conflictProcessing) {
|
|
417
|
-
console.log(`Installation cancelled by user`);
|
|
418
|
-
return {
|
|
419
|
-
success: true,
|
|
420
|
-
data: {
|
|
421
|
-
packageName,
|
|
422
|
-
targetDir: getAIDir(cwd),
|
|
423
|
-
resolvedPackages: [],
|
|
424
|
-
totalPackages: 0,
|
|
425
|
-
installed: 0,
|
|
426
|
-
skipped: 1,
|
|
427
|
-
totalGroundzeroFiles: 0
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
const { finalResolvedPackages, conflictResult } = conflictProcessing;
|
|
432
|
-
displayDependencyTree(finalResolvedPackages, true);
|
|
433
|
-
const packageYmlPath = getLocalPackageYmlPath(cwd);
|
|
434
|
-
const packageYmlExists = await exists(packageYmlPath);
|
|
435
|
-
// 9) If dryRun, delegate to handleDryRunMode and return
|
|
436
|
-
if (options.dryRun) {
|
|
437
|
-
return await handleDryRunMode(finalResolvedPackages, packageName, targetDir, options, packageYmlExists);
|
|
438
|
-
}
|
|
439
|
-
// 10) Resolve platforms, create dirs, perform phases, write metadata, update package.yml, display results, return
|
|
440
|
-
const canPromptForPlatforms = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
441
|
-
const finalPlatforms = options.resolvedPlatforms && options.resolvedPlatforms.length > 0
|
|
442
|
-
? options.resolvedPlatforms
|
|
443
|
-
: await resolvePlatforms(cwd, specifiedPlatforms, { interactive: canPromptForPlatforms });
|
|
444
|
-
const createdDirs = await createPlatformDirectories(cwd, finalPlatforms);
|
|
445
|
-
const mainPackage = finalResolvedPackages.find((f) => f.isRoot);
|
|
446
|
-
const installationOutcome = await performIndexBasedInstallationPhases({
|
|
447
|
-
cwd,
|
|
448
|
-
packages: finalResolvedPackages,
|
|
449
|
-
platforms: finalPlatforms,
|
|
450
|
-
conflictResult,
|
|
451
|
-
options,
|
|
452
|
-
targetDir
|
|
453
|
-
});
|
|
454
|
-
for (const resolved of finalResolvedPackages) {
|
|
455
|
-
await writeLocalPackageFromRegistry(cwd, resolved.name, resolved.version);
|
|
456
|
-
}
|
|
457
|
-
if (packageYmlExists && mainPackage) {
|
|
458
|
-
const persistTarget = resolvePersistRange(canonicalPlan.persistDecision, mainPackage.version);
|
|
459
|
-
if (persistTarget) {
|
|
460
|
-
await addPackageToYml(cwd, packageName, mainPackage.version, persistTarget.target === 'dev-packages', persistTarget.range, true);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
displayInstallationResults(packageName, finalResolvedPackages, { platforms: finalPlatforms, created: createdDirs }, options, mainPackage, installationOutcome.allAddedFiles, installationOutcome.allUpdatedFiles, installationOutcome.rootFileResults, missingPackages, remoteOutcomes);
|
|
464
|
-
return {
|
|
465
|
-
success: true,
|
|
466
|
-
data: {
|
|
467
|
-
packageName,
|
|
468
|
-
targetDir: getAIDir(cwd),
|
|
469
|
-
resolvedPackages: finalResolvedPackages,
|
|
470
|
-
totalPackages: finalResolvedPackages.length,
|
|
471
|
-
installed: installationOutcome.installedCount,
|
|
472
|
-
skipped: installationOutcome.skippedCount,
|
|
473
|
-
totalGroundzeroFiles: installationOutcome.totalGroundzeroFiles
|
|
474
|
-
},
|
|
475
|
-
warnings: warnings.length > 0 ? Array.from(new Set(warnings)) : undefined
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
export function formatSelectionSummary(source, packageName, version) {
|
|
479
|
-
const packageSpecifier = packageName.startsWith('@') ? packageName : `@${packageName}`;
|
|
480
|
-
return `✓ Selected ${source} ${packageSpecifier}@${version}`;
|
|
481
|
-
}
|
|
482
|
-
function updateRemoteOutcomesFromBatch(batchResult, remoteOutcomes) {
|
|
483
|
-
if (!batchResult.failed || batchResult.failed.length === 0) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
for (const failure of batchResult.failed) {
|
|
487
|
-
const reasonLabel = extractRemoteErrorReason(failure.error ?? 'Unknown error');
|
|
488
|
-
const reasonTag = mapReasonLabelToOutcome(reasonLabel);
|
|
489
|
-
const packageName = failure.name;
|
|
490
|
-
const message = `Remote pull failed for \`${packageName}\` (reason: ${reasonLabel})`;
|
|
491
|
-
remoteOutcomes[packageName] = {
|
|
492
|
-
name: packageName,
|
|
493
|
-
reason: reasonTag,
|
|
494
|
-
message
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
function mapReasonLabelToOutcome(reasonLabel) {
|
|
499
|
-
switch (reasonLabel) {
|
|
500
|
-
case 'not found in remote registry':
|
|
501
|
-
case 'not found in registry':
|
|
502
|
-
return 'not-found';
|
|
503
|
-
case 'access denied':
|
|
504
|
-
return 'access-denied';
|
|
505
|
-
case 'network error':
|
|
506
|
-
return 'network';
|
|
507
|
-
case 'integrity check failed':
|
|
508
|
-
return 'integrity';
|
|
509
|
-
default:
|
|
510
|
-
return 'unknown';
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
async function determineCanonicalInstallPlan(args) {
|
|
514
|
-
const normalizedCliSpec = args.cliSpec?.trim() || undefined;
|
|
515
|
-
const existing = await findCanonicalDependency(args.cwd, args.packageName);
|
|
516
|
-
const target = args.devFlag ? 'dev-packages' : 'packages';
|
|
517
|
-
if (existing) {
|
|
518
|
-
const canonicalConstraint = parseConstraintOrThrow('package', existing.range, args.packageName);
|
|
519
|
-
if (normalizedCliSpec) {
|
|
520
|
-
const cliConstraint = parseConstraintOrThrow('cli', normalizedCliSpec, args.packageName);
|
|
521
|
-
if (!isRangeSubset(cliConstraint.resolverRange, canonicalConstraint.resolverRange)) {
|
|
522
|
-
throw buildCanonicalConflictError(args.packageName, cliConstraint.displayRange, existing.range);
|
|
523
|
-
}
|
|
524
|
-
return {
|
|
525
|
-
effectiveRange: canonicalConstraint.resolverRange,
|
|
526
|
-
dependencyState: 'existing',
|
|
527
|
-
canonicalRange: existing.range,
|
|
528
|
-
canonicalTarget: existing.target,
|
|
529
|
-
persistDecision: { type: 'none' },
|
|
530
|
-
compatibilityMessage: `Using version range from package.yml (${existing.range}); CLI spec '${cliConstraint.displayRange}' is compatible.`
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
return {
|
|
534
|
-
effectiveRange: canonicalConstraint.resolverRange,
|
|
535
|
-
dependencyState: 'existing',
|
|
536
|
-
canonicalRange: existing.range,
|
|
537
|
-
canonicalTarget: existing.target,
|
|
538
|
-
persistDecision: { type: 'none' }
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
if (normalizedCliSpec) {
|
|
542
|
-
const cliConstraint = parseConstraintOrThrow('cli', normalizedCliSpec, args.packageName);
|
|
543
|
-
return {
|
|
544
|
-
effectiveRange: cliConstraint.resolverRange,
|
|
545
|
-
dependencyState: 'fresh',
|
|
546
|
-
persistDecision: {
|
|
547
|
-
type: 'explicit',
|
|
548
|
-
target,
|
|
549
|
-
range: cliConstraint.displayRange
|
|
550
|
-
}
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
return {
|
|
554
|
-
effectiveRange: '*',
|
|
555
|
-
dependencyState: 'fresh',
|
|
556
|
-
persistDecision: {
|
|
557
|
-
type: 'derive',
|
|
558
|
-
target,
|
|
559
|
-
mode: 'caret-or-exact'
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
async function findCanonicalDependency(cwd, packageName) {
|
|
564
|
-
const packageYmlPath = getLocalPackageYmlPath(cwd);
|
|
565
|
-
if (!(await exists(packageYmlPath))) {
|
|
566
|
-
return null;
|
|
567
|
-
}
|
|
568
|
-
try {
|
|
569
|
-
const config = await parsePackageYml(packageYmlPath);
|
|
570
|
-
const match = locateDependencyInArray(config.packages, packageName, 'packages') ||
|
|
571
|
-
locateDependencyInArray(config['dev-packages'], packageName, 'dev-packages');
|
|
572
|
-
return match;
|
|
573
|
-
}
|
|
574
|
-
catch (error) {
|
|
575
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
576
|
-
throw new Error(`Failed to parse ${packageYmlPath}: ${detail}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
function locateDependencyInArray(deps, packageName, target) {
|
|
580
|
-
if (!deps) {
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
const entry = deps.find(dep => arePackageNamesEquivalent(dep.name, packageName));
|
|
584
|
-
if (!entry) {
|
|
585
|
-
return null;
|
|
586
|
-
}
|
|
587
|
-
if (!entry.version || !entry.version.trim()) {
|
|
588
|
-
throw new Error(`Dependency '${packageName}' in .openpackage/package.yml must declare a version range. Edit the file and try again.`);
|
|
589
|
-
}
|
|
590
|
-
return {
|
|
591
|
-
range: entry.version.trim(),
|
|
592
|
-
target
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
function parseConstraintOrThrow(source, raw, packageName) {
|
|
596
|
-
try {
|
|
597
|
-
const parsed = parseVersionRange(raw);
|
|
598
|
-
return { resolverRange: parsed.range, displayRange: parsed.original };
|
|
599
|
-
}
|
|
600
|
-
catch (error) {
|
|
601
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
602
|
-
if (source === 'cli') {
|
|
603
|
-
throw new Error(`Invalid version spec '${raw}' provided via CLI for '${packageName}'. ${message}. Adjust the CLI input and try again.`);
|
|
604
|
-
}
|
|
605
|
-
throw new Error(`Dependency '${packageName}' in .openpackage/package.yml has invalid version '${raw}'. ${message}. Edit the file and try again.`);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
function isRangeSubset(candidate, canonical) {
|
|
609
|
-
try {
|
|
610
|
-
return semver.subset(candidate, canonical, { includePrerelease: true });
|
|
611
|
-
}
|
|
612
|
-
catch {
|
|
613
|
-
return false;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
function buildCanonicalConflictError(packageName, cliSpec, canonicalRange) {
|
|
617
|
-
return new Error(`Requested '${packageName}@${cliSpec}', but .openpackage/package.yml declares '${packageName}' with range '${canonicalRange}'. Edit package.yml to change the dependency line, then re-run opkg install.`);
|
|
618
|
-
}
|
|
619
|
-
function resolvePersistRange(decision, selectedVersion) {
|
|
620
|
-
if (decision.type === 'none') {
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
if (decision.type === 'explicit') {
|
|
624
|
-
return { range: decision.range, target: decision.target };
|
|
625
|
-
}
|
|
626
|
-
// Create caret range from the selected version
|
|
627
|
-
const derivedRange = createCaretRange(selectedVersion);
|
|
628
|
-
return { range: derivedRange, target: decision.target };
|
|
629
|
-
}
|
|
630
|
-
function buildNoVersionFoundError(packageName, constraint, selection, mode) {
|
|
631
|
-
const stableList = formatVersionList(selection.availableStable);
|
|
632
|
-
const prereleaseList = formatVersionList(selection.availablePrerelease);
|
|
633
|
-
const suggestions = [
|
|
634
|
-
'Edit .openpackage/package.yml or adjust the CLI range, then retry.',
|
|
635
|
-
'Use opkg save/pack to create a compatible version in the local registry.'
|
|
636
|
-
];
|
|
637
|
-
if (mode === 'local-only') {
|
|
638
|
-
suggestions.push('Re-run without --local to include remote versions in resolution.');
|
|
639
|
-
}
|
|
640
|
-
const message = [
|
|
641
|
-
`No version of '${packageName}' satisfies '${constraint}'.`,
|
|
642
|
-
`Available stable versions: ${stableList}`,
|
|
643
|
-
`Available WIP/pre-release versions: ${prereleaseList}`,
|
|
644
|
-
'Suggested next steps:',
|
|
645
|
-
...suggestions.map(suggestion => ` • ${suggestion}`)
|
|
646
|
-
].join('\n');
|
|
647
|
-
return new Error(message);
|
|
648
|
-
}
|
|
649
|
-
function formatVersionList(versions) {
|
|
650
|
-
if (!versions || versions.length === 0) {
|
|
651
|
-
return 'none';
|
|
652
|
-
}
|
|
653
|
-
return versions.join(', ');
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Main install command router - handles both individual and bulk install
|
|
657
|
-
* @param packageName - Name of package to install (optional, installs all if not provided)
|
|
658
|
-
* @param targetDir - Target directory for installation
|
|
659
|
-
* @param options - Installation options
|
|
660
|
-
* @returns Command result with installation status and data
|
|
661
|
-
*/
|
|
662
24
|
async function installCommand(packageName, targetDir, options) {
|
|
663
|
-
|
|
664
|
-
options.resolutionMode =
|
|
665
|
-
logger.debug('Install resolution mode selected', { mode });
|
|
666
|
-
// If no package name provided, install all from package.yml
|
|
25
|
+
assertTargetDirOutsideMetadata(targetDir);
|
|
26
|
+
options.resolutionMode = determineResolutionMode(options);
|
|
27
|
+
logger.debug('Install resolution mode selected', { mode: options.resolutionMode });
|
|
667
28
|
if (!packageName) {
|
|
668
|
-
return await
|
|
29
|
+
return await runBulkInstallPipeline(targetDir, options);
|
|
669
30
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
31
|
+
const { name, version } = parsePackageInput(packageName);
|
|
32
|
+
return await runInstallPipeline({
|
|
33
|
+
...options,
|
|
34
|
+
packageName: name,
|
|
35
|
+
version,
|
|
36
|
+
targetDir
|
|
37
|
+
});
|
|
674
38
|
}
|
|
675
|
-
/**
|
|
676
|
-
* Setup the install command
|
|
677
|
-
* @param program - Commander program instance to register the command with
|
|
678
|
-
*/
|
|
679
39
|
export function setupInstallCommand(program) {
|
|
680
40
|
program
|
|
681
41
|
.command('install')
|
|
682
42
|
.alias('i')
|
|
683
43
|
.description('Install packages from the local (and optional remote) registry into this workspace. Works with WIP copies from `opkg save` and stable releases from `opkg pack`.')
|
|
684
44
|
.argument('[package-name]', 'name of the package to install (optional - installs all from package.yml if not specified). Supports package@version syntax.')
|
|
685
|
-
.argument('[target-dir]', 'target directory relative to
|
|
45
|
+
.argument('[target-dir]', 'target directory relative to the workspace install root (defaults to ./ai)', '.')
|
|
686
46
|
.option('--dry-run', 'preview changes without applying them')
|
|
687
47
|
.option('--force', 'overwrite existing files')
|
|
688
48
|
.option('--conflicts <strategy>', 'conflict handling strategy: keep-both, overwrite, skip, or ask')
|
|
@@ -694,7 +54,6 @@ export function setupInstallCommand(program) {
|
|
|
694
54
|
.option('--profile <profile>', 'profile to use for authentication')
|
|
695
55
|
.option('--api-key <key>', 'API key for authentication (overrides profile)')
|
|
696
56
|
.action(withErrorHandling(async (packageName, targetDir, options) => {
|
|
697
|
-
// Normalize platforms option early for downstream logic
|
|
698
57
|
options.platforms = normalizePlatforms(options.platforms);
|
|
699
58
|
const commandOptions = options;
|
|
700
59
|
const rawConflictStrategy = commandOptions.conflicts ?? options.conflictStrategy;
|
|
@@ -711,7 +70,6 @@ export function setupInstallCommand(program) {
|
|
|
711
70
|
const result = await installCommand(packageName, targetDir, options);
|
|
712
71
|
if (!result.success) {
|
|
713
72
|
if (result.error === 'Package not found') {
|
|
714
|
-
// Handled case: already printed minimal message, do not bubble to global handler
|
|
715
73
|
return;
|
|
716
74
|
}
|
|
717
75
|
throw new Error(result.error || 'Installation operation failed');
|