soso-ppm 2.4.8
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/.github/workflows/npm-publish.yaml +24 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/bin/soso.js +78 -0
- package/lib/cache.js +196 -0
- package/lib/commands/clean.js +22 -0
- package/lib/commands/info.js +67 -0
- package/lib/commands/install.js +220 -0
- package/lib/commands/publish.js +143 -0
- package/lib/commands/update.js +111 -0
- package/lib/config.js +126 -0
- package/lib/lockfile.js +136 -0
- package/lib/resolver.js +139 -0
- package/lib/utils/git.js +136 -0
- package/lib/utils/help.js +53 -0
- package/lib/utils/logger.js +50 -0
- package/package.json +39 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 18
|
|
18
|
+
registry-url: https://registry.npmjs.org/
|
|
19
|
+
|
|
20
|
+
- run: npm install
|
|
21
|
+
|
|
22
|
+
- run: npm publish
|
|
23
|
+
env:
|
|
24
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NitoGX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Soso
|
|
2
|
+
A powerfull package manager!
|
|
3
|
+
## What is "Soso"?
|
|
4
|
+
Soso is a powerfull package manager written in javascript using Node.js.
|
|
5
|
+
To install, publish, update packages, you'll need the pre-requesites below
|
|
6
|
+
## Pre-requesites
|
|
7
|
+
- Node.js (https://nodejs.org)
|
|
8
|
+
- Git (https://git-scm.com/)
|
|
9
|
+
## How to install?
|
|
10
|
+
First, clone the repo:
|
|
11
|
+
```bash
|
|
12
|
+
git clone --branch master --single-branch https://github.com/Nitogx/soso-ppm.git
|
|
13
|
+
```
|
|
14
|
+
Then, inside the repo folder, install the required packages:
|
|
15
|
+
```bash
|
|
16
|
+
npm i
|
|
17
|
+
```
|
|
18
|
+
And lastly, link it:
|
|
19
|
+
```console
|
|
20
|
+
npm link
|
|
21
|
+
```
|
|
22
|
+
### How to use it?
|
|
23
|
+
In a new terminal run:
|
|
24
|
+
```bash
|
|
25
|
+
soso -h
|
|
26
|
+
# Or
|
|
27
|
+
soso --help
|
|
28
|
+
```
|
|
29
|
+
And you'll have a detailled guide about all commands!
|
|
30
|
+
|
|
31
|
+
### Official Packages
|
|
32
|
+
[Hugging Face API for Soso PPM](https://github.com/Nitogx/huggingface-inference-wrapper/tree/master)
|
|
33
|
+
[Clark AI for Soso PPM](https://github.com/Nitogx/clarky/tree/master)
|
package/bin/soso.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { install } = require('../lib/commands/install');
|
|
5
|
+
const { publish } = require('../lib/commands/publish');
|
|
6
|
+
const { update } = require('../lib/commands/update');
|
|
7
|
+
const { info } = require('../lib/commands/info');
|
|
8
|
+
const { clean } = require('../lib/commands/clean');
|
|
9
|
+
const { printHelp, printVersion } = require('../lib/utils/help');
|
|
10
|
+
const { enableDebug } = require('../lib/utils/logger');
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0];
|
|
14
|
+
|
|
15
|
+
// Check for flags
|
|
16
|
+
const debug = args.includes('--debug') || args.includes('-d');
|
|
17
|
+
if (debug) {
|
|
18
|
+
enableDebug();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
try {
|
|
23
|
+
switch (command) {
|
|
24
|
+
case 'install':
|
|
25
|
+
case 'i':
|
|
26
|
+
await install(args.slice(1));
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case 'publish':
|
|
30
|
+
await publish(args.slice(1));
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case 'update':
|
|
34
|
+
await update(args.slice(1));
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case 'info':
|
|
38
|
+
await info(args.slice(1));
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case 'cache':
|
|
42
|
+
if (args[1] === 'clean') {
|
|
43
|
+
await clean(args.slice(2));
|
|
44
|
+
} else {
|
|
45
|
+
console.error('Unknown cache command. Use: soso cache clean');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 'version':
|
|
51
|
+
case '-v':
|
|
52
|
+
case '--version':
|
|
53
|
+
printVersion();
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'help':
|
|
57
|
+
case '-h':
|
|
58
|
+
case '--help':
|
|
59
|
+
case undefined:
|
|
60
|
+
printHelp();
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
default:
|
|
64
|
+
console.error(`Unknown command: ${command}`);
|
|
65
|
+
console.error('Run "soso help" for usage information.');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (debug) {
|
|
70
|
+
console.error(error);
|
|
71
|
+
} else {
|
|
72
|
+
console.error(`Error: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main();
|
package/lib/cache.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const { getCacheDir, ensureDir } = require('./config');
|
|
7
|
+
const { debug } = require('./utils/logger');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cache manager for package storage
|
|
11
|
+
*/
|
|
12
|
+
class CacheManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.cacheDir = getCacheDir();
|
|
15
|
+
ensureDir(this.cacheDir);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate cache key for package
|
|
20
|
+
*/
|
|
21
|
+
getCacheKey(name, version) {
|
|
22
|
+
const key = `${name}@${version}`;
|
|
23
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get cache path for package
|
|
28
|
+
*/
|
|
29
|
+
getCachePath(name, version) {
|
|
30
|
+
const key = this.getCacheKey(name, version);
|
|
31
|
+
return path.join(this.cacheDir, key, 'package');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if package is cached
|
|
36
|
+
*/
|
|
37
|
+
has(name, version) {
|
|
38
|
+
const cachePath = this.getCachePath(name, version);
|
|
39
|
+
const packageJsonPath = path.join(cachePath, 'package.json');
|
|
40
|
+
return fs.existsSync(packageJsonPath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get cached package path
|
|
45
|
+
*/
|
|
46
|
+
get(name, version) {
|
|
47
|
+
if (!this.has(name, version)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return this.getCachePath(name, version);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add package to cache
|
|
55
|
+
* @param {string} name - Package name
|
|
56
|
+
* @param {string} version - Package version
|
|
57
|
+
* @param {string} sourcePath - Path to package files
|
|
58
|
+
* @returns {string} Cache path
|
|
59
|
+
*/
|
|
60
|
+
add(name, version, sourcePath) {
|
|
61
|
+
const cachePath = this.getCachePath(name, version);
|
|
62
|
+
|
|
63
|
+
debug(`Caching ${name}@${version} to ${cachePath}`);
|
|
64
|
+
|
|
65
|
+
ensureDir(cachePath);
|
|
66
|
+
|
|
67
|
+
// Copy all files from source to cache
|
|
68
|
+
this.copyRecursive(sourcePath, cachePath);
|
|
69
|
+
|
|
70
|
+
return cachePath;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Copy directory recursively
|
|
75
|
+
*/
|
|
76
|
+
copyRecursive(src, dest) {
|
|
77
|
+
ensureDir(dest);
|
|
78
|
+
|
|
79
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const srcPath = path.join(src, entry.name);
|
|
83
|
+
const destPath = path.join(dest, entry.name);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
this.copyRecursive(srcPath, destPath);
|
|
87
|
+
} else {
|
|
88
|
+
fs.copyFileSync(srcPath, destPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculate integrity hash for package
|
|
95
|
+
*/
|
|
96
|
+
calculateIntegrity(packagePath) {
|
|
97
|
+
const hash = crypto.createHash('sha256');
|
|
98
|
+
this.hashDirectory(packagePath, hash);
|
|
99
|
+
return 'sha256-' + hash.digest('base64');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Hash directory contents recursively
|
|
104
|
+
*/
|
|
105
|
+
hashDirectory(dir, hash) {
|
|
106
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
107
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
108
|
+
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
const fullPath = path.join(dir, entry.name);
|
|
111
|
+
|
|
112
|
+
// Skip node_modules
|
|
113
|
+
if (entry.name === 'node_modules') {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
hash.update(entry.name);
|
|
118
|
+
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
this.hashDirectory(fullPath, hash);
|
|
121
|
+
} else {
|
|
122
|
+
const content = fs.readFileSync(fullPath);
|
|
123
|
+
hash.update(content);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Clear entire cache
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
debug(`Clearing cache at ${this.cacheDir}`);
|
|
133
|
+
|
|
134
|
+
if (fs.existsSync(this.cacheDir)) {
|
|
135
|
+
this.removeRecursive(this.cacheDir);
|
|
136
|
+
ensureDir(this.cacheDir);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Remove directory recursively
|
|
142
|
+
*/
|
|
143
|
+
removeRecursive(dir) {
|
|
144
|
+
if (!fs.existsSync(dir)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
149
|
+
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
const fullPath = path.join(dir, entry.name);
|
|
152
|
+
|
|
153
|
+
if (entry.isDirectory()) {
|
|
154
|
+
this.removeRecursive(fullPath);
|
|
155
|
+
} else {
|
|
156
|
+
fs.unlinkSync(fullPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fs.rmdirSync(dir);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get cache statistics
|
|
165
|
+
*/
|
|
166
|
+
getStats() {
|
|
167
|
+
const entries = fs.readdirSync(this.cacheDir);
|
|
168
|
+
return {
|
|
169
|
+
packages: entries.length,
|
|
170
|
+
size: this.getDirectorySize(this.cacheDir)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Calculate directory size
|
|
176
|
+
*/
|
|
177
|
+
getDirectorySize(dir) {
|
|
178
|
+
let size = 0;
|
|
179
|
+
|
|
180
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
181
|
+
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const fullPath = path.join(dir, entry.name);
|
|
184
|
+
|
|
185
|
+
if (entry.isDirectory()) {
|
|
186
|
+
size += this.getDirectorySize(fullPath);
|
|
187
|
+
} else {
|
|
188
|
+
size += fs.statSync(fullPath).size;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return size;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { CacheManager };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CacheManager } = require('../cache');
|
|
4
|
+
const { success, info } = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Clean package cache
|
|
8
|
+
*/
|
|
9
|
+
async function clean(args) {
|
|
10
|
+
const cache = new CacheManager();
|
|
11
|
+
|
|
12
|
+
info('Clearing package cache...');
|
|
13
|
+
|
|
14
|
+
const stats = cache.getStats();
|
|
15
|
+
const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
16
|
+
|
|
17
|
+
cache.clear();
|
|
18
|
+
|
|
19
|
+
success(`Cleared cache (${stats.packages} packages, ${sizeMB} MB)`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { clean };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { loadRegistry } = require('../config');
|
|
5
|
+
const { error } = require('../utils/logger');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Show package information
|
|
9
|
+
*/
|
|
10
|
+
async function info(args) {
|
|
11
|
+
if (args.length === 0 || args[0].startsWith('-')) {
|
|
12
|
+
throw new Error('Package name required. Usage: soso info <package>');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const packageName = args[0];
|
|
16
|
+
const registry = loadRegistry();
|
|
17
|
+
|
|
18
|
+
const packageData = registry.packages[packageName];
|
|
19
|
+
|
|
20
|
+
if (!packageData) {
|
|
21
|
+
error(`Package not found: ${packageName}`);
|
|
22
|
+
console.log('\nAvailable packages:');
|
|
23
|
+
const packages = Object.keys(registry.packages).sort();
|
|
24
|
+
if (packages.length === 0) {
|
|
25
|
+
console.log(' (none)');
|
|
26
|
+
} else {
|
|
27
|
+
packages.forEach(name => console.log(` ${name}`));
|
|
28
|
+
}
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Display package information
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(chalk.bold.cyan(packageData.name));
|
|
35
|
+
console.log();
|
|
36
|
+
|
|
37
|
+
const versions = Object.keys(packageData.versions).sort((a, b) => {
|
|
38
|
+
const semver = require('semver');
|
|
39
|
+
return semver.rcompare(a, b);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(chalk.bold('Versions:'));
|
|
43
|
+
for (const version of versions) {
|
|
44
|
+
const versionData = packageData.versions[version];
|
|
45
|
+
const isLatest = version === versions[0];
|
|
46
|
+
const tag = isLatest ? chalk.green(' (latest)') : '';
|
|
47
|
+
|
|
48
|
+
console.log(` ${chalk.yellow(version)}${tag}`);
|
|
49
|
+
console.log(` ${chalk.gray('Git:')} ${versionData.gitUrl}#${versionData.tag}`);
|
|
50
|
+
|
|
51
|
+
if (versionData.publishedAt) {
|
|
52
|
+
const date = new Date(versionData.publishedAt).toLocaleString();
|
|
53
|
+
console.log(` ${chalk.gray('Published:')} ${date}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (versionData.dependencies && Object.keys(versionData.dependencies).length > 0) {
|
|
57
|
+
console.log(` ${chalk.gray('Dependencies:')}`);
|
|
58
|
+
for (const [depName, depRange] of Object.entries(versionData.dependencies)) {
|
|
59
|
+
console.log(` ${depName}: ${depRange}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { info };
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { loadRegistry, ensureDir } = require('../config');
|
|
6
|
+
const { Resolver } = require('../resolver');
|
|
7
|
+
const { CacheManager } = require('../cache');
|
|
8
|
+
const { Lockfile } = require('../lockfile');
|
|
9
|
+
const { success, error, info, debug } = require('../utils/logger');
|
|
10
|
+
const git = require('../utils/git');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Install dependencies
|
|
15
|
+
*/
|
|
16
|
+
async function install(args) {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
19
|
+
|
|
20
|
+
// Load package.json
|
|
21
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
22
|
+
throw new Error('No package.json found in current directory');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
26
|
+
|
|
27
|
+
// Determine what to install
|
|
28
|
+
let specificPackage = null;
|
|
29
|
+
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
30
|
+
specificPackage = args[0];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (specificPackage) {
|
|
34
|
+
info(`Installing ${specificPackage}...`);
|
|
35
|
+
// Add to dependencies if not present
|
|
36
|
+
if (!packageJson.dependencies) {
|
|
37
|
+
packageJson.dependencies = {};
|
|
38
|
+
}
|
|
39
|
+
if (!packageJson.dependencies[specificPackage]) {
|
|
40
|
+
packageJson.dependencies[specificPackage] = '*';
|
|
41
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
info('Installing dependencies...');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const dependencies = packageJson.dependencies || {};
|
|
48
|
+
|
|
49
|
+
if (Object.keys(dependencies).length === 0) {
|
|
50
|
+
success('No dependencies to install');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Load registry and initialize components
|
|
55
|
+
const registry = loadRegistry();
|
|
56
|
+
const cache = new CacheManager();
|
|
57
|
+
const lockfile = new Lockfile(cwd);
|
|
58
|
+
|
|
59
|
+
// Resolve dependencies
|
|
60
|
+
info('Resolving dependency tree...');
|
|
61
|
+
const resolver = new Resolver(registry);
|
|
62
|
+
const resolved = await resolver.resolve(dependencies);
|
|
63
|
+
|
|
64
|
+
debug(`Resolved ${Object.keys(resolved).length} packages`);
|
|
65
|
+
|
|
66
|
+
// Prepare node_modules
|
|
67
|
+
const nodeModulesPath = path.join(cwd, 'node_modules');
|
|
68
|
+
ensureDir(nodeModulesPath);
|
|
69
|
+
|
|
70
|
+
// Install packages
|
|
71
|
+
const installed = {};
|
|
72
|
+
|
|
73
|
+
for (const [name, version] of Object.entries(resolved)) {
|
|
74
|
+
await installPackage(name, version, nodeModulesPath, cache, registry, installed);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Write lockfile
|
|
78
|
+
lockfile.write(installed);
|
|
79
|
+
|
|
80
|
+
success(`Installed ${Object.keys(installed).length} packages`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Install a single package
|
|
85
|
+
*/
|
|
86
|
+
async function installPackage(name, version, nodeModulesPath, cache, registry, installed) {
|
|
87
|
+
info(`Installing ${name}@${version}...`);
|
|
88
|
+
|
|
89
|
+
// Check cache first
|
|
90
|
+
let sourcePath = cache.get(name, version);
|
|
91
|
+
|
|
92
|
+
if (sourcePath) {
|
|
93
|
+
debug(`Using cached ${name}@${version}`);
|
|
94
|
+
} else {
|
|
95
|
+
// Fetch from registry
|
|
96
|
+
sourcePath = await fetchPackage(name, version, registry, cache);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Install to node_modules
|
|
100
|
+
const targetPath = getPackageInstallPath(name, nodeModulesPath);
|
|
101
|
+
ensureDir(path.dirname(targetPath));
|
|
102
|
+
|
|
103
|
+
// Copy from cache to node_modules
|
|
104
|
+
copyDirectory(sourcePath, targetPath);
|
|
105
|
+
|
|
106
|
+
// Calculate integrity
|
|
107
|
+
const integrity = cache.calculateIntegrity(sourcePath);
|
|
108
|
+
|
|
109
|
+
// Get package info from registry
|
|
110
|
+
const packageData = registry.packages[name];
|
|
111
|
+
const versionData = packageData.versions[version];
|
|
112
|
+
|
|
113
|
+
// Track installed package
|
|
114
|
+
installed[name] = {
|
|
115
|
+
version,
|
|
116
|
+
resolved: versionData.gitUrl,
|
|
117
|
+
integrity,
|
|
118
|
+
dependencies: versionData.dependencies || {}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
debug(`Installed ${name}@${version} to ${targetPath}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Fetch package from registry
|
|
126
|
+
*/
|
|
127
|
+
async function fetchPackage(name, version, registry, cache) {
|
|
128
|
+
const packageData = registry.packages[name];
|
|
129
|
+
if (!packageData) {
|
|
130
|
+
throw new Error(`Package not found in registry: ${name}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const versionData = packageData.versions[version];
|
|
134
|
+
if (!versionData) {
|
|
135
|
+
throw new Error(`Version ${version} not found for ${name}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const gitUrl = versionData.gitUrl;
|
|
139
|
+
const tag = `v${version}`;
|
|
140
|
+
|
|
141
|
+
debug(`Fetching ${name}@${version} from ${gitUrl}`);
|
|
142
|
+
|
|
143
|
+
// Create temporary directory for cloning
|
|
144
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'soso-'));
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Shallow clone at specific tag
|
|
148
|
+
await git.clone(gitUrl, tempDir, { shallow: true, branch: tag });
|
|
149
|
+
|
|
150
|
+
// Add to cache
|
|
151
|
+
const cachePath = cache.add(name, version, tempDir);
|
|
152
|
+
|
|
153
|
+
return cachePath;
|
|
154
|
+
} finally {
|
|
155
|
+
// Clean up temp directory
|
|
156
|
+
removeDirectory(tempDir);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get installation path for package
|
|
162
|
+
*/
|
|
163
|
+
function getPackageInstallPath(name, nodeModulesPath) {
|
|
164
|
+
if (name.startsWith('@')) {
|
|
165
|
+
// Scoped package: @scope/name -> node_modules/@scope/name
|
|
166
|
+
const parts = name.split('/');
|
|
167
|
+
return path.join(nodeModulesPath, parts[0], parts[1]);
|
|
168
|
+
}
|
|
169
|
+
return path.join(nodeModulesPath, name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Copy directory recursively
|
|
174
|
+
*/
|
|
175
|
+
function copyDirectory(src, dest) {
|
|
176
|
+
ensureDir(dest);
|
|
177
|
+
|
|
178
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
179
|
+
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const srcPath = path.join(src, entry.name);
|
|
182
|
+
const destPath = path.join(dest, entry.name);
|
|
183
|
+
|
|
184
|
+
// Skip .git directory
|
|
185
|
+
if (entry.name === '.git') {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (entry.isDirectory()) {
|
|
190
|
+
copyDirectory(srcPath, destPath);
|
|
191
|
+
} else {
|
|
192
|
+
fs.copyFileSync(srcPath, destPath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Remove directory recursively
|
|
199
|
+
*/
|
|
200
|
+
function removeDirectory(dir) {
|
|
201
|
+
if (!fs.existsSync(dir)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
206
|
+
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
const fullPath = path.join(dir, entry.name);
|
|
209
|
+
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
removeDirectory(fullPath);
|
|
212
|
+
} else {
|
|
213
|
+
fs.unlinkSync(fullPath);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fs.rmdirSync(dir);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = { install };
|