daiso 1.0.3 → 1.0.5
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 +218 -537
- package/dist/api/actionsProxy.js +82 -0
- package/dist/api/cgvHandlers.js +103 -16
- package/dist/api/daisoHandlers.js +127 -0
- package/dist/api/emart24Handlers.js +176 -0
- package/dist/api/gs25Handlers.js +256 -0
- package/dist/api/handlers.js +184 -108
- package/dist/api/healthChecks.js +332 -0
- package/dist/api/lottecinemaHandlers.js +155 -0
- package/dist/api/lottemartHandlers.js +128 -0
- package/dist/api/megaboxHandlers.js +81 -50
- package/dist/api/routes/cuRoutes.js +17 -0
- package/dist/api/routes/daisoRoutes.js +6 -1
- package/dist/api/routes/emart24Routes.js +22 -0
- package/dist/api/routes/gs25Routes.js +22 -0
- package/dist/api/routes/healthRoutes.js +44 -0
- package/dist/api/routes/lottecinemaRoutes.js +22 -0
- package/dist/api/routes/lottemartRoutes.js +18 -0
- package/dist/api/routes/oliveyoungRoutes.js +6 -1
- package/dist/api/routes/sevenelevenRoutes.js +32 -0
- package/dist/api/sevenelevenHandlers.js +200 -0
- package/dist/cli/args.js +54 -0
- package/dist/cli/commands/convenience.js +241 -0
- package/dist/cli/commands/daiso.js +96 -0
- package/dist/cli/constants.js +5 -0
- package/dist/cli/deps.js +48 -0
- package/dist/cli/http.js +98 -0
- package/dist/cli/interactiveFetch.js +31 -0
- package/dist/cli/interactiveItemSearch.js +259 -0
- package/dist/cli/interactivePrompt.js +49 -0
- package/dist/cli/interactiveTypes.js +4 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli.js +66 -181
- package/dist/cliHelp.js +216 -9
- package/dist/cliInteractive.js +174 -0
- package/dist/cliPicker.js +56 -0
- package/dist/cliRenderer.js +139 -0
- package/dist/index.js +147 -8
- package/dist/pages/openapi.js +2 -1
- package/dist/pages/openapiFullSpec.js +58 -0
- package/dist/pages/openapiResponse.js +28 -0
- package/dist/pages/openapiSpec.js +2 -41
- package/dist/pages/openapiSpecActions.js +109 -0
- package/dist/pages/openapiSpecComponents.js +30 -391
- package/dist/pages/openapiSpecComponentsCgv.js +24 -0
- package/dist/pages/openapiSpecComponentsCu.js +112 -0
- package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
- package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
- package/dist/pages/openapiSpecComponentsGs25.js +109 -0
- package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
- package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
- package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
- package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
- package/dist/pages/openapiSpecPathsCgv.js +66 -3
- package/dist/pages/openapiSpecPathsCu.js +138 -0
- package/dist/pages/openapiSpecPathsDaiso.js +302 -0
- package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
- package/dist/pages/openapiSpecPathsEmart24.js +223 -0
- package/dist/pages/openapiSpecPathsGs25.js +166 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +135 -0
- package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
- package/dist/pages/openapiSpecPathsMegabox.js +52 -3
- package/dist/pages/openapiSpecPathsOliveyoung.js +231 -0
- package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
- package/dist/pages/prompt.js +8 -402
- package/dist/pages/promptCoreText.js +278 -0
- package/dist/pages/promptReferenceText.js +131 -0
- package/dist/pages/promptRetailCinemaText.js +273 -0
- package/dist/services/cgv/client.js +6 -1
- package/dist/services/cgv/index.js +3 -3
- package/dist/services/cgv/location.js +235 -0
- package/dist/services/cgv/tools/findTheaters.js +30 -5
- package/dist/services/cgv/tools/getTimetable.js +42 -14
- package/dist/services/cgv/tools/searchMovies.js +40 -12
- package/dist/services/cgv/transport.js +0 -3
- package/dist/services/cu/api.js +11 -0
- package/dist/services/cu/client.js +297 -0
- package/dist/services/cu/index.js +21 -0
- package/dist/services/cu/tools/checkInventory.js +123 -0
- package/dist/services/cu/tools/findNearbyStores.js +49 -0
- package/dist/services/cu/types.js +4 -0
- package/dist/services/daiso/api.js +22 -2
- package/dist/services/daiso/client.js +59 -0
- package/dist/services/daiso/index.js +2 -0
- package/dist/services/daiso/product.js +17 -0
- package/dist/services/daiso/tools/checkInventory.js +76 -41
- package/dist/services/daiso/tools/findStores.js +17 -7
- package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
- package/dist/services/emart24/api.js +11 -0
- package/dist/services/emart24/client.js +173 -0
- package/dist/services/emart24/index.js +22 -0
- package/dist/services/emart24/inventoryLookup.js +134 -0
- package/dist/services/emart24/tools/checkInventory.js +54 -0
- package/dist/services/emart24/tools/findNearbyStores.js +72 -0
- package/dist/services/emart24/tools/searchProducts.js +63 -0
- package/dist/services/emart24/types.js +4 -0
- package/dist/services/gs25/api.js +9 -0
- package/dist/services/gs25/client.js +198 -0
- package/dist/services/gs25/index.js +22 -0
- package/dist/services/gs25/storeUtils.js +168 -0
- package/dist/services/gs25/tools/checkInventory.js +163 -0
- package/dist/services/gs25/tools/findNearbyStores.js +103 -0
- package/dist/services/gs25/tools/searchProducts.js +50 -0
- package/dist/services/gs25/types.js +4 -0
- package/dist/services/lottecinema/api.js +12 -0
- package/dist/services/lottecinema/client.js +161 -0
- package/dist/services/lottecinema/index.js +30 -0
- package/dist/services/lottecinema/location.js +191 -0
- package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
- package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
- package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
- package/dist/services/lottecinema/types.js +4 -0
- package/dist/services/lottemart/api.js +31 -0
- package/dist/services/lottemart/client.js +245 -0
- package/dist/services/lottemart/clientTypes.js +4 -0
- package/dist/services/lottemart/config.js +4 -0
- package/dist/services/lottemart/debug.js +92 -0
- package/dist/services/lottemart/geocode.js +49 -0
- package/dist/services/lottemart/index.js +30 -0
- package/dist/services/lottemart/keyword.js +29 -0
- package/dist/services/lottemart/parser.js +185 -0
- package/dist/services/lottemart/session.js +331 -0
- package/dist/services/lottemart/storeSearch.js +52 -0
- package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
- package/dist/services/lottemart/tools/searchProducts.js +62 -0
- package/dist/services/lottemart/types.js +4 -0
- package/dist/services/lottemart/zetta.js +105 -0
- package/dist/services/megabox/location.js +219 -0
- package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
- package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
- package/dist/services/megabox/tools/listNowShowing.js +31 -3
- package/dist/services/oliveyoung/api.js +2 -0
- package/dist/services/oliveyoung/client.js +150 -9
- package/dist/services/oliveyoung/index.js +3 -1
- package/dist/services/oliveyoung/tools/checkInventory.js +15 -4
- package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
- package/dist/services/seveneleven/api.js +15 -0
- package/dist/services/seveneleven/client.js +279 -0
- package/dist/services/seveneleven/index.js +30 -0
- package/dist/services/seveneleven/inventory.js +302 -0
- package/dist/services/seveneleven/productKeyword.js +141 -0
- package/dist/services/seveneleven/tools/checkInventory.js +82 -0
- package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
- package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
- package/dist/services/seveneleven/tools/searchProducts.js +56 -0
- package/dist/services/seveneleven/tools/searchStores.js +45 -0
- package/dist/services/seveneleven/types.js +4 -0
- package/dist/utils/cliInteractiveHelpers.js +63 -0
- package/dist/utils/daisoKeyword.js +30 -0
- package/dist/utils/http.js +16 -2
- package/openapi.json +1977 -734
- package/openapi.yaml +1430 -543
- package/package.json +28 -15
- package/dist/tools/checkInventory.js +0 -98
- package/dist/tools/findStores.js +0 -125
- package/dist/tools/getPriceInfo.js +0 -77
- package/dist/tools/searchProducts.js +0 -61
- package/dist/types/index.js +0 -4
|
@@ -1,63 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 메가박스 GET API 핸들러
|
|
3
3
|
*/
|
|
4
|
-
import { fetchMegaboxBookingList,
|
|
4
|
+
import { fetchMegaboxBookingList, toYyyymmdd } from '../services/megabox/client.js';
|
|
5
|
+
import { fetchMegaboxNearbyTheaters, resolveMegaboxNearestTheater } from '../services/megabox/location.js';
|
|
5
6
|
import { errorResponse, successResponse } from './response.js';
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
13
|
-
return 6371 * c;
|
|
7
|
+
function parseOptionalNumber(value) {
|
|
8
|
+
if (!value) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const parsed = Number.parseFloat(value);
|
|
12
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
14
13
|
}
|
|
15
14
|
/**
|
|
16
15
|
* 메가박스 주변 지점 조회 API 핸들러
|
|
17
16
|
* GET /api/megabox/theaters?lat={위도}&lng={경도}&playDate={YYYYMMDD}&areaCode={지역코드}
|
|
18
17
|
*/
|
|
19
18
|
export async function handleMegaboxFindNearbyTheaters(c) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
19
|
+
const keyword = c.req.query('keyword') || undefined;
|
|
20
|
+
const lat = parseOptionalNumber(c.req.query('lat'));
|
|
21
|
+
const lng = parseOptionalNumber(c.req.query('lng'));
|
|
22
22
|
const playDate = c.req.query('playDate') || toYyyymmdd();
|
|
23
|
-
const areaCode = c.req.query('areaCode') ||
|
|
23
|
+
const areaCode = c.req.query('areaCode') || undefined;
|
|
24
24
|
const limit = parseInt(c.req.query('limit') || '10');
|
|
25
25
|
const timeoutMs = parseInt(c.req.query('timeoutMs') || '15000');
|
|
26
26
|
try {
|
|
27
|
-
const
|
|
27
|
+
const result = await fetchMegaboxNearbyTheaters({
|
|
28
|
+
keyword,
|
|
29
|
+
latitude: lat,
|
|
30
|
+
longitude: lng,
|
|
28
31
|
playDate,
|
|
29
32
|
areaCode,
|
|
33
|
+
limit,
|
|
34
|
+
timeout: timeoutMs,
|
|
35
|
+
}, {
|
|
36
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
30
37
|
timeout: timeoutMs,
|
|
31
38
|
});
|
|
32
|
-
|
|
33
|
-
const merged = theaters
|
|
34
|
-
.map((theater, index) => {
|
|
35
|
-
const infoResult = infoResults[index];
|
|
36
|
-
if (infoResult.status !== 'fulfilled') {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
if (infoResult.value.latitude === null || infoResult.value.longitude === null) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
const distanceKm = calculateDistanceKm(lat, lng, infoResult.value.latitude, infoResult.value.longitude);
|
|
43
|
-
return {
|
|
44
|
-
theaterId: theater.theaterId,
|
|
45
|
-
theaterName: theater.theaterName,
|
|
46
|
-
address: infoResult.value.address,
|
|
47
|
-
latitude: infoResult.value.latitude,
|
|
48
|
-
longitude: infoResult.value.longitude,
|
|
49
|
-
distanceKm: Number(distanceKm.toFixed(2)),
|
|
50
|
-
};
|
|
51
|
-
})
|
|
52
|
-
.filter((theater) => theater !== null)
|
|
53
|
-
.sort((a, b) => a.distanceKm - b.distanceKm)
|
|
54
|
-
.slice(0, limit);
|
|
55
|
-
return successResponse(c, {
|
|
56
|
-
location: { latitude: lat, longitude: lng },
|
|
57
|
-
playDate,
|
|
58
|
-
areaCode,
|
|
59
|
-
theaters: merged,
|
|
60
|
-
}, { total: merged.length, pageSize: limit });
|
|
39
|
+
return successResponse(c, result, { total: result.count, pageSize: limit });
|
|
61
40
|
}
|
|
62
41
|
catch (error) {
|
|
63
42
|
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
@@ -70,16 +49,38 @@ export async function handleMegaboxFindNearbyTheaters(c) {
|
|
|
70
49
|
*/
|
|
71
50
|
export async function handleMegaboxListNowShowing(c) {
|
|
72
51
|
const playDate = c.req.query('playDate') || toYyyymmdd();
|
|
73
|
-
const
|
|
52
|
+
const keyword = c.req.query('keyword') || undefined;
|
|
53
|
+
const lat = parseOptionalNumber(c.req.query('lat'));
|
|
54
|
+
const lng = parseOptionalNumber(c.req.query('lng'));
|
|
55
|
+
let theaterId = c.req.query('theaterId') || undefined;
|
|
74
56
|
const movieId = c.req.query('movieId') || undefined;
|
|
75
|
-
const areaCode = c.req.query('areaCode') ||
|
|
57
|
+
const areaCode = c.req.query('areaCode') || undefined;
|
|
76
58
|
const timeoutMs = parseInt(c.req.query('timeoutMs') || '15000');
|
|
77
59
|
try {
|
|
60
|
+
let resolvedTheater = null;
|
|
61
|
+
let resolvedLocation = null;
|
|
62
|
+
if (!theaterId && (keyword || typeof lat === 'number' || typeof lng === 'number')) {
|
|
63
|
+
const resolved = await resolveMegaboxNearestTheater({
|
|
64
|
+
keyword,
|
|
65
|
+
latitude: lat,
|
|
66
|
+
longitude: lng,
|
|
67
|
+
areaCode,
|
|
68
|
+
playDate,
|
|
69
|
+
timeout: timeoutMs,
|
|
70
|
+
}, {
|
|
71
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
72
|
+
timeout: timeoutMs,
|
|
73
|
+
});
|
|
74
|
+
theaterId = resolved.theater?.theaterId;
|
|
75
|
+
resolvedTheater = resolved.theater;
|
|
76
|
+
resolvedLocation = resolved.location;
|
|
77
|
+
}
|
|
78
|
+
const resolvedAreaCode = resolvedLocation?.areaCode || areaCode || '11';
|
|
78
79
|
const result = await fetchMegaboxBookingList({
|
|
79
80
|
playDate,
|
|
80
81
|
theaterId,
|
|
81
82
|
movieId,
|
|
82
|
-
areaCode,
|
|
83
|
+
areaCode: resolvedAreaCode,
|
|
83
84
|
timeout: timeoutMs,
|
|
84
85
|
});
|
|
85
86
|
return successResponse(c, {
|
|
@@ -87,8 +88,12 @@ export async function handleMegaboxListNowShowing(c) {
|
|
|
87
88
|
filters: {
|
|
88
89
|
theaterId: theaterId || null,
|
|
89
90
|
movieId: movieId || null,
|
|
90
|
-
|
|
91
|
+
keyword: keyword || null,
|
|
92
|
+
latitude: lat ?? null,
|
|
93
|
+
longitude: lng ?? null,
|
|
94
|
+
areaCode: resolvedAreaCode,
|
|
91
95
|
},
|
|
96
|
+
resolvedTheater,
|
|
92
97
|
theaters: result.theaters,
|
|
93
98
|
movies: result.movies,
|
|
94
99
|
showtimes: result.showtimes,
|
|
@@ -105,17 +110,39 @@ export async function handleMegaboxListNowShowing(c) {
|
|
|
105
110
|
*/
|
|
106
111
|
export async function handleMegaboxGetRemainingSeats(c) {
|
|
107
112
|
const playDate = c.req.query('playDate') || toYyyymmdd();
|
|
108
|
-
const
|
|
113
|
+
const keyword = c.req.query('keyword') || undefined;
|
|
114
|
+
const lat = parseOptionalNumber(c.req.query('lat'));
|
|
115
|
+
const lng = parseOptionalNumber(c.req.query('lng'));
|
|
116
|
+
let theaterId = c.req.query('theaterId') || undefined;
|
|
109
117
|
const movieId = c.req.query('movieId') || undefined;
|
|
110
|
-
const areaCode = c.req.query('areaCode') ||
|
|
118
|
+
const areaCode = c.req.query('areaCode') || undefined;
|
|
111
119
|
const limit = parseInt(c.req.query('limit') || '50');
|
|
112
120
|
const timeoutMs = parseInt(c.req.query('timeoutMs') || '15000');
|
|
113
121
|
try {
|
|
122
|
+
let resolvedTheater = null;
|
|
123
|
+
let resolvedLocation = null;
|
|
124
|
+
if (!theaterId && (keyword || typeof lat === 'number' || typeof lng === 'number')) {
|
|
125
|
+
const resolved = await resolveMegaboxNearestTheater({
|
|
126
|
+
keyword,
|
|
127
|
+
latitude: lat,
|
|
128
|
+
longitude: lng,
|
|
129
|
+
areaCode,
|
|
130
|
+
playDate,
|
|
131
|
+
timeout: timeoutMs,
|
|
132
|
+
}, {
|
|
133
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
134
|
+
timeout: timeoutMs,
|
|
135
|
+
});
|
|
136
|
+
theaterId = resolved.theater?.theaterId;
|
|
137
|
+
resolvedTheater = resolved.theater;
|
|
138
|
+
resolvedLocation = resolved.location;
|
|
139
|
+
}
|
|
140
|
+
const resolvedAreaCode = resolvedLocation?.areaCode || areaCode || '11';
|
|
114
141
|
const { showtimes } = await fetchMegaboxBookingList({
|
|
115
142
|
playDate,
|
|
116
143
|
theaterId,
|
|
117
144
|
movieId,
|
|
118
|
-
areaCode,
|
|
145
|
+
areaCode: resolvedAreaCode,
|
|
119
146
|
timeout: timeoutMs,
|
|
120
147
|
});
|
|
121
148
|
const seats = showtimes
|
|
@@ -133,8 +160,12 @@ export async function handleMegaboxGetRemainingSeats(c) {
|
|
|
133
160
|
filters: {
|
|
134
161
|
theaterId: theaterId || null,
|
|
135
162
|
movieId: movieId || null,
|
|
136
|
-
|
|
163
|
+
keyword: keyword || null,
|
|
164
|
+
latitude: lat ?? null,
|
|
165
|
+
longitude: lng ?? null,
|
|
166
|
+
areaCode: resolvedAreaCode,
|
|
137
167
|
},
|
|
168
|
+
resolvedTheater,
|
|
138
169
|
seats,
|
|
139
170
|
}, { total: seats.length, pageSize: limit });
|
|
140
171
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CU GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleCuFindStores, handleCuCheckInventory } from '../handlers.js';
|
|
6
|
+
export function registerCuRoutes(app) {
|
|
7
|
+
app.get('/api/cu/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 60 * 24,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 5,
|
|
10
|
+
keyPrefix: 'cu-stores-v1',
|
|
11
|
+
}, () => handleCuFindStores(c)));
|
|
12
|
+
app.get('/api/cu/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 10,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'cu-inventory-v1',
|
|
16
|
+
}, () => handleCuCheckInventory(c)));
|
|
17
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 다이소 GET API 라우트 등록
|
|
3
3
|
*/
|
|
4
4
|
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
-
import { handleSearchProducts, handleGetProduct, handleFindStores, handleCheckInventory, } from '../handlers.js';
|
|
5
|
+
import { handleSearchProducts, handleGetProduct, handleFindStores, handleCheckInventory, handleGetDisplayLocation, } from '../handlers.js';
|
|
6
6
|
export function registerDaisoRoutes(app) {
|
|
7
7
|
app.get('/api/daiso/products', async (c) => withEdgeCache(c.req.url, {
|
|
8
8
|
ttlSeconds: 60 * 30,
|
|
@@ -24,4 +24,9 @@ export function registerDaisoRoutes(app) {
|
|
|
24
24
|
staleWhileRevalidateSeconds: 60,
|
|
25
25
|
keyPrefix: 'daiso-inventory-v1',
|
|
26
26
|
}, () => handleCheckInventory(c)));
|
|
27
|
+
app.get('/api/daiso/display-location', async (c) => withEdgeCache(c.req.url, {
|
|
28
|
+
ttlSeconds: 60 * 10,
|
|
29
|
+
staleWhileRevalidateSeconds: 60,
|
|
30
|
+
keyPrefix: 'daiso-display-location-v1',
|
|
31
|
+
}, () => handleGetDisplayLocation(c)));
|
|
27
32
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 이마트24 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleEmart24CheckInventory, handleEmart24FindStores, handleEmart24SearchProducts, } from '../emart24Handlers.js';
|
|
6
|
+
export function registerEmart24Routes(app) {
|
|
7
|
+
app.get('/api/emart24/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 30,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 3,
|
|
10
|
+
keyPrefix: 'emart24-stores-v1',
|
|
11
|
+
}, () => handleEmart24FindStores(c)));
|
|
12
|
+
app.get('/api/emart24/products', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 10,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'emart24-products-v1',
|
|
16
|
+
}, () => handleEmart24SearchProducts(c)));
|
|
17
|
+
app.get('/api/emart24/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 5,
|
|
19
|
+
staleWhileRevalidateSeconds: 60,
|
|
20
|
+
keyPrefix: 'emart24-inventory-v1',
|
|
21
|
+
}, () => handleEmart24CheckInventory(c)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GS25 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleGs25CheckInventory, handleGs25FindStores, handleGs25SearchProducts, } from '../gs25Handlers.js';
|
|
6
|
+
export function registerGs25Routes(app) {
|
|
7
|
+
app.get('/api/gs25/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 10,
|
|
9
|
+
staleWhileRevalidateSeconds: 60,
|
|
10
|
+
keyPrefix: 'gs25-stores-v1',
|
|
11
|
+
}, () => handleGs25FindStores(c)));
|
|
12
|
+
app.get('/api/gs25/products', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 3,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'gs25-products-v1',
|
|
16
|
+
}, () => handleGs25SearchProducts(c)));
|
|
17
|
+
app.get('/api/gs25/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 2,
|
|
19
|
+
staleWhileRevalidateSeconds: 30,
|
|
20
|
+
keyPrefix: 'gs25-inventory-v1',
|
|
21
|
+
}, () => handleGs25CheckInventory(c)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 운영용 상세 헬스 체크 라우트
|
|
3
|
+
*/
|
|
4
|
+
import { errorResponse } from '../response.js';
|
|
5
|
+
import { runHealthChecks } from '../healthChecks.js';
|
|
6
|
+
function parseBoolean(value) {
|
|
7
|
+
const normalized = (value || '').trim().toLowerCase();
|
|
8
|
+
return normalized === 'true' || normalized === '1' || normalized === 'y';
|
|
9
|
+
}
|
|
10
|
+
function readHealthCheckToken(headers) {
|
|
11
|
+
const authorization = headers.get('Authorization') || '';
|
|
12
|
+
if (authorization.toLowerCase().startsWith('bearer ')) {
|
|
13
|
+
return authorization.slice('bearer '.length).trim();
|
|
14
|
+
}
|
|
15
|
+
return (headers.get('x-health-check-key') || '').trim();
|
|
16
|
+
}
|
|
17
|
+
export function registerHealthRoutes(app) {
|
|
18
|
+
app.get('/api/health/checks', async (c) => {
|
|
19
|
+
const secret = c.env?.HEALTH_CHECK_SECRET?.trim();
|
|
20
|
+
if (!secret) {
|
|
21
|
+
return errorResponse(c, 'HEALTH_CHECK_SECRET_NOT_CONFIGURED', 'HEALTH_CHECK_SECRET이 설정되어 있지 않습니다.', 503);
|
|
22
|
+
}
|
|
23
|
+
if (readHealthCheckToken(c.req.raw.headers) !== secret) {
|
|
24
|
+
return errorResponse(c, 'UNAUTHORIZED_HEALTH_CHECK', '유효한 헬스 체크 시크릿 키가 필요합니다.', 401);
|
|
25
|
+
}
|
|
26
|
+
const mode = c.req.query('mode') === 'deep' ? 'deep' : 'quick';
|
|
27
|
+
const timeoutMs = Number.parseInt(c.req.query('timeoutMs') || '3000', 10);
|
|
28
|
+
const baseUrl = c.env?.HEALTH_CHECK_BASE_URL?.trim() || new URL(c.req.url).origin;
|
|
29
|
+
const fetchImpl = c.env?.HEALTH_CHECK_BASE_URL && c.env?.HEALTH_CHECK_TRANSPORT !== 'network'
|
|
30
|
+
? async (input, init) => app.fetch(new Request(input, init), c.env)
|
|
31
|
+
: undefined;
|
|
32
|
+
const result = await runHealthChecks({
|
|
33
|
+
baseUrl,
|
|
34
|
+
service: c.req.query('service') || undefined,
|
|
35
|
+
check: c.req.query('check') || undefined,
|
|
36
|
+
mode,
|
|
37
|
+
timeoutMs,
|
|
38
|
+
includeSamples: parseBoolean(c.req.query('includeSamples')),
|
|
39
|
+
fresh: parseBoolean(c.req.query('fresh')),
|
|
40
|
+
fetchImpl,
|
|
41
|
+
});
|
|
42
|
+
return c.json(result);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 롯데시네마 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleLotteCinemaFindNearbyTheaters, handleLotteCinemaGetRemainingSeats, handleLotteCinemaListNowShowing, } from '../lottecinemaHandlers.js';
|
|
6
|
+
export function registerLotteCinemaRoutes(app) {
|
|
7
|
+
app.get('/api/lottecinema/theaters', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 60 * 24,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 5,
|
|
10
|
+
keyPrefix: 'lottecinema-theaters-v1',
|
|
11
|
+
}, () => handleLotteCinemaFindNearbyTheaters(c)));
|
|
12
|
+
app.get('/api/lottecinema/movies', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 10,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'lottecinema-movies-v1',
|
|
16
|
+
}, () => handleLotteCinemaListNowShowing(c)));
|
|
17
|
+
app.get('/api/lottecinema/seats', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 3,
|
|
19
|
+
staleWhileRevalidateSeconds: 30,
|
|
20
|
+
keyPrefix: 'lottecinema-seats-v1',
|
|
21
|
+
}, () => handleLotteCinemaGetRemainingSeats(c)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 롯데마트 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleLotteMartDebug, handleLotteMartFindStores, handleLotteMartSearchProducts } from '../lottemartHandlers.js';
|
|
6
|
+
export function registerLotteMartRoutes(app) {
|
|
7
|
+
app.get('/api/lottemart/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 30,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 3,
|
|
10
|
+
keyPrefix: 'lottemart-stores-v1',
|
|
11
|
+
}, () => handleLotteMartFindStores(c)));
|
|
12
|
+
app.get('/api/lottemart/products', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 5,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'lottemart-products-v1',
|
|
16
|
+
}, () => handleLotteMartSearchProducts(c)));
|
|
17
|
+
app.get('/api/lottemart/debug', (c) => handleLotteMartDebug(c));
|
|
18
|
+
}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* 올리브영 GET API 라우트 등록
|
|
3
3
|
*/
|
|
4
4
|
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
-
import { handleOliveyoungFindStores, handleOliveyoungCheckInventory } from '../handlers.js';
|
|
5
|
+
import { handleOliveyoungFindStores, handleOliveyoungCheckInventory, handleOliveyoungSearchProducts, } from '../handlers.js';
|
|
6
6
|
export function registerOliveyoungRoutes(app) {
|
|
7
|
+
app.get('/api/oliveyoung/products', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 5,
|
|
9
|
+
staleWhileRevalidateSeconds: 60,
|
|
10
|
+
keyPrefix: 'oliveyoung-products-v1',
|
|
11
|
+
}, () => handleOliveyoungSearchProducts(c)));
|
|
7
12
|
app.get('/api/oliveyoung/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
13
|
ttlSeconds: 60 * 60 * 24,
|
|
9
14
|
staleWhileRevalidateSeconds: 60 * 5,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 세븐일레븐 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleSevenElevenCheckInventory, handleSevenElevenGetCatalogSnapshot, handleSevenElevenGetSearchPopwords, handleSevenElevenSearchStores, handleSevenElevenSearchProducts, } from '../sevenelevenHandlers.js';
|
|
6
|
+
export function registerSevenElevenRoutes(app) {
|
|
7
|
+
app.get('/api/seveneleven/products', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 3,
|
|
9
|
+
staleWhileRevalidateSeconds: 60,
|
|
10
|
+
keyPrefix: 'seveneleven-products-v1',
|
|
11
|
+
}, () => handleSevenElevenSearchProducts(c)));
|
|
12
|
+
app.get('/api/seveneleven/stores', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 3,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'seveneleven-stores-v1',
|
|
16
|
+
}, () => handleSevenElevenSearchStores(c)));
|
|
17
|
+
app.get('/api/seveneleven/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 10,
|
|
19
|
+
staleWhileRevalidateSeconds: 60,
|
|
20
|
+
keyPrefix: 'seveneleven-inventory-v1',
|
|
21
|
+
}, () => handleSevenElevenCheckInventory(c)));
|
|
22
|
+
app.get('/api/seveneleven/popwords', async (c) => withEdgeCache(c.req.url, {
|
|
23
|
+
ttlSeconds: 60 * 5,
|
|
24
|
+
staleWhileRevalidateSeconds: 60,
|
|
25
|
+
keyPrefix: 'seveneleven-popwords-v1',
|
|
26
|
+
}, () => handleSevenElevenGetSearchPopwords(c)));
|
|
27
|
+
app.get('/api/seveneleven/catalog', async (c) => withEdgeCache(c.req.url, {
|
|
28
|
+
ttlSeconds: 60 * 5,
|
|
29
|
+
staleWhileRevalidateSeconds: 60,
|
|
30
|
+
keyPrefix: 'seveneleven-catalog-v1',
|
|
31
|
+
}, () => handleSevenElevenGetCatalogSnapshot(c)));
|
|
32
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 세븐일레븐 GET API 핸들러
|
|
3
|
+
*/
|
|
4
|
+
/* c8 ignore start */
|
|
5
|
+
import { errorResponse, successResponse } from './response.js';
|
|
6
|
+
import { fetchSevenElevenStoresByKeyword, fetchSevenElevenCatalogSnapshot, fetchSevenElevenSearchPopwords, searchSevenElevenProducts, } from '../services/seveneleven/client.js';
|
|
7
|
+
import { checkSevenElevenInventory } from '../services/seveneleven/inventory.js';
|
|
8
|
+
function parseBoolean(value, defaultValue) {
|
|
9
|
+
if (typeof value !== 'string') {
|
|
10
|
+
return defaultValue;
|
|
11
|
+
}
|
|
12
|
+
const normalized = value.trim().toLowerCase();
|
|
13
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'y') {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'n') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return defaultValue;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 세븐일레븐 상품 검색 API 핸들러
|
|
23
|
+
* GET /api/seveneleven/products?query={검색어}&page={페이지}&size={개수}&sort={정렬}
|
|
24
|
+
*/
|
|
25
|
+
export async function handleSevenElevenSearchProducts(c) {
|
|
26
|
+
const query = c.req.query('query') || '';
|
|
27
|
+
const page = parseInt(c.req.query('page') || '1', 10);
|
|
28
|
+
const size = parseInt(c.req.query('size') || '20', 10);
|
|
29
|
+
const sort = c.req.query('sort') || 'recommend';
|
|
30
|
+
if (query.trim().length === 0) {
|
|
31
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(query)를 입력해주세요.');
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const result = await searchSevenElevenProducts({
|
|
35
|
+
query,
|
|
36
|
+
page,
|
|
37
|
+
size,
|
|
38
|
+
sort,
|
|
39
|
+
});
|
|
40
|
+
return successResponse(c, {
|
|
41
|
+
query: result.query,
|
|
42
|
+
count: result.products.length,
|
|
43
|
+
totalCount: result.totalCount,
|
|
44
|
+
collectionIds: result.collectionIds,
|
|
45
|
+
products: result.products,
|
|
46
|
+
}, {
|
|
47
|
+
total: result.totalCount,
|
|
48
|
+
page,
|
|
49
|
+
pageSize: size,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
54
|
+
return errorResponse(c, 'SEVENELEVEN_PRODUCT_SEARCH_FAILED', message, 500);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 세븐일레븐 매장 검색 API 핸들러
|
|
59
|
+
* GET /api/seveneleven/stores?keyword={검색어}&limit={개수}
|
|
60
|
+
*/
|
|
61
|
+
export async function handleSevenElevenSearchStores(c) {
|
|
62
|
+
const keyword = c.req.query('keyword') || '';
|
|
63
|
+
const limit = parseInt(c.req.query('limit') || '20', 10);
|
|
64
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 20;
|
|
65
|
+
if (keyword.trim().length === 0) {
|
|
66
|
+
return errorResponse(c, 'MISSING_KEYWORD', '매장 검색어(keyword)를 입력해주세요.');
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const result = await fetchSevenElevenStoresByKeyword({
|
|
70
|
+
keyword,
|
|
71
|
+
limit: safeLimit,
|
|
72
|
+
});
|
|
73
|
+
return successResponse(c, {
|
|
74
|
+
keyword: result.query,
|
|
75
|
+
count: result.stores.length,
|
|
76
|
+
totalCount: result.totalCount,
|
|
77
|
+
stores: result.stores,
|
|
78
|
+
}, {
|
|
79
|
+
total: result.totalCount,
|
|
80
|
+
page: 1,
|
|
81
|
+
pageSize: safeLimit,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
86
|
+
return errorResponse(c, 'SEVENELEVEN_STORE_SEARCH_FAILED', message, 500);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 세븐일레븐 재고 확인 API 핸들러
|
|
91
|
+
* GET /api/seveneleven/inventory?keyword={검색어}&storeKeyword={매장키워드}
|
|
92
|
+
*/
|
|
93
|
+
export async function handleSevenElevenCheckInventory(c) {
|
|
94
|
+
const keyword = c.req.query('keyword') || '';
|
|
95
|
+
const storeKeyword = c.req.query('storeKeyword') || '';
|
|
96
|
+
const storeLimit = parseInt(c.req.query('storeLimit') || '20', 10);
|
|
97
|
+
const timeoutMs = parseInt(c.req.query('timeoutMs') || '20000', 10);
|
|
98
|
+
const safeStoreLimit = Number.isFinite(storeLimit) && storeLimit > 0 ? storeLimit : 20;
|
|
99
|
+
const safeTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 20000;
|
|
100
|
+
if (keyword.trim().length === 0) {
|
|
101
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const result = await checkSevenElevenInventory({
|
|
105
|
+
productKeyword: keyword,
|
|
106
|
+
storeKeyword,
|
|
107
|
+
storeLimit: safeStoreLimit,
|
|
108
|
+
}, {
|
|
109
|
+
timeout: safeTimeoutMs,
|
|
110
|
+
});
|
|
111
|
+
const note = result.stockAvailable
|
|
112
|
+
? '실시간 재고 데이터가 포함되어 있습니다.'
|
|
113
|
+
: result.stockError
|
|
114
|
+
? `실시간 재고 API 호출에 실패했습니다: ${result.stockError.message}`
|
|
115
|
+
: '실시간 재고 API가 현재 제한되어 있어 매장 목록만 제공됩니다.';
|
|
116
|
+
return successResponse(c, {
|
|
117
|
+
keyword,
|
|
118
|
+
storeKeyword,
|
|
119
|
+
product: result.product,
|
|
120
|
+
stockAvailable: result.stockAvailable,
|
|
121
|
+
stockError: result.stockError,
|
|
122
|
+
note,
|
|
123
|
+
inventory: {
|
|
124
|
+
totalStoreCount: result.totalStoreCount,
|
|
125
|
+
inStockStoreCount: result.inStockStoreCount,
|
|
126
|
+
count: result.stores.length,
|
|
127
|
+
stores: result.stores,
|
|
128
|
+
},
|
|
129
|
+
}, {
|
|
130
|
+
total: result.totalStoreCount,
|
|
131
|
+
pageSize: safeStoreLimit,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
136
|
+
return errorResponse(c, 'SEVENELEVEN_INVENTORY_CHECK_FAILED', message, 500);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 세븐일레븐 인기 검색어 API 핸들러
|
|
141
|
+
* GET /api/seveneleven/popwords?label={라벨}
|
|
142
|
+
*/
|
|
143
|
+
export async function handleSevenElevenGetSearchPopwords(c) {
|
|
144
|
+
const label = c.req.query('label') || 'home';
|
|
145
|
+
try {
|
|
146
|
+
const keywords = await fetchSevenElevenSearchPopwords(label);
|
|
147
|
+
return successResponse(c, {
|
|
148
|
+
label,
|
|
149
|
+
available: keywords.length > 0,
|
|
150
|
+
count: keywords.length,
|
|
151
|
+
keywords,
|
|
152
|
+
note: keywords.length === 0
|
|
153
|
+
? '현재 응답에서 인기 검색어 목록을 찾지 못했습니다.'
|
|
154
|
+
: '홈 인기 검색어를 조회했습니다.',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
159
|
+
return errorResponse(c, 'SEVENELEVEN_POPWORD_FETCH_FAILED', message, 500);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 세븐일레븐 카탈로그 스냅샷 API 핸들러
|
|
164
|
+
* GET /api/seveneleven/catalog?includeIssues={bool}&includeExhibition={bool}&limit={개수}
|
|
165
|
+
*/
|
|
166
|
+
export async function handleSevenElevenGetCatalogSnapshot(c) {
|
|
167
|
+
const includeIssues = parseBoolean(c.req.query('includeIssues'), true);
|
|
168
|
+
const includeExhibition = parseBoolean(c.req.query('includeExhibition'), true);
|
|
169
|
+
const limit = parseInt(c.req.query('limit') || '20', 10);
|
|
170
|
+
try {
|
|
171
|
+
const result = await fetchSevenElevenCatalogSnapshot({
|
|
172
|
+
includeIssues,
|
|
173
|
+
includeExhibition,
|
|
174
|
+
});
|
|
175
|
+
return successResponse(c, {
|
|
176
|
+
options: {
|
|
177
|
+
includeIssues,
|
|
178
|
+
includeExhibition,
|
|
179
|
+
limit,
|
|
180
|
+
},
|
|
181
|
+
pages: {
|
|
182
|
+
totalCount: result.pages.length,
|
|
183
|
+
items: result.pages.slice(0, limit),
|
|
184
|
+
},
|
|
185
|
+
issues: {
|
|
186
|
+
totalCount: result.issues.length,
|
|
187
|
+
items: result.issues.slice(0, limit),
|
|
188
|
+
},
|
|
189
|
+
exhibitions: {
|
|
190
|
+
totalCount: result.exhibitions.length,
|
|
191
|
+
items: result.exhibitions.slice(0, limit),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
197
|
+
return errorResponse(c, 'SEVENELEVEN_CATALOG_FETCH_FAILED', message, 500);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/* c8 ignore stop */
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 인자 파싱 및 URL 조작 유틸리티
|
|
3
|
+
*/
|
|
4
|
+
import { DEFAULT_BASE_URL } from './constants.js';
|
|
5
|
+
export function parseCliArgs(args) {
|
|
6
|
+
const positionals = [];
|
|
7
|
+
const options = {};
|
|
8
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
9
|
+
const token = args[index];
|
|
10
|
+
if (!token.startsWith('--')) {
|
|
11
|
+
positionals.push(token);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const withoutPrefix = token.slice(2);
|
|
15
|
+
const equalIndex = withoutPrefix.indexOf('=');
|
|
16
|
+
if (equalIndex >= 0) {
|
|
17
|
+
const key = withoutPrefix.slice(0, equalIndex);
|
|
18
|
+
const value = withoutPrefix.slice(equalIndex + 1);
|
|
19
|
+
options[key] = value;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const key = withoutPrefix;
|
|
23
|
+
const nextValue = args[index + 1];
|
|
24
|
+
if (!nextValue || nextValue.startsWith('--')) {
|
|
25
|
+
options[key] = 'true';
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
options[key] = nextValue;
|
|
29
|
+
index += 1;
|
|
30
|
+
}
|
|
31
|
+
return { positionals, options };
|
|
32
|
+
}
|
|
33
|
+
export function toUrl(pathOrUrl) {
|
|
34
|
+
if (pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')) {
|
|
35
|
+
return new URL(pathOrUrl);
|
|
36
|
+
}
|
|
37
|
+
const normalizedPath = pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`;
|
|
38
|
+
return new URL(normalizedPath, DEFAULT_BASE_URL);
|
|
39
|
+
}
|
|
40
|
+
export function applyOptionsToQuery(url, options) {
|
|
41
|
+
for (const [key, value] of Object.entries(options)) {
|
|
42
|
+
url.searchParams.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function toQueryOptions(options) {
|
|
46
|
+
const queryOptions = {};
|
|
47
|
+
for (const [key, value] of Object.entries(options)) {
|
|
48
|
+
if (key === 'help' || key === 'json') {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
queryOptions[key] = value;
|
|
52
|
+
}
|
|
53
|
+
return queryOptions;
|
|
54
|
+
}
|