react-native-cache-build-gitlab 1.0.8 → 1.0.10

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 @@
1
+ export { providerGitLab } from "./provider-gitlab/providerGitlab.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { providerGitLab } from "./provider-gitlab/providerGitlab.js";
@@ -0,0 +1,19 @@
1
+ import { type RemoteArtifact } from "@rock-js/tools";
2
+ export type GitLabRepoDetails = {
3
+ registryServer: string;
4
+ projectId: number | string;
5
+ token: string;
6
+ tokenHeader: "PRIVATE-TOKEN" | "JOB-TOKEN";
7
+ packageName: string;
8
+ };
9
+ export type GitLabArtifact = {
10
+ packageId: number;
11
+ fileId: number;
12
+ name: string;
13
+ version: string;
14
+ sizeInBytes: number;
15
+ createdAt: string;
16
+ downloadUrl: string;
17
+ };
18
+ export declare function fetchGitLabArtifactsByName(name: string | undefined, repo: GitLabRepoDetails, limit?: number, version?: string): Promise<GitLabArtifact[]>;
19
+ export declare function deleteGitLabArtifacts(artifacts: GitLabArtifact[], repo: GitLabRepoDetails, artifactNameForLog?: string): Promise<RemoteArtifact[]>;
@@ -0,0 +1,106 @@
1
+ import { cacheManager, color, colorLink, logger, RockError, } from "@rock-js/tools";
2
+ const PAGE_SIZE = 100;
3
+ function headersFor(repo) {
4
+ return {
5
+ [repo.tokenHeader]: repo.token,
6
+ Accept: "application/json",
7
+ };
8
+ }
9
+ async function httpJson(url, repo) {
10
+ const res = await fetch(url, { headers: headersFor(repo) });
11
+ if (!res.ok) {
12
+ const body = await res.text().catch(() => "");
13
+ const info = `${res.status} ${res.statusText}${body ? ` — ${body}` : ""}`;
14
+ if (res.status === 401 || res.status === 403) {
15
+ cacheManager.remove("gitlabToken");
16
+ throw new RockError(`GitLab auth failed.\nUpdate token under ${color.bold("remoteCacheProvider")} in ${colorLink("rock.config.mjs")}. Local uses ${color.bold("PRIVATE-TOKEN")}, CI uses ${color.bold("JOB-TOKEN")}.\nURL: ${colorLink(url)}\nError: ${info}`);
17
+ }
18
+ if (res.status === 404) {
19
+ throw new RockError(`GitLab 404.\nCheck registryServer/projectId/packageName or token perms.\nURL: ${colorLink(url)}`);
20
+ }
21
+ throw new RockError(`GitLab request failed: ${info}`);
22
+ }
23
+ return (await res.json());
24
+ }
25
+ function genericDownloadUrl(repo, packageName, version, fileName) {
26
+ const { registryServer, projectId } = repo;
27
+ return `${registryServer}/api/v4/projects/${encodeURIComponent(String(projectId))}/packages/generic/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}/${encodeURIComponent(fileName)}`;
28
+ }
29
+ export async function fetchGitLabArtifactsByName(name, repo, limit, version) {
30
+ if (!repo?.token) {
31
+ throw new RockError("Missing GitLab token. Valid repoDetails.token & tokenHeader pass.");
32
+ }
33
+ const perPage = Math.min(limit ?? PAGE_SIZE, PAGE_SIZE);
34
+ let page = 1;
35
+ const packages = [];
36
+ while (true) {
37
+ const url = `${repo.registryServer}/api/v4/projects/${encodeURIComponent(String(repo.projectId))}/packages` +
38
+ `?package_type=generic&per_page=${perPage}&page=${page}` +
39
+ (name ? `&package_name=${encodeURIComponent(name)}` : "");
40
+ const chunk = await httpJson(url, repo);
41
+ if (!chunk.length)
42
+ break;
43
+ packages.push(...chunk);
44
+ if (chunk.length < perPage)
45
+ break;
46
+ page += 1;
47
+ }
48
+ const artifacts = [];
49
+ for (const pkg of packages) {
50
+ if (pkg.package_type !== "generic" || !pkg.version)
51
+ continue;
52
+ // Don't filter by version here - we'll filter by filename later
53
+ let fPage = 1;
54
+ while (true) {
55
+ const filesUrl = `${repo.registryServer}/api/v4/projects/${encodeURIComponent(String(repo.projectId))}` +
56
+ `/packages/${pkg.id}/package_files?per_page=${PAGE_SIZE}&page=${fPage}`;
57
+ const files = await httpJson(filesUrl, repo);
58
+ if (!files.length)
59
+ break;
60
+ for (const file of files) {
61
+ // If version (fingerprint) is provided, filter by filename containing it
62
+ if (version && !file.file_name.includes(version))
63
+ continue;
64
+ artifacts.push({
65
+ packageId: pkg.id,
66
+ fileId: file.id,
67
+ name: pkg.name,
68
+ version: pkg.version,
69
+ sizeInBytes: file.size,
70
+ createdAt: file.created_at,
71
+ downloadUrl: genericDownloadUrl(repo, pkg.name, pkg.version, file.file_name),
72
+ });
73
+ }
74
+ if (files.length < PAGE_SIZE)
75
+ break;
76
+ fPage += 1;
77
+ }
78
+ }
79
+ artifacts.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
80
+ return artifacts;
81
+ }
82
+ export async function deleteGitLabArtifacts(artifacts, repo, artifactNameForLog) {
83
+ const deleted = [];
84
+ try {
85
+ for (const artifact of artifacts) {
86
+ const url = `${repo.registryServer}/api/v4/projects/${encodeURIComponent(String(repo.projectId))}/packages/${artifact.packageId}/package_files/${artifact.fileId}`;
87
+ const res = await fetch(url, {
88
+ method: "DELETE",
89
+ headers: headersFor(repo),
90
+ });
91
+ if (!res.ok) {
92
+ logger.warn(`Delete failed packageId=${artifact.packageId}, fileId=${artifact.fileId}: ${res.status} ${res.statusText}`);
93
+ continue;
94
+ }
95
+ deleted.push({
96
+ name: `${artifact.name}@${artifact.version}`,
97
+ url: artifact.downloadUrl,
98
+ id: String(artifact.fileId),
99
+ });
100
+ }
101
+ return deleted;
102
+ }
103
+ catch (error) {
104
+ throw new RockError(`Failed to delete GitLab artifacts${artifactNameForLog ? ` for "${artifactNameForLog}"` : ""}`, { cause: error });
105
+ }
106
+ }
@@ -0,0 +1,8 @@
1
+ import { GitLabRepoDetails } from "./artifacts";
2
+ export type DetectGitLabRepoInput = Partial<GitLabRepoDetails> & {
3
+ registryServer?: string;
4
+ projectId?: number | string;
5
+ token?: string;
6
+ tokenHeader?: "JOB-TOKEN" | "PRIVATE-TOKEN";
7
+ };
8
+ export declare function detectGitLabRepoDetails(override?: DetectGitLabRepoInput): Promise<GitLabRepoDetails>;
@@ -0,0 +1,21 @@
1
+ import { RockError } from "@rock-js/tools";
2
+ export async function detectGitLabRepoDetails(override) {
3
+ const registryServer = override?.registryServer;
4
+ const projectId = override?.projectId;
5
+ const token = override?.token;
6
+ const tokenHeader = override?.tokenHeader;
7
+ const packageName = override?.packageName;
8
+ if (!projectId) {
9
+ throw new RockError("Missing GitLab projectId. Set GITLAB_PROJECT_ID or pass in override.");
10
+ }
11
+ if (!token) {
12
+ throw new RockError("Missing GitLab token. Use CI_JOB_TOKEN or GITLAB_TOKEN.");
13
+ }
14
+ return {
15
+ registryServer,
16
+ projectId,
17
+ token,
18
+ tokenHeader,
19
+ packageName,
20
+ };
21
+ }
@@ -0,0 +1,36 @@
1
+ import type { RemoteArtifact, RemoteBuildCache } from "@rock-js/tools";
2
+ import { type GitLabRepoDetails } from "./artifacts.js";
3
+ export declare class GitLabBuildCache implements RemoteBuildCache {
4
+ name: string;
5
+ repoDetails: GitLabRepoDetails | null;
6
+ constructor(config?: {
7
+ registryServer: string;
8
+ projectId: number | string;
9
+ token?: string;
10
+ tokenHeader?: "JOB-TOKEN" | "PRIVATE-TOKEN";
11
+ packageName: string;
12
+ });
13
+ getRepoDetails(): Promise<GitLabRepoDetails>;
14
+ list({ artifactName, limit, }: {
15
+ artifactName?: string;
16
+ limit?: number;
17
+ }): Promise<RemoteArtifact[]>;
18
+ download({ artifactName, }: {
19
+ artifactName: string;
20
+ }): Promise<Response>;
21
+ delete({ artifactName, limit, skipLatest, }: {
22
+ artifactName: string;
23
+ limit?: number;
24
+ skipLatest?: boolean;
25
+ }): Promise<RemoteArtifact[]>;
26
+ upload(): Promise<RemoteArtifact & {
27
+ getResponse: (buffer: Buffer | ((registryServer: string) => Buffer), contentType?: string | undefined) => Response;
28
+ }>;
29
+ }
30
+ export declare const providerGitLab: (options?: {
31
+ registryServer: string;
32
+ projectId: number;
33
+ token?: string;
34
+ tokenHeader?: "PRIVATE-TOKEN" | "JOB-TOKEN";
35
+ packageName: string;
36
+ }) => () => RemoteBuildCache;
@@ -0,0 +1,78 @@
1
+ import { deleteGitLabArtifacts, fetchGitLabArtifactsByName, } from "./artifacts.js";
2
+ import { detectGitLabRepoDetails } from "./config.js";
3
+ function parseRockArtifact(artifactName) {
4
+ if (!artifactName)
5
+ return {};
6
+ const maybe = artifactName.split("-").pop();
7
+ if (/^[a-f0-9]{40}$/.test(maybe))
8
+ return { fingerprint: maybe };
9
+ return {};
10
+ }
11
+ export class GitLabBuildCache {
12
+ name = "GitLab";
13
+ repoDetails = null;
14
+ constructor(config) {
15
+ if (config) {
16
+ const token = config.token ?? process.env.CI_JOB_TOKEN;
17
+ const tokenHeader = config.tokenHeader ?? (process.env.CI ? "JOB-TOKEN" : "PRIVATE-TOKEN");
18
+ if (!token) {
19
+ throw new Error("GitLab token is required. Set CI_JOB_TOKEN (CI) or GITLAB_PRIVATE_TOKEN/PRIVATE_TOKEN (local).");
20
+ }
21
+ this.repoDetails = {
22
+ packageName: config.packageName,
23
+ registryServer: config.registryServer,
24
+ projectId: config.projectId,
25
+ token,
26
+ tokenHeader,
27
+ };
28
+ }
29
+ }
30
+ async getRepoDetails() {
31
+ if (!this.repoDetails) {
32
+ this.repoDetails = await detectGitLabRepoDetails();
33
+ }
34
+ return this.repoDetails;
35
+ }
36
+ async list({ artifactName, limit, }) {
37
+ const repo = await this.getRepoDetails();
38
+ const { fingerprint } = parseRockArtifact(artifactName);
39
+ const artifacts = await fetchGitLabArtifactsByName(repo.packageName, repo, limit, fingerprint);
40
+ return artifacts.map((artifact) => ({
41
+ name: `${artifact.name}@${artifact.version}`,
42
+ url: artifact.downloadUrl,
43
+ id: `${artifact.packageId}:${artifact.fileId}`,
44
+ }));
45
+ }
46
+ async download({ artifactName, }) {
47
+ const repo = await this.getRepoDetails();
48
+ const { fingerprint } = parseRockArtifact(artifactName);
49
+ if (!fingerprint)
50
+ throw new Error("artifactName is required");
51
+ const list = await fetchGitLabArtifactsByName(repo.packageName, repo, 1, fingerprint);
52
+ if (list.length === 0) {
53
+ throw new Error(`No GitLab artifact found for "${artifactName}"${fingerprint ? ` (version=${fingerprint})` : ""}`);
54
+ }
55
+ const url = list[0].downloadUrl;
56
+ return fetch(url, {
57
+ headers: {
58
+ [repo.tokenHeader]: repo.token,
59
+ "Accept-Encoding": "identity",
60
+ },
61
+ redirect: "follow",
62
+ });
63
+ }
64
+ async delete({ artifactName, limit, skipLatest, }) {
65
+ const repo = await this.getRepoDetails();
66
+ const { fingerprint } = parseRockArtifact(artifactName);
67
+ const artifacts = await fetchGitLabArtifactsByName(repo.packageName, repo, limit, fingerprint);
68
+ if (artifacts.length === 0) {
69
+ throw new Error(`No GitLab artifact found for "${artifactName}"${fingerprint ? ` (version=${fingerprint})` : ""}`);
70
+ }
71
+ const list = skipLatest ? artifacts.slice(1) : artifacts;
72
+ return deleteGitLabArtifacts(list, repo, artifactName);
73
+ }
74
+ async upload() {
75
+ throw new Error("Uploading via RemoteBuildCache is not yet implemented.");
76
+ }
77
+ }
78
+ export const providerGitLab = (options) => () => new GitLabBuildCache(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-cache-build-gitlab",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",