sanjang 0.3.1 → 0.3.3
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/dashboard/app.js +304 -44
- package/dashboard/index.html +43 -6
- package/dashboard/style.css +306 -0
- package/dist/bin/sanjang.d.ts +1 -0
- package/dist/bin/sanjang.js +138 -0
- package/dist/lib/config.d.ts +19 -0
- package/dist/lib/config.js +318 -0
- package/dist/lib/engine/cache.d.ts +7 -0
- package/dist/lib/engine/cache.js +183 -0
- package/dist/lib/engine/config-hotfix.d.ts +7 -0
- package/dist/lib/engine/config-hotfix.js +129 -0
- package/dist/lib/engine/conflict.d.ts +12 -0
- package/dist/lib/engine/conflict.js +32 -0
- package/dist/lib/engine/diagnostics.d.ts +15 -0
- package/dist/lib/engine/diagnostics.js +58 -0
- package/dist/lib/engine/naming.d.ts +10 -0
- package/dist/lib/engine/naming.js +83 -0
- package/dist/lib/engine/ports.d.ts +9 -0
- package/dist/lib/engine/ports.js +55 -0
- package/dist/lib/engine/pr.d.ts +27 -0
- package/dist/lib/engine/pr.js +54 -0
- package/dist/lib/engine/process.d.ts +15 -0
- package/dist/lib/engine/process.js +250 -0
- package/dist/lib/engine/self-heal.d.ts +12 -0
- package/dist/lib/engine/self-heal.js +98 -0
- package/dist/lib/engine/smart-init.d.ts +7 -0
- package/dist/lib/engine/smart-init.js +138 -0
- package/dist/lib/engine/smart-pr.d.ts +19 -0
- package/dist/lib/engine/smart-pr.js +105 -0
- package/dist/lib/engine/snapshot.d.ts +10 -0
- package/dist/lib/engine/snapshot.js +35 -0
- package/dist/lib/engine/state.d.ts +7 -0
- package/dist/lib/engine/state.js +53 -0
- package/dist/lib/engine/suggest.d.ts +21 -0
- package/dist/lib/engine/suggest.js +121 -0
- package/dist/lib/engine/warp.d.ts +23 -0
- package/dist/lib/engine/warp.js +32 -0
- package/dist/lib/engine/watcher.d.ts +11 -0
- package/dist/lib/engine/watcher.js +43 -0
- package/dist/lib/engine/worktree.d.ts +13 -0
- package/dist/lib/engine/worktree.js +91 -0
- package/dist/lib/server.d.ts +20 -0
- package/dist/lib/server.js +1466 -0
- package/dist/lib/types.d.ts +109 -0
- package/dist/lib/types.js +2 -0
- package/package.json +5 -4
- package/bin/__tests__/sanjang.test.ts +0 -42
- package/bin/sanjang.js +0 -17
- package/bin/sanjang.ts +0 -144
- package/lib/config.ts +0 -337
- package/lib/engine/cache.ts +0 -218
- package/lib/engine/config-hotfix.ts +0 -161
- package/lib/engine/conflict.ts +0 -33
- package/lib/engine/diagnostics.ts +0 -81
- package/lib/engine/naming.ts +0 -93
- package/lib/engine/ports.ts +0 -61
- package/lib/engine/pr.ts +0 -71
- package/lib/engine/process.ts +0 -283
- package/lib/engine/self-heal.ts +0 -130
- package/lib/engine/smart-init.ts +0 -136
- package/lib/engine/smart-pr.ts +0 -130
- package/lib/engine/snapshot.ts +0 -45
- package/lib/engine/state.ts +0 -60
- package/lib/engine/suggest.ts +0 -169
- package/lib/engine/warp.ts +0 -47
- package/lib/engine/watcher.ts +0 -40
- package/lib/engine/worktree.ts +0 -100
- package/lib/server.ts +0 -1560
- package/lib/types.ts +0 -130
package/dashboard/style.css
CHANGED
|
@@ -2110,3 +2110,309 @@ header.hidden {
|
|
|
2110
2110
|
color: var(--text-muted, #666);
|
|
2111
2111
|
flex: 1;
|
|
2112
2112
|
}
|
|
2113
|
+
|
|
2114
|
+
/* ============================================================
|
|
2115
|
+
Basecamp Scene
|
|
2116
|
+
============================================================ */
|
|
2117
|
+
|
|
2118
|
+
.bc-scene {
|
|
2119
|
+
position: relative;
|
|
2120
|
+
height: 200px;
|
|
2121
|
+
background: linear-gradient(180deg, #080a10 0%, #0c0e14 60%, #12151e 100%);
|
|
2122
|
+
border-radius: 12px;
|
|
2123
|
+
overflow: hidden;
|
|
2124
|
+
border: 1px solid #1c2030;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
/* Stars */
|
|
2128
|
+
.bc-star {
|
|
2129
|
+
position: absolute;
|
|
2130
|
+
width: 4px;
|
|
2131
|
+
height: 4px;
|
|
2132
|
+
background: #fff;
|
|
2133
|
+
image-rendering: pixelated;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
.bc-star.twinkle {
|
|
2137
|
+
animation: twinkle 2s steps(2) infinite;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
.bc-star.twinkle-slow {
|
|
2141
|
+
animation: twinkle 3.5s steps(2) infinite 0.8s;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
@keyframes twinkle {
|
|
2145
|
+
0%, 100% { opacity: 0.6; }
|
|
2146
|
+
50% { opacity: 0.1; }
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/* Mountains */
|
|
2150
|
+
.bc-mountain {
|
|
2151
|
+
position: absolute;
|
|
2152
|
+
bottom: 0;
|
|
2153
|
+
left: 0;
|
|
2154
|
+
right: 0;
|
|
2155
|
+
image-rendering: pixelated;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
.bc-mountain-back {
|
|
2159
|
+
height: 100px;
|
|
2160
|
+
background: #1a1d2a;
|
|
2161
|
+
clip-path: polygon(
|
|
2162
|
+
0% 100%, 0% 70%, 5% 70%, 5% 60%, 10% 60%, 10% 50%, 15% 50%, 15% 40%,
|
|
2163
|
+
20% 40%, 20% 35%, 25% 35%, 25% 45%, 30% 45%, 30% 55%, 35% 55%, 35% 50%,
|
|
2164
|
+
40% 50%, 40% 35%, 45% 35%, 45% 25%, 50% 25%, 50% 20%, 55% 20%, 55% 30%,
|
|
2165
|
+
60% 30%, 60% 40%, 65% 40%, 65% 50%, 70% 50%, 70% 40%, 75% 40%, 75% 50%,
|
|
2166
|
+
80% 50%, 80% 60%, 85% 60%, 85% 70%, 90% 70%, 90% 80%, 95% 80%, 100% 80%,
|
|
2167
|
+
100% 100%
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
.bc-mountain-front {
|
|
2172
|
+
height: 80px;
|
|
2173
|
+
background: #12151e;
|
|
2174
|
+
clip-path: polygon(
|
|
2175
|
+
0% 100%, 0% 80%, 5% 80%, 5% 70%, 10% 70%, 10% 55%, 15% 55%, 15% 45%,
|
|
2176
|
+
20% 45%, 20% 40%, 25% 40%, 25% 50%, 30% 50%, 30% 60%, 35% 60%, 35% 55%,
|
|
2177
|
+
40% 55%, 40% 40%, 45% 40%, 45% 30%, 48% 30%, 48% 25%, 52% 25%, 52% 20%,
|
|
2178
|
+
55% 20%, 55% 30%, 58% 30%, 58% 40%, 62% 40%, 62% 50%, 65% 50%, 65% 45%,
|
|
2179
|
+
70% 45%, 70% 35%, 75% 35%, 75% 45%, 80% 45%, 80% 55%, 85% 55%, 85% 65%,
|
|
2180
|
+
90% 65%, 90% 75%, 95% 75%, 95% 85%, 100% 85%, 100% 100%
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
/* Snow caps */
|
|
2185
|
+
.bc-snow {
|
|
2186
|
+
position: absolute;
|
|
2187
|
+
height: 4px;
|
|
2188
|
+
background: rgba(255, 255, 255, 0.25);
|
|
2189
|
+
image-rendering: pixelated;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/* Campfire glow */
|
|
2193
|
+
.bc-glow {
|
|
2194
|
+
position: absolute;
|
|
2195
|
+
bottom: 0;
|
|
2196
|
+
left: 50%;
|
|
2197
|
+
transform: translateX(-50%);
|
|
2198
|
+
width: 80px;
|
|
2199
|
+
height: 40px;
|
|
2200
|
+
background: radial-gradient(ellipse at center bottom, rgba(255, 140, 50, 0.15) 0%, transparent 70%);
|
|
2201
|
+
animation: glow-pulse 2s ease infinite;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
@keyframes glow-pulse {
|
|
2205
|
+
0%, 100% { opacity: 1; }
|
|
2206
|
+
50% { opacity: 0.6; }
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/* Campfire pixel */
|
|
2210
|
+
.bc-campfire {
|
|
2211
|
+
position: absolute;
|
|
2212
|
+
bottom: 12px;
|
|
2213
|
+
left: 50%;
|
|
2214
|
+
transform: translateX(-50%);
|
|
2215
|
+
width: 4px;
|
|
2216
|
+
height: 4px;
|
|
2217
|
+
background: #8b4513;
|
|
2218
|
+
image-rendering: pixelated;
|
|
2219
|
+
box-shadow:
|
|
2220
|
+
-4px 0 0 #8b4513,
|
|
2221
|
+
4px 0 0 #8b4513,
|
|
2222
|
+
0 -4px 0 #ff6600,
|
|
2223
|
+
-4px -4px 0 #ff8800,
|
|
2224
|
+
4px -4px 0 #ff8800,
|
|
2225
|
+
0 -8px 0 #ffaa00,
|
|
2226
|
+
-4px -8px 0 #ff6600,
|
|
2227
|
+
4px -8px 0 #ff6600,
|
|
2228
|
+
0 -12px 0 #ffcc00;
|
|
2229
|
+
animation: bc-fire 0.4s steps(1) infinite;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
@keyframes bc-fire {
|
|
2233
|
+
0% {
|
|
2234
|
+
box-shadow:
|
|
2235
|
+
-4px 0 0 #8b4513,
|
|
2236
|
+
4px 0 0 #8b4513,
|
|
2237
|
+
0 -4px 0 #ff6600,
|
|
2238
|
+
-4px -4px 0 #ff8800,
|
|
2239
|
+
4px -4px 0 #ff8800,
|
|
2240
|
+
0 -8px 0 #ffaa00,
|
|
2241
|
+
-4px -8px 0 #ff6600,
|
|
2242
|
+
4px -8px 0 #ff6600,
|
|
2243
|
+
0 -12px 0 #ffcc00;
|
|
2244
|
+
}
|
|
2245
|
+
50% {
|
|
2246
|
+
box-shadow:
|
|
2247
|
+
-4px 0 0 #8b4513,
|
|
2248
|
+
4px 0 0 #8b4513,
|
|
2249
|
+
0 -4px 0 #ff8800,
|
|
2250
|
+
-4px -4px 0 #ff6600,
|
|
2251
|
+
4px -4px 0 #ffaa00,
|
|
2252
|
+
0 -8px 0 #ff6600,
|
|
2253
|
+
-4px -8px 0 #ffcc00,
|
|
2254
|
+
4px -8px 0 #ff8800,
|
|
2255
|
+
0 -12px 0 #ff6600;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
/* Sherpa pixel character (idle pose) */
|
|
2260
|
+
.bc-sherpa {
|
|
2261
|
+
position: absolute;
|
|
2262
|
+
bottom: 16px;
|
|
2263
|
+
left: calc(50% + 24px);
|
|
2264
|
+
width: 4px;
|
|
2265
|
+
height: 4px;
|
|
2266
|
+
background: transparent;
|
|
2267
|
+
image-rendering: pixelated;
|
|
2268
|
+
box-shadow:
|
|
2269
|
+
/* Hat */
|
|
2270
|
+
0 -20px 0 #e74c3c,
|
|
2271
|
+
-4px -20px 0 #e74c3c,
|
|
2272
|
+
4px -20px 0 #e74c3c,
|
|
2273
|
+
-4px -24px 0 #e74c3c,
|
|
2274
|
+
0 -24px 0 #e74c3c,
|
|
2275
|
+
4px -24px 0 #e74c3c,
|
|
2276
|
+
/* Face */
|
|
2277
|
+
-4px -16px 0 #f5c6a0,
|
|
2278
|
+
0 -16px 0 #f5c6a0,
|
|
2279
|
+
4px -16px 0 #f5c6a0,
|
|
2280
|
+
/* Body */
|
|
2281
|
+
-4px -12px 0 #3498db,
|
|
2282
|
+
0 -12px 0 #3498db,
|
|
2283
|
+
4px -12px 0 #3498db,
|
|
2284
|
+
-4px -8px 0 #3498db,
|
|
2285
|
+
0 -8px 0 #3498db,
|
|
2286
|
+
4px -8px 0 #3498db,
|
|
2287
|
+
/* Backpack */
|
|
2288
|
+
8px -12px 0 #8b6914,
|
|
2289
|
+
8px -8px 0 #8b6914,
|
|
2290
|
+
/* Legs */
|
|
2291
|
+
-4px -4px 0 #2c3e50,
|
|
2292
|
+
0 -4px 0 #2c3e50,
|
|
2293
|
+
-4px 0 0 #2c3e50,
|
|
2294
|
+
4px 0 0 #2c3e50;
|
|
2295
|
+
animation: sherpa-idle 1s steps(1) infinite;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
@keyframes sherpa-idle {
|
|
2299
|
+
0%, 100% {
|
|
2300
|
+
box-shadow:
|
|
2301
|
+
0 -20px 0 #e74c3c,
|
|
2302
|
+
-4px -20px 0 #e74c3c,
|
|
2303
|
+
4px -20px 0 #e74c3c,
|
|
2304
|
+
-4px -24px 0 #e74c3c,
|
|
2305
|
+
0 -24px 0 #e74c3c,
|
|
2306
|
+
4px -24px 0 #e74c3c,
|
|
2307
|
+
-4px -16px 0 #f5c6a0,
|
|
2308
|
+
0 -16px 0 #f5c6a0,
|
|
2309
|
+
4px -16px 0 #f5c6a0,
|
|
2310
|
+
-4px -12px 0 #3498db,
|
|
2311
|
+
0 -12px 0 #3498db,
|
|
2312
|
+
4px -12px 0 #3498db,
|
|
2313
|
+
-4px -8px 0 #3498db,
|
|
2314
|
+
0 -8px 0 #3498db,
|
|
2315
|
+
4px -8px 0 #3498db,
|
|
2316
|
+
8px -12px 0 #8b6914,
|
|
2317
|
+
8px -8px 0 #8b6914,
|
|
2318
|
+
-4px -4px 0 #2c3e50,
|
|
2319
|
+
0 -4px 0 #2c3e50,
|
|
2320
|
+
-4px 0 0 #2c3e50,
|
|
2321
|
+
4px 0 0 #2c3e50;
|
|
2322
|
+
}
|
|
2323
|
+
50% {
|
|
2324
|
+
box-shadow:
|
|
2325
|
+
0 -20px 0 #e74c3c,
|
|
2326
|
+
-4px -20px 0 #e74c3c,
|
|
2327
|
+
4px -20px 0 #e74c3c,
|
|
2328
|
+
-4px -24px 0 #e74c3c,
|
|
2329
|
+
0 -24px 0 #e74c3c,
|
|
2330
|
+
4px -24px 0 #e74c3c,
|
|
2331
|
+
-4px -16px 0 #f5c6a0,
|
|
2332
|
+
0 -16px 0 #f5c6a0,
|
|
2333
|
+
4px -16px 0 #f5c6a0,
|
|
2334
|
+
-4px -12px 0 #3498db,
|
|
2335
|
+
0 -12px 0 #3498db,
|
|
2336
|
+
4px -12px 0 #3498db,
|
|
2337
|
+
-4px -8px 0 #3498db,
|
|
2338
|
+
0 -8px 0 #3498db,
|
|
2339
|
+
4px -8px 0 #3498db,
|
|
2340
|
+
8px -12px 0 #8b6914,
|
|
2341
|
+
8px -8px 0 #8b6914,
|
|
2342
|
+
-4px -4px 0 #2c3e50,
|
|
2343
|
+
4px -4px 0 #2c3e50,
|
|
2344
|
+
-4px 0 0 #2c3e50,
|
|
2345
|
+
0 0 0 #2c3e50;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
/* Speech bubble */
|
|
2350
|
+
.bc-speech {
|
|
2351
|
+
position: absolute;
|
|
2352
|
+
bottom: 52px;
|
|
2353
|
+
left: calc(50% + 8px);
|
|
2354
|
+
background: #1c2030;
|
|
2355
|
+
border: 1px solid #2a2f42;
|
|
2356
|
+
border-radius: 8px;
|
|
2357
|
+
padding: 6px 10px;
|
|
2358
|
+
font-size: 11px;
|
|
2359
|
+
white-space: nowrap;
|
|
2360
|
+
color: var(--text-secondary);
|
|
2361
|
+
animation: bubble-float 3s ease-in-out infinite;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
.bc-speech::after {
|
|
2365
|
+
content: '';
|
|
2366
|
+
position: absolute;
|
|
2367
|
+
bottom: -6px;
|
|
2368
|
+
left: 20px;
|
|
2369
|
+
width: 0;
|
|
2370
|
+
height: 0;
|
|
2371
|
+
border-left: 6px solid transparent;
|
|
2372
|
+
border-right: 6px solid transparent;
|
|
2373
|
+
border-top: 6px solid #1c2030;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
@keyframes bubble-float {
|
|
2377
|
+
0%, 100% { transform: translateY(0); }
|
|
2378
|
+
50% { transform: translateY(-3px); }
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
.bc-speech.fade-out {
|
|
2382
|
+
opacity: 0;
|
|
2383
|
+
transition: opacity 0.5s;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
.bc-speech.fade-in {
|
|
2387
|
+
opacity: 1;
|
|
2388
|
+
transition: opacity 0.5s;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
/* ============================================================
|
|
2392
|
+
Activity Trail
|
|
2393
|
+
============================================================ */
|
|
2394
|
+
|
|
2395
|
+
#activity-trail {
|
|
2396
|
+
background: #0a0c11;
|
|
2397
|
+
border: 1px solid #1c2030;
|
|
2398
|
+
border-radius: 10px;
|
|
2399
|
+
overflow: hidden;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
.activity-info {
|
|
2403
|
+
padding: 12px 20px;
|
|
2404
|
+
display: flex;
|
|
2405
|
+
justify-content: space-between;
|
|
2406
|
+
align-items: center;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
.activity-streak {
|
|
2410
|
+
font-size: 13px;
|
|
2411
|
+
color: #8fbc8f;
|
|
2412
|
+
font-weight: 600;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
.activity-period {
|
|
2416
|
+
font-size: 11px;
|
|
2417
|
+
color: var(--text-muted);
|
|
2418
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const command = args[0];
|
|
7
|
+
// Parse options
|
|
8
|
+
let projectRoot = process.cwd();
|
|
9
|
+
let port = 4000;
|
|
10
|
+
let force = false;
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
if (args[i] === "--project" && args[i + 1]) {
|
|
13
|
+
projectRoot = resolve(args[i + 1]);
|
|
14
|
+
i++;
|
|
15
|
+
}
|
|
16
|
+
if (args[i] === "--port" && args[i + 1]) {
|
|
17
|
+
port = parseInt(args[i + 1]);
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
if (args[i] === "--force") {
|
|
21
|
+
force = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Find git root
|
|
25
|
+
try {
|
|
26
|
+
projectRoot = execSync("git rev-parse --show-toplevel", {
|
|
27
|
+
cwd: projectRoot,
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
console.error("⛰ 산장: git 저장소를 찾을 수 없습니다.");
|
|
34
|
+
console.error(" git 저장소 안에서 실행해주세요.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
if (command === "init") {
|
|
38
|
+
const { generateConfig, detectApps } = await import("../lib/config.js");
|
|
39
|
+
// Detect apps in subdirectories
|
|
40
|
+
const apps = detectApps(projectRoot);
|
|
41
|
+
let appDir;
|
|
42
|
+
if (apps.length >= 2) {
|
|
43
|
+
// Multi-app interview
|
|
44
|
+
console.log("");
|
|
45
|
+
console.log("⛰ 여러 앱이 감지되었습니다:");
|
|
46
|
+
for (let i = 0; i < apps.length; i++) {
|
|
47
|
+
console.log(` ${i + 1}) ${apps[i].dir}/\t(${apps[i].framework})`);
|
|
48
|
+
}
|
|
49
|
+
console.log("");
|
|
50
|
+
const { createInterface } = await import("node:readline");
|
|
51
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
52
|
+
const answer = await new Promise((resolve) => {
|
|
53
|
+
rl.question(" 어떤 앱을 띄울까요? [번호]: ", resolve);
|
|
54
|
+
});
|
|
55
|
+
rl.close();
|
|
56
|
+
const idx = parseInt(answer) - 1;
|
|
57
|
+
if (idx < 0 || idx >= apps.length || isNaN(idx)) {
|
|
58
|
+
console.error("⛰ 잘못된 선택입니다.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
appDir = apps[idx].dir;
|
|
62
|
+
console.log(` → ${appDir}/ (${apps[idx].framework}) 선택됨`);
|
|
63
|
+
}
|
|
64
|
+
else if (apps.length === 1) {
|
|
65
|
+
appDir = apps[0].dir;
|
|
66
|
+
}
|
|
67
|
+
const result = generateConfig(projectRoot, { appDir, force });
|
|
68
|
+
if (result.created) {
|
|
69
|
+
console.log(`⛰ ${result.message}`);
|
|
70
|
+
console.log(` 프레임워크: ${result.framework}`);
|
|
71
|
+
console.log(` 설정 파일: ${result.configPath}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(`⛰ ${result.message}`);
|
|
75
|
+
}
|
|
76
|
+
// Add .sanjang to .gitignore if not present
|
|
77
|
+
const gitignorePath = resolve(projectRoot, ".gitignore");
|
|
78
|
+
if (existsSync(gitignorePath)) {
|
|
79
|
+
const { readFileSync, appendFileSync } = await import("node:fs");
|
|
80
|
+
const content = readFileSync(gitignorePath, "utf8");
|
|
81
|
+
if (!content.includes(".sanjang")) {
|
|
82
|
+
appendFileSync(gitignorePath, "\n# Sanjang local dev camps\n.sanjang/\n");
|
|
83
|
+
console.log(" .gitignore에 .sanjang/ 추가됨");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Prebuild dependency cache
|
|
87
|
+
const { loadConfig } = await import("../lib/config.js");
|
|
88
|
+
const initConfig = await loadConfig(projectRoot);
|
|
89
|
+
if (initConfig.setup) {
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log(" 의존성 캐시를 빌드합니다...");
|
|
92
|
+
const { buildCache } = await import("../lib/engine/cache.js");
|
|
93
|
+
const cacheResult = await buildCache(projectRoot, initConfig, (msg) => {
|
|
94
|
+
console.log(` ${msg}`);
|
|
95
|
+
});
|
|
96
|
+
if (cacheResult.success) {
|
|
97
|
+
console.log(` 캐시 빌드 완료 ✓ (${(cacheResult.duration / 1000).toFixed(1)}초)`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(` ⚠️ 캐시 빌드 실패: ${cacheResult.error}`);
|
|
101
|
+
console.log(" 캠프 생성 시 일반 설치를 사용합니다.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Auto-start server unless --no-start
|
|
105
|
+
const noStart = args.includes("--no-start");
|
|
106
|
+
if (!noStart) {
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log(" 서버를 시작합니다...");
|
|
109
|
+
const { startServer } = await import("../lib/server.js");
|
|
110
|
+
await startServer(projectRoot, { port });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log("");
|
|
114
|
+
console.log(" 다음 단계: sanjang 또는 npx sanjang 으로 서버를 시작하세요.");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (command === "help" || command === "--help" || command === "-h") {
|
|
118
|
+
console.log(`
|
|
119
|
+
⛰ 산장 (Sanjang) — 바이브코더를 위한 로컬 개발 환경 매니저
|
|
120
|
+
|
|
121
|
+
사용법:
|
|
122
|
+
sanjang 서버 시작 (대시보드: http://localhost:4000)
|
|
123
|
+
sanjang init 프로젝트 분석 → sanjang.config.js 생성
|
|
124
|
+
sanjang help 이 도움말
|
|
125
|
+
|
|
126
|
+
옵션:
|
|
127
|
+
--port <N> 대시보드 포트 (기본: 4000)
|
|
128
|
+
--project <path> 프로젝트 경로 (기본: 현재 디렉토리)
|
|
129
|
+
--force 기존 설정을 덮어쓰고 다시 생성
|
|
130
|
+
|
|
131
|
+
자세히: https://github.com/paul-sherpas/sanjang
|
|
132
|
+
`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Default: start server
|
|
136
|
+
const { startServer } = await import("../lib/server.js");
|
|
137
|
+
await startServer(projectRoot, { port });
|
|
138
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DetectedApp, DetectedProject, GenerateConfigResult, SanjangConfig } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Load sanjang.config.js from project root.
|
|
4
|
+
* Returns merged config with defaults.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadConfig(projectRoot: string): Promise<SanjangConfig>;
|
|
7
|
+
/**
|
|
8
|
+
* Auto-detect project type and generate config.
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectProject(projectRoot: string): DetectedProject;
|
|
11
|
+
/**
|
|
12
|
+
* Scan first-level subdirectories for app candidates.
|
|
13
|
+
* Returns array of { dir, framework, detected } sorted by dir name.
|
|
14
|
+
*/
|
|
15
|
+
export declare function detectApps(projectRoot: string): DetectedApp[];
|
|
16
|
+
export declare function generateConfig(projectRoot: string, options?: {
|
|
17
|
+
appDir?: string;
|
|
18
|
+
force?: boolean;
|
|
19
|
+
}): GenerateConfigResult;
|