react-native-cache-build-gitlab 1.0.6 โ†’ 1.0.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/README.md CHANGED
@@ -4,14 +4,13 @@ GitLab Generic Package provider for [RockJS](https://rockjs.dev) with single pac
4
4
 
5
5
  ## Features
6
6
 
7
- ๐Ÿ“ฆ **Single Package Storage**: All native builds (iOS & Android) stored in one package.
7
+ ๐Ÿ“ฆ **Single Package Storage**: Stores all native builds (iOS & Android) in a single GitLab Generic Package.
8
8
 
9
9
  ๐Ÿงพ **Fingerprint-based Lookup**: Fast artifact retrieval by filename matching.
10
10
 
11
11
  ๐Ÿค–๏ธ **CI/CD Ready**: Works seamlessly with GitLab CI.
12
12
 
13
- ๐Ÿช™ **Cost Effective**: Reduces package registry clutter.
14
-
13
+ ๐Ÿช™ **Cost Effective**: Reduces package registry clutter by grouping artifacts.
15
14
 
16
15
  ## Installation
17
16
 
@@ -21,99 +20,100 @@ npm install react-native-cache-build-gitlab
21
20
  yarn add react-native-cache-build-gitlab
22
21
  ```
23
22
 
24
- ## โš ๏ธ Important
25
- > You must configure your project according to the [RockJS documentation](https://www.rockjs.dev/docs/cli/migrating-from-community-cli).
23
+ ## โš ๏ธ Prerequisites & Configuration
24
+
25
+ **Important:** You must configure your project according to the [RockJS documentation](https://www.rockjs.dev/docs/cli/migrating-from-community-cli).
26
26
 
27
- ## Before to use this provider, make sure you have:
27
+ Before using this provider, you need to set up authentication. This provider uses `CI_JOB_TOKEN` as the default environment variable for authentication.
28
28
 
29
- Create a GitLab Personal Access Token (PAT) and expose it locally as the environment variable **CI_JOB_TOKEN**.
30
29
  ### **1. Create a Personal Access Token**
31
- * Go to **GitLab** โ†’ **Edit profile** โ†’ **Personal access tokens**.
32
- * Click **Add new token**.
33
- * Give the token a name and expiration date (recommended).
34
- * Select scopes you need (commonly: read_api, read_package_registry, write_package_registry).
35
- * Create the token and copy it now โ€” GitLab shows it only once (**Note: You need copy it now because you won't be able to see it again**).
30
+
31
+ - Go to **GitLab** โ†’ **Edit profile** โ†’ **Personal access tokens**.
32
+ - Click **Add new token**.
33
+ - Give the token a name and expiration date (recommended).
34
+ - Select scopes you need (commonly: `read_api`, `read_repository`, `write_repository`).
35
+ - Create the token and copy it now โ€” GitLab shows it only once.
36
36
 
37
37
  ![img.png](assets/example_PAT.png)
38
38
 
39
39
  ### **2. Expose the token as an environment variable**
40
40
 
41
- **โš ๏ธ Important:** The provider expects the environment variable name to be ***CI_JOB_TOKEN***.
41
+ The provider expects the token in the `CI_JOB_TOKEN` environment variable
42
42
 
43
- * #### Temporary (current shell session only)
44
- ```bash
45
- export CI_JOB_TOKEN=glt-abc123...
46
- ````
43
+ #### Temporary (Current Shell)
47
44
 
48
- ### Persist permanently (macOS with Zsh)
49
- * Open your Zsh config:
50
- ```bash
51
- nano ~/.zshrc
52
- ```
53
- * Add the line:
54
45
  ```bash
55
46
  export CI_JOB_TOKEN=glt-abc123...
56
47
  ```
57
- * Reload your shell:
48
+
49
+ #### Permanent (macOS/Linux - Zsh):
50
+
58
51
  ```bash
52
+ echo 'export CI_JOB_TOKEN=glt-abc123...' >> ~/.zshrc
59
53
  source ~/.zshrc
60
54
  ```
61
- * Verify:
62
- ```bash
63
- echo $CI_JOB_TOKEN
64
- ```
55
+
56
+ **Note:** In GitLab CI pipelines, `CI_JOB_TOKEN` is automatically injected, so you don't need to configure it manually there.
65
57
 
66
58
  ## Usage
67
59
 
68
60
  In your `rock.config.mjs`:
69
61
 
70
62
  ```ts
71
- import {platformIOS} from "@rock-js/platform-ios";
72
- import {platformAndroid} from "@rock-js/platform-android";
73
- import {providerGitLab} from "react-native-cache-build-gitlab";
74
- import {pluginMetro} from "@rock-js/plugin-metro";
63
+ import { platformIOS } from "@rock-js/platform-ios";
64
+ import { platformAndroid } from "@rock-js/platform-android";
65
+ import { providerGitLab } from "react-native-cache-build-gitlab";
66
+ import { pluginMetro } from "@rock-js/plugin-metro";
75
67
 
76
68
  export default {
77
- bundler: pluginMetro(),
78
- platforms: {
79
- ios: platformIOS(),
80
- android: platformAndroid(),
81
- },
82
- remoteCacheProvider: providerGitLab({
83
- packageName: "mobile-artifacts",
84
- registryServer: "https://your-gitlab-instance.com",
85
- projectId: 1234,
86
- /*
87
- * token: default is process.env.CI_JOB_TOKEN
88
- * tokenHeader: default is process.env.CI ? "JOB-TOKEN" : "PRIVATE-TOKEN"
89
- * */
90
- }),
91
- fingerprint: {
92
- ignorePaths: [
93
- "ios/Podfile.lock",
94
- "ios/**/xcuserdata",
95
- "ios/**/project.pbxproj",
96
- // Add more paths to ignore as needed
97
- ],
98
- },
69
+ bundler: pluginMetro(),
70
+ platforms: {
71
+ ios: platformIOS(),
72
+ android: platformAndroid(),
73
+ },
74
+ remoteCacheProvider: providerGitLab({
75
+ packageName: "mobile-artifacts",
76
+ registryServer: "https://your-gitlab-instance.com",
77
+ projectId: 1234,
78
+ /*
79
+ * token: process.env.CI_JOB_TOKEN (default)
80
+ * tokenHeader: process.env.CI ? "JOB-TOKEN" : "PRIVATE-TOKEN" (default)
81
+ * */
82
+ }),
83
+ fingerprint: {
84
+ ignorePaths: [
85
+ "ios/Podfile.lock",
86
+ "ios/**/xcuserdata",
87
+ "ios/**/project.pbxproj",
88
+ // Add more paths to ignore as needed
89
+ ],
90
+ },
99
91
  };
100
92
  ```
101
93
 
102
94
  ## Configuration
103
95
 
104
- | Option | Type | Description |
105
- | ------------- | -------------------------------- |-------------------------------------------------------------------------------------------------|
106
- | `packageName` | `string` | Package name in GitLab Generic Package Registry |
107
- | `registryServer` | `string` | GitLab instance URL |
108
- | `projectId` | `number` | GitLab project ID |
96
+ | Option | Type | Description |
97
+ | ---------------- | -------- | ------------------------------------------------------------------------------------------ |
98
+ | `packageName` | `string` | Package name in GitLab Generic Package Registry |
99
+ | `registryServer` | `string` | GitLab instance URL |
100
+ | `projectId` | `number` | GitLab project ID |
101
+ | `token` | `string` | (Optional) Auth token. Defaults to `process.env.CI_JOB_TOKEN` |
102
+ | `tokenHeader` | `string` | (Optional) Auth token header. Defaults to `process.env.CI ? "JOB-TOKEN" : "PRIVATE-TOKEN"` |
109
103
 
110
104
  ## How It Works
111
105
 
112
106
  ### Upload (CI)
113
107
 
114
- All builds are uploaded to a **single package** with version `1.0.0` (Can change version if needed) at *Package Registry*.:
108
+ When your CI pipeline runs, you can use a script to upload the build artifacts to the GitLab Package Registry. (See the `example` folder in the repository for the `upload-cache-remote.sh` script).
115
109
 
116
- >You can use the script **upload-cache-remote.sh** from **example** to upload build cache to GitLab Package Registry.
110
+ ![img.png](assets/ci.png)
111
+
112
+ ### Registry Structure
113
+
114
+ All builds are uploaded to a single package version (e.g., 1.0.0).
115
+
116
+ ![img.png](assets/registry.png)
117
117
 
118
118
  ```
119
119
  mobile-artifacts@1.0.0/
@@ -122,13 +122,19 @@ mobile-artifacts@1.0.0/
122
122
  โ””โ”€โ”€ ...
123
123
  ```
124
124
 
125
- ### Download (Local/CI)
125
+ ### Download (Local)
126
126
 
127
- When running:
127
+ When you run `bun rock:run-ios` or `bun rock:run-android`:
128
128
 
129
- 1. Calculate project fingerprint
130
- 2. Search for file containing the fingerprint
131
- 3. Download and extract
129
+ 1. Rock calculates the **project fingerprint**.
130
+ 2. The provider searches the GitLab Package for a file containing that fingerprint.
131
+ 3. If found, it downloads and extracts the artifact automatically.
132
+
133
+ > Android:
134
+ > ![img.png](assets/run-android.png)
135
+
136
+ > iOS:
137
+ > ![img.png](assets/run-ios.png)
132
138
 
133
139
  ## GitLab CI Example
134
140
 
@@ -136,11 +142,33 @@ When running:
136
142
  build_android_cache:
137
143
  stage: build
138
144
  script:
139
- - bun run build:android --variant=devDebug
140
- - CACHE_DIR="$(ls -1dt .rock/cache/remote-build/rock-android-* | head -n1)"
141
- - sh scripts/upload-cache-remote.sh "${CACHE_DIR}" rock-android-devDebug-{FP}.zip android
145
+ - |
146
+ bun run rock:build-android
147
+
148
+ CACHE_DIR="$(ls -1dt .rock/cache/remote-build/${NAME_PREFIX}-* | head -n1 || true)"
149
+
150
+ if [[ -z "${CACHE_DIR}" || ! -d "${CACHE_DIR}" ]]; then
151
+ echo "No cache output under .rock/cache/remote-build/${NAME_PREFIX}-*"
152
+ exit 1
153
+ fi
154
+ echo "CACHE_DIR=${CACHE_DIR}"
155
+
156
+ FP="${CACHE_DIR##*-}"
157
+ echo "Fingerprint: ${FP}"
158
+
159
+ if ! compgen -G "${CACHE_DIR}"/*.apk > /dev/null; then
160
+ echo "No .apk found in ${CACHE_DIR}"
161
+ ls -la "${CACHE_DIR}" || true
162
+ exit 1
163
+ fi
164
+
165
+ FILE_UPLOAD_NAME="${NAME_PREFIX}-${FP}.zip"
166
+ echo "FILE_UPLOAD_NAME=${FILE_UPLOAD_NAME}"
167
+
168
+ sh scripts/upload-cache-remote.sh "${CACHE_DIR}" "${FILE_UPLOAD_NAME}" android
142
169
  ```
143
- > ***You can refer to the CI gitlab config in folder example***
170
+
171
+ > **_You can refer to the CI gitlab config in folder example_**
144
172
 
145
173
  ## License
146
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-cache-build-gitlab",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
@@ -15,6 +15,9 @@
15
15
  },
16
16
  "author": "Nguyแป…n Cรดng Tuแบฅn",
17
17
  "homepage": "https://github.com/congtuandevmobile",
18
+ "repository": {
19
+ "url": "https://github.com/congtuandevmobile/react-native-cache-build-gitlab"
20
+ },
18
21
  "license": "MIT",
19
22
  "devDependencies": {
20
23
  "@types/bun": "latest",
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export { providerGitLab } from "./provider-gitlab/providerGitlab.js";
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- export { providerGitLab } from "./provider-gitlab/providerGitlab.js";
@@ -1,19 +0,0 @@
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[]>;
@@ -1,106 +0,0 @@
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
- }
@@ -1,8 +0,0 @@
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>;
@@ -1,21 +0,0 @@
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
- }
@@ -1,36 +0,0 @@
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;
@@ -1,78 +0,0 @@
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);