@o861runners/nmp 1.26.13-0.11255
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/.publishrc.example.json +55 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/bin/cli.js +183 -0
- package/package.json +36 -0
- package/src/config/loader.js +110 -0
- package/src/config/replacer.js +57 -0
- package/src/core/publisher.js +314 -0
- package/src/core/registry.js +42 -0
- package/src/registries/gitea.js +44 -0
- package/src/registries/github.js +39 -0
- package/src/registries/npm.js +90 -0
- package/src/registries/pocketbase.js +102 -0
- package/src/registries/supabase.js +86 -0
- package/src/utils/npm-args.js +48 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { NpmRegistry } from "../registries/npm.js";
|
|
5
|
+
import { GitHubRegistry } from "../registries/github.js";
|
|
6
|
+
import { GiteaRegistry } from "../registries/gitea.js";
|
|
7
|
+
import { SupabaseRegistry } from "../registries/supabase.js";
|
|
8
|
+
import { PocketBaseRegistry } from "../registries/pocketbase.js";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
const REGISTRY_TYPES = {
|
|
13
|
+
npm: NpmRegistry,
|
|
14
|
+
github: GitHubRegistry,
|
|
15
|
+
gitea: GiteaRegistry,
|
|
16
|
+
supabase: SupabaseRegistry,
|
|
17
|
+
pocketbase: PocketBaseRegistry,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class Publisher {
|
|
21
|
+
constructor(config, options = {}) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.registries = this.initRegistries();
|
|
25
|
+
this.tmpDir = path.join(process.cwd(), ".npm-multi-publish-tmp");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize registries từ config
|
|
30
|
+
*/
|
|
31
|
+
initRegistries() {
|
|
32
|
+
const registries = [];
|
|
33
|
+
|
|
34
|
+
for (const [name, regConfig] of Object.entries(this.config.registries || {})) {
|
|
35
|
+
// Skip disabled
|
|
36
|
+
if (!regConfig.enabled) {
|
|
37
|
+
console.log(chalk.gray(`○ ${name} (disabled)`));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Filter by --target
|
|
42
|
+
if (this.options.targets && !this.options.targets.includes(name)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const RegistryClass = REGISTRY_TYPES[regConfig.type];
|
|
47
|
+
if (!RegistryClass) {
|
|
48
|
+
console.warn(chalk.yellow(`⚠️ Unknown registry type: ${regConfig.type}`));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
registries.push(new RegistryClass(name, regConfig, this.config));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (registries.length === 0) {
|
|
56
|
+
throw new Error("No enabled registries found");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return registries;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate version: 1.yy.mmdd.1hhMM
|
|
64
|
+
* @returns {string} Version string
|
|
65
|
+
*
|
|
66
|
+
* Examples:
|
|
67
|
+
* - 30/01/2026 15:45 → "1.26.0130.11545"
|
|
68
|
+
* - 15/12/2026 09:30 → "1.26.1215.10930"
|
|
69
|
+
*/
|
|
70
|
+
generateVersion() {
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const yy = String(now.getFullYear()).slice(-2);
|
|
73
|
+
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
74
|
+
const dd = String(now.getDate()).padStart(2, "0");
|
|
75
|
+
const hh = String(now.getHours()).padStart(2, "0");
|
|
76
|
+
const MM = String(now.getMinutes()).padStart(2, "0");
|
|
77
|
+
|
|
78
|
+
return `1.${yy}.${mm}${dd}.1${hh}${MM}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create package.json for specific registry
|
|
83
|
+
* @param {object} registry - Registry instance
|
|
84
|
+
* @param {object} originalPkg - Original package.json content
|
|
85
|
+
* @returns {object} { pkgPath: string, pkg: object }
|
|
86
|
+
*
|
|
87
|
+
* This creates a registry-specific package.json with:
|
|
88
|
+
* - New auto-generated version
|
|
89
|
+
* - Registry-specific package name (with scope if applicable)
|
|
90
|
+
*/
|
|
91
|
+
async createRegistryPackage(registry, originalPkg) {
|
|
92
|
+
const registryPkgPath = path.join(this.tmpDir, `package-${registry.name}.json`);
|
|
93
|
+
const newVersion = this.generateVersion();
|
|
94
|
+
|
|
95
|
+
// Clone package.json
|
|
96
|
+
const registryPkg = { ...originalPkg };
|
|
97
|
+
|
|
98
|
+
// Update version
|
|
99
|
+
registryPkg.version = newVersion;
|
|
100
|
+
|
|
101
|
+
console.log(JSON.stringify({ registry, originalPkg, registryPkg }, null, 2));
|
|
102
|
+
|
|
103
|
+
// Update package name based on registry config
|
|
104
|
+
if (registry.config.scope) {
|
|
105
|
+
// Remove existing scope if any
|
|
106
|
+
const baseName = registryPkg.name.replace(/^@[^/]+\//, "");
|
|
107
|
+
registryPkg.name = `${registry.config.scope}/${baseName}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (registry.config.package_name && registry.config.package_name + "" !== "") {
|
|
111
|
+
registryPkg.name = registry.config.package_name;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Write registry-specific package.json
|
|
115
|
+
fs.writeFileSync(registryPkgPath, JSON.stringify(registryPkg, null, 2));
|
|
116
|
+
|
|
117
|
+
return { pkgPath: registryPkgPath, pkg: registryPkg };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Pack package for specific registry
|
|
122
|
+
* @param {object} registry - Registry instance
|
|
123
|
+
* @param {string} pkgPath - Path to registry-specific package.json
|
|
124
|
+
* @returns {string} Path to created .tgz file
|
|
125
|
+
*
|
|
126
|
+
* Flow:
|
|
127
|
+
* 1. Backup original package.json
|
|
128
|
+
* 2. Replace with registry-specific package.json
|
|
129
|
+
* 3. Run npm pack
|
|
130
|
+
* 4. Rename .tgz with registry name prefix
|
|
131
|
+
* 5. Restore original package.json
|
|
132
|
+
*/
|
|
133
|
+
async packForRegistry(registry, pkgPath) {
|
|
134
|
+
const spinner = ora(`Packing for ${registry.name}...`).start();
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// Backup original package.json
|
|
138
|
+
const originalPkgPath = path.join(process.cwd(), "package.json");
|
|
139
|
+
const backupPkgPath = path.join(this.tmpDir, "package.json.backup");
|
|
140
|
+
fs.copyFileSync(originalPkgPath, backupPkgPath);
|
|
141
|
+
|
|
142
|
+
// Replace with registry-specific package.json
|
|
143
|
+
fs.copyFileSync(pkgPath, originalPkgPath);
|
|
144
|
+
|
|
145
|
+
// Pack
|
|
146
|
+
const packOutput = execSync("npm pack --json", { encoding: "utf-8" });
|
|
147
|
+
const packInfo = JSON.parse(packOutput)[0];
|
|
148
|
+
const artifactPath = packInfo.filename;
|
|
149
|
+
|
|
150
|
+
// Move to registry-specific directory with registry name prefix
|
|
151
|
+
const registryArtifactPath = path.join(this.tmpDir, `${registry.name}-${packInfo.filename}`);
|
|
152
|
+
fs.renameSync(artifactPath, registryArtifactPath);
|
|
153
|
+
|
|
154
|
+
// Restore original package.json
|
|
155
|
+
fs.copyFileSync(backupPkgPath, originalPkgPath);
|
|
156
|
+
|
|
157
|
+
spinner.succeed(`Package created for ${registry.name}`);
|
|
158
|
+
console.log(chalk.gray(` Name: ${packInfo.name}@${packInfo.version}`));
|
|
159
|
+
console.log(chalk.gray(` Size: ${(packInfo.size / 1024).toFixed(2)} KB`));
|
|
160
|
+
console.log(chalk.gray(` File: ${registryArtifactPath}`));
|
|
161
|
+
|
|
162
|
+
return registryArtifactPath;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
spinner.fail(`Pack failed for ${registry.name}`);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Main execution flow
|
|
171
|
+
*/
|
|
172
|
+
async run() {
|
|
173
|
+
console.log(chalk.bold("\n🚀 NPM Multi-Registry Publisher\n"));
|
|
174
|
+
|
|
175
|
+
// Create temp directory
|
|
176
|
+
if (!fs.existsSync(this.tmpDir)) {
|
|
177
|
+
fs.mkdirSync(this.tmpDir, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 1. Build (if needed)
|
|
181
|
+
if (!this.options.skipBuild && this.config.build?.command) {
|
|
182
|
+
const spinner = ora("Building package...").start();
|
|
183
|
+
try {
|
|
184
|
+
execSync(this.config.build.command, { stdio: "pipe" });
|
|
185
|
+
spinner.succeed("Build completed");
|
|
186
|
+
} catch (error) {
|
|
187
|
+
spinner.fail("Build failed");
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 2. Read original package.json
|
|
193
|
+
const originalPkg = JSON.parse(fs.readFileSync("package.json", "utf-8"));
|
|
194
|
+
console.log(chalk.bold(`\n📦 Original package: ${originalPkg.name}@${originalPkg.version}\n`));
|
|
195
|
+
|
|
196
|
+
// 3. Validate all registries
|
|
197
|
+
console.log(chalk.bold("📋 Validating registries...\n"));
|
|
198
|
+
for (const registry of this.registries) {
|
|
199
|
+
try {
|
|
200
|
+
await registry.validate();
|
|
201
|
+
console.log(chalk.green(`✓ ${registry.name}`));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.log(chalk.red(`✗ ${registry.name}: ${error.message}`));
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log("");
|
|
209
|
+
|
|
210
|
+
// 4. Authenticate all
|
|
211
|
+
console.log(chalk.bold("🔑 Authenticating...\n"));
|
|
212
|
+
for (const registry of this.registries) {
|
|
213
|
+
try {
|
|
214
|
+
await registry.authenticate();
|
|
215
|
+
console.log(chalk.green(`✓ ${registry.name}`));
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.log(chalk.red(`✗ ${registry.name}: ${error.message}`));
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log("");
|
|
223
|
+
|
|
224
|
+
// 5. Create and publish to each registry
|
|
225
|
+
console.log(chalk.bold("📦 Creating packages and publishing...\n"));
|
|
226
|
+
const results = [];
|
|
227
|
+
|
|
228
|
+
for (const registry of this.registries) {
|
|
229
|
+
if (this.options.dryRun) {
|
|
230
|
+
console.log(chalk.cyan(`[DRY RUN] ${registry.name}`));
|
|
231
|
+
results.push({
|
|
232
|
+
registry: registry.name,
|
|
233
|
+
success: true,
|
|
234
|
+
dryRun: true,
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
try {
|
|
238
|
+
// Create registry-specific package
|
|
239
|
+
const { pkgPath, pkg } = await this.createRegistryPackage(registry, originalPkg);
|
|
240
|
+
console.log(chalk.blue(`📝 Package name for ${registry.name}: ${pkg.name}@${pkg.version}`));
|
|
241
|
+
|
|
242
|
+
// Pack for registry
|
|
243
|
+
const artifactPath = await this.packForRegistry(registry, pkgPath);
|
|
244
|
+
|
|
245
|
+
// Publish
|
|
246
|
+
const result = await registry.publish(artifactPath);
|
|
247
|
+
results.push({
|
|
248
|
+
registry: registry.name,
|
|
249
|
+
packageName: pkg.name,
|
|
250
|
+
version: pkg.version,
|
|
251
|
+
...result,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (result.success) {
|
|
255
|
+
console.log(chalk.green(`✓ ${registry.name} - ${pkg.name}@${pkg.version}`));
|
|
256
|
+
} else {
|
|
257
|
+
console.log(chalk.red(`✗ ${registry.name}: ${result.error}`));
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
results.push({
|
|
261
|
+
registry: registry.name,
|
|
262
|
+
success: false,
|
|
263
|
+
error: error.message,
|
|
264
|
+
});
|
|
265
|
+
console.log(chalk.red(`✗ ${registry.name}: ${error.message}`));
|
|
266
|
+
}finally{
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log("");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 6. Cleanup
|
|
275
|
+
for (const registry of this.registries) {
|
|
276
|
+
await registry.cleanup();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 7. Summary
|
|
280
|
+
this.printSummary(results);
|
|
281
|
+
|
|
282
|
+
return results;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Print summary table
|
|
287
|
+
*/
|
|
288
|
+
printSummary(results) {
|
|
289
|
+
console.log(chalk.bold("📊 Summary:\n"));
|
|
290
|
+
|
|
291
|
+
const successful = results.filter((r) => r.success);
|
|
292
|
+
const failed = results.filter((r) => !r.success && !r.dryRun);
|
|
293
|
+
|
|
294
|
+
for (const result of results) {
|
|
295
|
+
const icon = result.dryRun ? "○" : result.success ? "✅" : "❌";
|
|
296
|
+
const status = result.dryRun ? chalk.cyan("[DRY RUN]") : result.success ? chalk.green("[SUCCESS]") : chalk.red("[FAILED]");
|
|
297
|
+
|
|
298
|
+
const pkgInfo = result.packageName && result.version ? ` - ${result.packageName}@${result.version}` : "";
|
|
299
|
+
|
|
300
|
+
console.log(`${icon} ${result.registry}${pkgInfo} ${status}`);
|
|
301
|
+
|
|
302
|
+
if (result.url) {
|
|
303
|
+
console.log(chalk.gray(` URL: ${result.url}`));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (result.error && !result.success) {
|
|
307
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log("");
|
|
312
|
+
console.log(chalk.bold(`Total: ${results.length} | Success: ${successful.length} | Failed: ${failed.length}`));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class cho tất cả registry publishers
|
|
3
|
+
*/
|
|
4
|
+
export class BaseRegistry {
|
|
5
|
+
constructor(name, config, globalConfig) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.globalConfig = globalConfig;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate registry config
|
|
13
|
+
* @throws {Error} nếu config không hợp lệ
|
|
14
|
+
*/
|
|
15
|
+
async validate() {
|
|
16
|
+
throw new Error('validate() must be implemented');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Authenticate với registry
|
|
21
|
+
* @throws {Error} nếu auth fail
|
|
22
|
+
*/
|
|
23
|
+
async authenticate() {
|
|
24
|
+
throw new Error('authenticate() must be implemented');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Publish package
|
|
29
|
+
* @param {string} artifactPath - Path to .tgz file
|
|
30
|
+
* @returns {object} - {success: boolean, error?: string}
|
|
31
|
+
*/
|
|
32
|
+
async publish(artifactPath) {
|
|
33
|
+
throw new Error('publish() must be implemented');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Cleanup (xóa temp files, .npmrc...)
|
|
38
|
+
*/
|
|
39
|
+
async cleanup() {
|
|
40
|
+
// Optional override
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NpmRegistry } from "./npm.js";
|
|
2
|
+
import { BaseRegistry } from "../core/registry.js";
|
|
3
|
+
|
|
4
|
+
export class GiteaRegistry extends BaseRegistry {
|
|
5
|
+
async validate() {
|
|
6
|
+
if (!this.config.url) {
|
|
7
|
+
throw new Error("Gitea registry requires url");
|
|
8
|
+
}
|
|
9
|
+
if (!this.config.owner) {
|
|
10
|
+
throw new Error("Gitea registry requires owner");
|
|
11
|
+
}
|
|
12
|
+
if (!this.config.token) {
|
|
13
|
+
throw new Error("Gitea registry requires token");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async authenticate() {
|
|
18
|
+
// Gitea Packages API format: {url}/api/packages/{owner}/npm
|
|
19
|
+
const baseUrl = this.config.url.replace(/\/$/, "");
|
|
20
|
+
this.config.registry = `${baseUrl}/api/packages/${this.config.owner}/npm`;
|
|
21
|
+
this.config.scope = `@${this.config.owner}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async publish(artifactPath) {
|
|
25
|
+
// Delegate to NPM publisher
|
|
26
|
+
const npmPublisher = new NpmRegistry(
|
|
27
|
+
this.name,
|
|
28
|
+
{
|
|
29
|
+
...this.config,
|
|
30
|
+
registry: this.config.registry,
|
|
31
|
+
token: this.config.token,
|
|
32
|
+
access: this.config.access || "public",
|
|
33
|
+
scope: this.config.scope,
|
|
34
|
+
},
|
|
35
|
+
this.globalConfig,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
await npmPublisher.authenticate();
|
|
39
|
+
const result = await npmPublisher.publish(artifactPath);
|
|
40
|
+
await npmPublisher.cleanup();
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NpmRegistry } from "./npm.js";
|
|
2
|
+
import { BaseRegistry } from "../core/registry.js";
|
|
3
|
+
|
|
4
|
+
export class GitHubRegistry extends BaseRegistry {
|
|
5
|
+
async validate() {
|
|
6
|
+
if (!this.config.owner || !this.config.repo) {
|
|
7
|
+
throw new Error("GitHub registry requires owner and repo");
|
|
8
|
+
}
|
|
9
|
+
if (!this.config.token) {
|
|
10
|
+
throw new Error("GitHub registry requires token");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async authenticate() {
|
|
15
|
+
// GitHub Packages sử dụng npm registry
|
|
16
|
+
this.config.registry = "https://npm.pkg.github.com";
|
|
17
|
+
this.config.scope = `@${this.config.owner}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async publish(artifactPath) {
|
|
21
|
+
// Delegate to NPM publisher với GitHub registry config
|
|
22
|
+
const npmPublisher = new NpmRegistry(
|
|
23
|
+
this.name,
|
|
24
|
+
{
|
|
25
|
+
registry: this.config.registry,
|
|
26
|
+
token: this.config.token,
|
|
27
|
+
access: "public", // GitHub Packages luôn là restricted
|
|
28
|
+
scope: this.config.scope,
|
|
29
|
+
},
|
|
30
|
+
this.globalConfig,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
await npmPublisher.authenticate();
|
|
34
|
+
const result = await npmPublisher.publish(artifactPath);
|
|
35
|
+
await npmPublisher.cleanup();
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { BaseRegistry } from "../core/registry.js";
|
|
5
|
+
import { buildNpmArgs } from "../utils/npm-args.js";
|
|
6
|
+
|
|
7
|
+
export class NpmRegistry extends BaseRegistry {
|
|
8
|
+
constructor(name, config, globalConfig) {
|
|
9
|
+
super(name, config, globalConfig);
|
|
10
|
+
this.npmrcPath = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async validate() {
|
|
14
|
+
// Apply default registry nếu chưa có
|
|
15
|
+
if (!this.config.registry) {
|
|
16
|
+
this.config.registry = this.globalConfig.defaults?.registry || "https://registry.npmjs.org";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!this.config.token) {
|
|
20
|
+
throw new Error(`NPM registry "${this.name}" missing token`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async authenticate() {
|
|
25
|
+
// Tạo .npmrc tạm trong temp directory
|
|
26
|
+
const tmpDir = path.join(process.cwd(), ".npm-multi-publish-tmp");
|
|
27
|
+
if (!fs.existsSync(tmpDir)) {
|
|
28
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.npmrcPath = path.join(tmpDir, `.npmrc-${this.name}`);
|
|
32
|
+
|
|
33
|
+
// Parse registry hostname
|
|
34
|
+
const registryUrl = new URL(this.config.registry);
|
|
35
|
+
const registryHost = `//${registryUrl.host}${registryUrl.pathname}`;
|
|
36
|
+
let npmrcContent = ``;
|
|
37
|
+
if (this.config.type === "gitea") {
|
|
38
|
+
npmrcContent += `${this.config.scope}:registry=${registryUrl}/` + `\n`;
|
|
39
|
+
npmrcContent += `registry=${registryUrl}/` + `\n`;
|
|
40
|
+
npmrcContent += `always-auth=true` + `\n`;
|
|
41
|
+
npmrcContent += `strict-ssl=true` + `\n`;
|
|
42
|
+
}
|
|
43
|
+
npmrcContent += `${registryHost.replace(/\/$/, "")}/:_authToken=${this.config.token}\n`;
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(this.npmrcPath, npmrcContent, { encoding: "utf8" });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async publish(artifactPath) {
|
|
49
|
+
const args = buildNpmArgs(this.config, this.globalConfig);
|
|
50
|
+
// Add userconfig flag
|
|
51
|
+
args.push("--userconfig", this.npmrcPath);
|
|
52
|
+
// Add artifact path
|
|
53
|
+
args.push(artifactPath);
|
|
54
|
+
|
|
55
|
+
// Parse registry hostname
|
|
56
|
+
const registryUrl = new URL(this.config.registry);
|
|
57
|
+
const registryHost = `//${registryUrl.host}${registryUrl.pathname}`;
|
|
58
|
+
|
|
59
|
+
// Use custom .npmrc
|
|
60
|
+
const env = {
|
|
61
|
+
...process.env,
|
|
62
|
+
NPM_CONFIG_USERCONFIG: this.npmrcPath,
|
|
63
|
+
[`npm_config_${registryHost.replace(/[^a-zA-Z0-9]/g, "_")}_auth_token`]: this.config.authToken,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const command = `npm ${args.join(" ")}`;
|
|
67
|
+
|
|
68
|
+
console.log(`📦 Publishing to ${this.name}...`);
|
|
69
|
+
console.log(` Registry: ${this.config.registry}`);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
execSync(command, {
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
// env,
|
|
75
|
+
});
|
|
76
|
+
return { success: true };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: error.message,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async cleanup() {
|
|
86
|
+
if (this.npmrcPath && fs.existsSync(this.npmrcPath)) {
|
|
87
|
+
fs.unlinkSync(this.npmrcPath);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fetch from 'node-fetch';
|
|
4
|
+
import FormData from 'form-data';
|
|
5
|
+
import { BaseRegistry } from '../core/registry.js';
|
|
6
|
+
|
|
7
|
+
export class PocketBaseRegistry extends BaseRegistry {
|
|
8
|
+
async validate() {
|
|
9
|
+
if (!this.config.url) {
|
|
10
|
+
throw new Error('PocketBase registry requires url');
|
|
11
|
+
}
|
|
12
|
+
if (!this.config.collection) {
|
|
13
|
+
throw new Error('PocketBase registry requires collection');
|
|
14
|
+
}
|
|
15
|
+
if (!this.config.email && !this.config.token) {
|
|
16
|
+
throw new Error('PocketBase registry requires email/password or token');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async authenticate() {
|
|
21
|
+
// If token provided, use it directly
|
|
22
|
+
if (this.config.token) {
|
|
23
|
+
this.authToken = this.config.token;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Otherwise authenticate with email/password
|
|
28
|
+
if (!this.config.email || !this.config.password) {
|
|
29
|
+
throw new Error('PocketBase requires email and password for authentication');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const response = await fetch(
|
|
33
|
+
`${this.config.url}/api/admins/auth-with-password`,
|
|
34
|
+
{
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
identity: this.config.email,
|
|
41
|
+
password: this.config.password,
|
|
42
|
+
}),
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`PocketBase auth failed: ${response.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
this.authToken = data.token;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async publish(artifactPath) {
|
|
55
|
+
const fileName = path.basename(artifactPath);
|
|
56
|
+
const fileStream = fs.createReadStream(artifactPath);
|
|
57
|
+
|
|
58
|
+
const form = new FormData();
|
|
59
|
+
form.append('file', fileStream);
|
|
60
|
+
form.append('name', fileName);
|
|
61
|
+
|
|
62
|
+
console.log(`📦 Uploading to PocketBase...`);
|
|
63
|
+
console.log(` Collection: ${this.config.collection}`);
|
|
64
|
+
console.log(` File: ${fileName}`);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(
|
|
68
|
+
`${this.config.url}/api/collections/${this.config.collection}/records`,
|
|
69
|
+
{
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
73
|
+
...form.getHeaders(),
|
|
74
|
+
},
|
|
75
|
+
body: form,
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const error = await response.text();
|
|
81
|
+
throw new Error(`Upload failed: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
|
|
86
|
+
// Generate file URL
|
|
87
|
+
const fileUrl = `${this.config.url}/api/files/${this.config.collection}/${data.id}/${data.file}`;
|
|
88
|
+
console.log(` File URL: ${fileUrl}`);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
url: fileUrl,
|
|
93
|
+
recordId: data.id,
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: error.message,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|