@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.
@@ -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
+ }