@highbeek/create-rnstarterkit 1.0.2-beta.3 → 1.0.2-beta.4

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.
@@ -15,7 +15,7 @@ async function generateApp(options) {
15
15
  const targetPath = path_1.default.join(process.cwd(), projectName);
16
16
  console.log("📂 Creating project...");
17
17
  await fs_extra_1.default.copy(templatePath, targetPath, {
18
- filter: (src) => shouldCopyPath(src),
18
+ filter: (src) => shouldCopyPath(src, templatePath),
19
19
  });
20
20
  await replaceProjectName(targetPath, projectName);
21
21
  await createStandardStructure(targetPath);
@@ -198,6 +198,7 @@ export const queryClient = new QueryClient();
198
198
  }
199
199
  async function configureApiClientTransport(targetPath, apiClient, apiClientType) {
200
200
  if (apiClient && apiClientType === "Axios") {
201
+ const templateRoot = await resolveTemplateRoot();
201
202
  await ensureDependencies(targetPath, {
202
203
  dependencies: {
203
204
  axios: "^1.12.2",
@@ -211,88 +212,9 @@ export const axiosClient = axios.create({
211
212
  timeout: 15000,
212
213
  });
213
214
  `);
214
- await fs_extra_1.default.writeFile(path_1.default.join(targetPath, "api/client.ts"), `import axios from "axios";
215
- import { API_BASE_URL } from "../config/env";
216
-
217
- export class ApiError extends Error {
218
- status: number;
219
- data: unknown;
220
-
221
- constructor(status: number, message: string, data: unknown) {
222
- super(message);
223
- this.name = "ApiError";
224
- this.status = status;
225
- this.data = data;
226
- }
227
- }
228
-
229
- type RequestOptions = {
230
- headers?: Record<string, string>;
231
- query?: Record<string, string | number | boolean | undefined>;
232
- token?: string | null;
233
- signal?: AbortSignal;
234
- };
235
-
236
- const client = axios.create({
237
- baseURL: API_BASE_URL,
238
- timeout: 15000,
239
- });
240
-
241
- function resolveErrorMessage(data: unknown, status: number) {
242
- if (
243
- typeof data === "object" &&
244
- data !== null &&
245
- "message" in data &&
246
- typeof (data as { message: unknown }).message === "string"
247
- ) {
248
- return (data as { message: string }).message;
249
- }
250
- return \`Request failed with status \${status}\`;
251
- }
252
-
253
- async function request<T>(
254
- method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
255
- path: string,
256
- body?: unknown,
257
- options: RequestOptions = {},
258
- ): Promise<T> {
259
- const { headers = {}, query, token, signal } = options;
260
- try {
261
- const response = await client.request<T>({
262
- method,
263
- url: path,
264
- data: body,
265
- params: query,
266
- signal,
267
- headers: {
268
- ...(token ? { Authorization: \`Bearer \${token}\` } : {}),
269
- ...headers,
270
- },
271
- });
272
- return response.data;
273
- } catch (error) {
274
- if (axios.isAxiosError(error)) {
275
- const status = error.response?.status ?? 500;
276
- const data = error.response?.data ?? null;
277
- throw new ApiError(status, resolveErrorMessage(data, status), data);
278
- }
279
- throw error;
280
- }
281
- }
282
-
283
- export const apiClient = {
284
- get: <T>(path: string, options?: RequestOptions) =>
285
- request<T>("GET", path, undefined, options),
286
- post: <T>(path: string, body?: unknown, options?: RequestOptions) =>
287
- request<T>("POST", path, body, options),
288
- put: <T>(path: string, body?: unknown, options?: RequestOptions) =>
289
- request<T>("PUT", path, body, options),
290
- patch: <T>(path: string, body?: unknown, options?: RequestOptions) =>
291
- request<T>("PATCH", path, body, options),
292
- delete: <T>(path: string, options?: RequestOptions) =>
293
- request<T>("DELETE", path, undefined, options),
294
- };
295
- `, "utf8");
215
+ await fs_extra_1.default.copy(path_1.default.join(templateRoot, "optional/apiClient/api/client.axios.ts"), path_1.default.join(targetPath, "api/client.ts"), {
216
+ overwrite: true,
217
+ });
296
218
  }
297
219
  }
298
220
  async function configureValidation(targetPath, validation) {
@@ -386,18 +308,20 @@ async function resolveTemplateRoot() {
386
308
  }
387
309
  throw new Error("Template root not found.");
388
310
  }
389
- function shouldCopyPath(src) {
390
- const normalized = src.replace(/\\/g, "/");
311
+ function shouldCopyPath(src, templatePath) {
312
+ const relative = path_1.default.relative(templatePath, src).replace(/\\/g, "/");
313
+ if (!relative || relative === ".")
314
+ return true;
391
315
  const blockedSegments = [
392
- "/.git/",
393
- "/node_modules/",
394
- "/ios/Pods/",
395
- "/android/.gradle/",
396
- "/vendor/bundle/",
316
+ ".git",
317
+ "node_modules",
318
+ "ios/Pods",
319
+ "android/.gradle",
320
+ "vendor/bundle",
397
321
  ];
398
- if (normalized.endsWith("/.git"))
399
- return false;
400
- return !blockedSegments.some((segment) => normalized.includes(segment));
322
+ return !blockedSegments.some((segment) => relative === segment ||
323
+ relative.startsWith(`${segment}/`) ||
324
+ relative.includes(`/${segment}/`));
401
325
  }
402
326
  function isTextFile(filePath) {
403
327
  const extension = path_1.default.extname(filePath).toLowerCase();
@@ -0,0 +1,124 @@
1
+ import axios, {
2
+ AxiosError,
3
+ AxiosRequestConfig,
4
+ AxiosResponse,
5
+ InternalAxiosRequestConfig,
6
+ } from "axios";
7
+ import { API_BASE_URL } from "../config/env";
8
+
9
+ export class ApiError extends Error {
10
+ status: number;
11
+ data: unknown;
12
+
13
+ constructor(status: number, message: string, data: unknown) {
14
+ super(message);
15
+ this.name = "ApiError";
16
+ this.status = status;
17
+ this.data = data;
18
+ }
19
+ }
20
+
21
+ type RequestOptions = {
22
+ headers?: Record<string, string>;
23
+ query?: Record<string, string | number | boolean | undefined>;
24
+ token?: string | null;
25
+ signal?: AbortSignal;
26
+ };
27
+
28
+ const client = axios.create({
29
+ baseURL: API_BASE_URL,
30
+ timeout: 15000,
31
+ });
32
+
33
+ let authTokenGetter: (() => string | null | undefined) | null = null;
34
+
35
+ export function setAuthTokenGetter(getter: (() => string | null | undefined) | null) {
36
+ authTokenGetter = getter;
37
+ }
38
+
39
+ function resolveErrorMessage(data: unknown, status: number) {
40
+ if (
41
+ typeof data === "object" &&
42
+ data !== null &&
43
+ "message" in data &&
44
+ typeof (data as { message: unknown }).message === "string"
45
+ ) {
46
+ return (data as { message: string }).message;
47
+ }
48
+ return `Request failed with status ${status}`;
49
+ }
50
+
51
+ export function addRequestInterceptor(
52
+ interceptor: (
53
+ config: InternalAxiosRequestConfig,
54
+ ) =>
55
+ | InternalAxiosRequestConfig
56
+ | Promise<InternalAxiosRequestConfig>,
57
+ ) {
58
+ return client.interceptors.request.use(interceptor);
59
+ }
60
+
61
+ export function addResponseInterceptor(
62
+ onFulfilled?: (
63
+ response: AxiosResponse,
64
+ ) => AxiosResponse | Promise<AxiosResponse>,
65
+ onRejected?: (error: AxiosError) => unknown,
66
+ ) {
67
+ return client.interceptors.response.use(onFulfilled, onRejected);
68
+ }
69
+
70
+ // Default auth + error interceptors.
71
+ addRequestInterceptor((config) => {
72
+ const token = authTokenGetter?.();
73
+ if (token) {
74
+ config.headers.Authorization = `Bearer ${token}`;
75
+ }
76
+ return config;
77
+ });
78
+
79
+ addResponseInterceptor(
80
+ (response) => response,
81
+ (error) => {
82
+ if (axios.isAxiosError(error)) {
83
+ const status = error.response?.status ?? 500;
84
+ const data = error.response?.data ?? null;
85
+ return Promise.reject(new ApiError(status, resolveErrorMessage(data, status), data));
86
+ }
87
+ return Promise.reject(error);
88
+ },
89
+ );
90
+
91
+ async function request<T>(
92
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
93
+ path: string,
94
+ body?: unknown,
95
+ options: RequestOptions = {},
96
+ ): Promise<T> {
97
+ const { headers = {}, query, token, signal } = options;
98
+ const requestConfig: AxiosRequestConfig = {
99
+ method,
100
+ url: path,
101
+ data: body,
102
+ params: query,
103
+ signal,
104
+ headers: {
105
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
106
+ ...headers,
107
+ },
108
+ };
109
+ const response = await client.request<T>(requestConfig);
110
+ return response.data;
111
+ }
112
+
113
+ export const apiClient = {
114
+ get: <T>(path: string, options?: RequestOptions) =>
115
+ request<T>("GET", path, undefined, options),
116
+ post: <T>(path: string, body?: unknown, options?: RequestOptions) =>
117
+ request<T>("POST", path, body, options),
118
+ put: <T>(path: string, body?: unknown, options?: RequestOptions) =>
119
+ request<T>("PUT", path, body, options),
120
+ patch: <T>(path: string, body?: unknown, options?: RequestOptions) =>
121
+ request<T>("PATCH", path, body, options),
122
+ delete: <T>(path: string, options?: RequestOptions) =>
123
+ request<T>("DELETE", path, undefined, options),
124
+ };
@@ -20,6 +20,41 @@ type RequestOptions = {
20
20
  signal?: AbortSignal;
21
21
  };
22
22
 
23
+ type RequestInterceptor = (
24
+ input: {
25
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
26
+ path: string;
27
+ options: RequestOptions;
28
+ },
29
+ ) =>
30
+ | {
31
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
32
+ path: string;
33
+ options: RequestOptions;
34
+ }
35
+ | Promise<{
36
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
37
+ path: string;
38
+ options: RequestOptions;
39
+ }>;
40
+ type ResponseInterceptor = (response: Response) => Response | Promise<Response>;
41
+
42
+ let authTokenGetter: (() => string | null | undefined) | null = null;
43
+ const requestInterceptors: RequestInterceptor[] = [];
44
+ const responseInterceptors: ResponseInterceptor[] = [];
45
+
46
+ export function setAuthTokenGetter(getter: (() => string | null | undefined) | null) {
47
+ authTokenGetter = getter;
48
+ }
49
+
50
+ export function addRequestInterceptor(interceptor: RequestInterceptor) {
51
+ requestInterceptors.push(interceptor);
52
+ }
53
+
54
+ export function addResponseInterceptor(interceptor: ResponseInterceptor) {
55
+ responseInterceptors.push(interceptor);
56
+ }
57
+
23
58
  function buildUrl(path: string, query?: RequestOptions["query"]) {
24
59
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
25
60
  const url = new URL(`${API_BASE_URL}${normalizedPath}`);
@@ -56,10 +91,15 @@ async function request<T>(
56
91
  path: string,
57
92
  options: RequestOptions = {},
58
93
  ): Promise<T> {
59
- const { headers = {}, query, body, token, signal } = options;
94
+ let requestInput = { method, path, options };
95
+ for (const interceptor of requestInterceptors) {
96
+ requestInput = await interceptor(requestInput);
97
+ }
60
98
 
61
- const response = await fetch(buildUrl(path, query), {
62
- method,
99
+ const { headers = {}, query, body, token, signal } = requestInput.options;
100
+
101
+ let response = await fetch(buildUrl(requestInput.path, query), {
102
+ method: requestInput.method,
63
103
  headers: {
64
104
  "Content-Type": "application/json",
65
105
  ...(token ? { Authorization: `Bearer ${token}` } : {}),
@@ -69,6 +109,10 @@ async function request<T>(
69
109
  signal,
70
110
  });
71
111
 
112
+ for (const interceptor of responseInterceptors) {
113
+ response = await interceptor(response);
114
+ }
115
+
72
116
  return parseResponse<T>(response);
73
117
  }
74
118
 
@@ -84,3 +128,15 @@ export const apiClient = {
84
128
  delete: <T>(path: string, options?: Omit<RequestOptions, "body">) =>
85
129
  request<T>("DELETE", path, options),
86
130
  };
131
+
132
+ // Default interceptor setup: attach bearer token if configured.
133
+ addRequestInterceptor((input) => {
134
+ const token = input.options.token ?? authTokenGetter?.() ?? null;
135
+ return {
136
+ ...input,
137
+ options: {
138
+ ...input.options,
139
+ token,
140
+ },
141
+ };
142
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highbeek/create-rnstarterkit",
3
- "version": "1.0.2-beta.3",
3
+ "version": "1.0.2-beta.4",
4
4
  "description": "CLI to scaffold production-ready React Native app structures.",
5
5
  "main": "dist/src/generators/appGenerator.js",
6
6
  "bin": {