@velox0/cerver 0.4.2 → 0.5.0
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 +37 -10
- package/lib/assets/embed.js +33 -8
- package/lib/codegen/dispatch_gen.js +4 -1
- package/lib/codegen/generator.js +90 -55
- package/lib/codegen/route_table.js +2 -2
- package/lib/commands/build.js +31 -8
- package/lib/commands/dev.js +9 -2
- package/lib/commands/new.js +667 -258
- package/lib/commands/run.js +8 -1
- package/lib/compiler/compile.js +9 -4
- package/lib/config.js +20 -1
- package/lib/parser/discover.js +6 -6
- package/package.json +2 -2
- package/runtime/cerver.h +10 -0
- package/runtime/http_writer.c +134 -27
- package/runtime/router.c +194 -10
- package/runtime/server.c +40 -6
- package/runtime/static.c +80 -64
- package/runtime/tests/runtime_tests.c +142 -13
- package/.github/workflows/ci.yml +0 -35
- package/.github/workflows/publish.yml +0 -50
- package/test/run.js +0 -355
package/runtime/static.c
CHANGED
|
@@ -85,6 +85,64 @@ static void add_cache_headers(cerver_response_t* res, const char* path) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/* ------------------------------------------------------------------ */
|
|
89
|
+
/* Helper to resolve fallback paths for directory/clean-URL routes. */
|
|
90
|
+
/* - "/" -> "/index.html" */
|
|
91
|
+
/* - "/page" or "/page/" -> "/page/page.html" */
|
|
92
|
+
/* ------------------------------------------------------------------ */
|
|
93
|
+
|
|
94
|
+
static void get_fallback_path(const char* path, char* out, size_t out_len) {
|
|
95
|
+
if (strcmp(path, "/") == 0 || strcmp(path, "") == 0) {
|
|
96
|
+
snprintf(out, out_len, "/index.html");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
size_t len = strlen(path);
|
|
101
|
+
while (len > 0 && path[len - 1] == '/') {
|
|
102
|
+
len--;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (len == 0) {
|
|
106
|
+
snprintf(out, out_len, "/index.html");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
int last_slash = -1;
|
|
111
|
+
for (int i = (int)len - 1; i >= 0; i--) {
|
|
112
|
+
if (path[i] == '/') {
|
|
113
|
+
last_slash = i;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
size_t segment_len = len - (last_slash + 1);
|
|
119
|
+
if (segment_len == 0) {
|
|
120
|
+
snprintf(out, out_len, "/index.html");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Extract the prefix and segment safely */
|
|
125
|
+
char prefix[1024];
|
|
126
|
+
if (len < sizeof(prefix)) {
|
|
127
|
+
memcpy(prefix, path, len);
|
|
128
|
+
prefix[len] = '\0';
|
|
129
|
+
} else {
|
|
130
|
+
snprintf(out, out_len, "/index.html");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
char segment[256];
|
|
135
|
+
if (segment_len < sizeof(segment)) {
|
|
136
|
+
memcpy(segment, path + last_slash + 1, segment_len);
|
|
137
|
+
segment[segment_len] = '\0';
|
|
138
|
+
} else {
|
|
139
|
+
snprintf(out, out_len, "/index.html");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
snprintf(out, out_len, "%s/%s.html", prefix, segment);
|
|
144
|
+
}
|
|
145
|
+
|
|
88
146
|
/* ------------------------------------------------------------------ */
|
|
89
147
|
/* Serve from embedded assets — hash-accelerated lookup */
|
|
90
148
|
/* ------------------------------------------------------------------ */
|
|
@@ -110,19 +168,15 @@ static int serve_embedded(cerver_server_t* srv, cerver_request_t* req, cerver_re
|
|
|
110
168
|
}
|
|
111
169
|
}
|
|
112
170
|
|
|
113
|
-
/* Try with
|
|
171
|
+
/* Try with fallback path (for directory-like paths) */
|
|
114
172
|
if (!found) {
|
|
115
|
-
char
|
|
116
|
-
|
|
117
|
-
if (plen > 0 && path[plen - 1] == '/') {
|
|
118
|
-
snprintf(index_path, sizeof(index_path), "%sindex.html", path);
|
|
119
|
-
} else {
|
|
120
|
-
snprintf(index_path, sizeof(index_path), "%s/index.html", path);
|
|
121
|
-
}
|
|
173
|
+
char fallback_path[CERVER_MAX_PATH];
|
|
174
|
+
get_fallback_path(path, fallback_path, sizeof(fallback_path));
|
|
122
175
|
|
|
123
|
-
uint32_t idx_hash = fnv1a(
|
|
176
|
+
uint32_t idx_hash = fnv1a(fallback_path);
|
|
124
177
|
for (int i = 0; i < srv->asset_count; i++) {
|
|
125
|
-
if (fnv1a(srv->assets[i].path) == idx_hash &&
|
|
178
|
+
if (fnv1a(srv->assets[i].path) == idx_hash &&
|
|
179
|
+
strcmp(srv->assets[i].path, fallback_path) == 0) {
|
|
126
180
|
found = &srv->assets[i];
|
|
127
181
|
break;
|
|
128
182
|
}
|
|
@@ -168,10 +222,12 @@ static int serve_filesystem(cerver_server_t* srv, cerver_request_t* req, cerver_
|
|
|
168
222
|
char full_path[CERVER_MAX_PATH * 2];
|
|
169
223
|
snprintf(full_path, sizeof(full_path), "%s%s", srv->public_dir, path);
|
|
170
224
|
|
|
171
|
-
/* Check if it's a directory — try
|
|
225
|
+
/* Check if it's a directory — try fallback path */
|
|
172
226
|
struct stat st;
|
|
173
227
|
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
174
|
-
|
|
228
|
+
char fallback_path[CERVER_MAX_PATH];
|
|
229
|
+
get_fallback_path(path, fallback_path, sizeof(fallback_path));
|
|
230
|
+
snprintf(full_path, sizeof(full_path), "%s%s", srv->public_dir, fallback_path);
|
|
175
231
|
if (stat(full_path, &st) != 0) return -1;
|
|
176
232
|
}
|
|
177
233
|
|
|
@@ -189,59 +245,19 @@ static int serve_filesystem(cerver_server_t* srv, cerver_request_t* req, cerver_
|
|
|
189
245
|
const char* mime = cerver_mime_from_path(full_path);
|
|
190
246
|
|
|
191
247
|
/*
|
|
192
|
-
* Use
|
|
193
|
-
* The
|
|
194
|
-
*
|
|
195
|
-
* but for simplicity we'll use read() for small files and mmap for large.
|
|
248
|
+
* Use sendfile for zero-copy filesystem static serving.
|
|
249
|
+
* The file is opened and its descriptor is stored in the response structure
|
|
250
|
+
* for streaming directly to the client socket in the writer.
|
|
196
251
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
/* Advise the kernel we'll read sequentially */
|
|
208
|
-
madvise(mapped, file_size, MADV_SEQUENTIAL);
|
|
209
|
-
|
|
210
|
-
res->status = 200;
|
|
211
|
-
res->content_type = mime;
|
|
212
|
-
res->body = (const char*)mapped;
|
|
213
|
-
res->body_len = file_size;
|
|
214
|
-
res->_body_owned = 2; /* Special flag: needs munmap, not free */
|
|
215
|
-
} else {
|
|
216
|
-
/* Small files: read into buffer (avoids mmap overhead) */
|
|
217
|
-
int fd = open(full_path, O_RDONLY);
|
|
218
|
-
if (fd < 0) return -1;
|
|
219
|
-
|
|
220
|
-
char* file_data = malloc(file_size);
|
|
221
|
-
if (!file_data) {
|
|
222
|
-
close(fd);
|
|
223
|
-
return -1;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
size_t total = 0;
|
|
227
|
-
while (total < file_size) {
|
|
228
|
-
ssize_t n = read(fd, file_data + total, file_size - total);
|
|
229
|
-
if (n <= 0) break;
|
|
230
|
-
total += (size_t)n;
|
|
231
|
-
}
|
|
232
|
-
close(fd);
|
|
233
|
-
|
|
234
|
-
if (total != file_size) {
|
|
235
|
-
free(file_data);
|
|
236
|
-
return -1;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
res->status = 200;
|
|
240
|
-
res->content_type = mime;
|
|
241
|
-
res->body = file_data;
|
|
242
|
-
res->body_len = file_size;
|
|
243
|
-
res->_body_owned = 1; /* malloc'd */
|
|
244
|
-
}
|
|
252
|
+
int fd = open(full_path, O_RDONLY);
|
|
253
|
+
if (fd < 0) return -1;
|
|
254
|
+
|
|
255
|
+
res->status = 200;
|
|
256
|
+
res->content_type = mime;
|
|
257
|
+
res->body = NULL;
|
|
258
|
+
res->body_len = file_size;
|
|
259
|
+
res->_body_owned = 3; /* Special flag: sendfile, close fd */
|
|
260
|
+
res->_file_fd = fd;
|
|
245
261
|
|
|
246
262
|
add_cache_headers(res, path);
|
|
247
263
|
|
|
@@ -224,6 +224,22 @@ static void test_route_match_mismatch_resets_params(void) {
|
|
|
224
224
|
MU_ASSERT_EQ_INT(0, req.params_count);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
static void test_route_match_multi_segment(void) {
|
|
228
|
+
cerver_route_t route;
|
|
229
|
+
route.method = "GET";
|
|
230
|
+
route.pattern = "/users/:id/profile";
|
|
231
|
+
route.handler = handler_a;
|
|
232
|
+
|
|
233
|
+
cerver_request_t req;
|
|
234
|
+
memset(&req, 0, sizeof(req));
|
|
235
|
+
strcpy(req.method, "GET");
|
|
236
|
+
strcpy(req.path, "/users/123/profile");
|
|
237
|
+
|
|
238
|
+
MU_ASSERT(cerver_route_match(&route, &req) == 1);
|
|
239
|
+
MU_ASSERT_EQ_INT(1, req.params_count);
|
|
240
|
+
MU_ASSERT_STREQ("123", cerver_req_param(&req, "id"));
|
|
241
|
+
}
|
|
242
|
+
|
|
227
243
|
static void test_request_header_helpers(void) {
|
|
228
244
|
cerver_request_t req;
|
|
229
245
|
memset(&req, 0, sizeof(req));
|
|
@@ -272,9 +288,9 @@ static void test_static_embedded_index_fallback(void) {
|
|
|
272
288
|
cerver_server_t srv;
|
|
273
289
|
cerver_init(&srv, 8080, 1);
|
|
274
290
|
|
|
275
|
-
static const unsigned char data[] = "
|
|
291
|
+
static const unsigned char data[] = "about";
|
|
276
292
|
cerver_asset_t assets[1];
|
|
277
|
-
assets[0].path = "/
|
|
293
|
+
assets[0].path = "/about/about.html";
|
|
278
294
|
assets[0].mime_type = "text/html";
|
|
279
295
|
assets[0].data = data;
|
|
280
296
|
assets[0].data_len = sizeof(data) - 1;
|
|
@@ -289,13 +305,91 @@ static void test_static_embedded_index_fallback(void) {
|
|
|
289
305
|
memset(&req, 0, sizeof(req));
|
|
290
306
|
memset(&res, 0, sizeof(res));
|
|
291
307
|
strcpy(req.method, "GET");
|
|
292
|
-
strcpy(req.path, "/
|
|
308
|
+
strcpy(req.path, "/about/");
|
|
293
309
|
|
|
294
310
|
MU_ASSERT_EQ_INT(0, cerver_serve_static(&srv, &req, &res));
|
|
295
311
|
MU_ASSERT(res.body == (const char*)data);
|
|
296
312
|
MU_ASSERT_STREQ("text/html", res.content_type);
|
|
297
313
|
}
|
|
298
314
|
|
|
315
|
+
static void test_static_filesystem_directory_fallback(void) {
|
|
316
|
+
char dir_template[] = "/tmp/cerver-test-XXXXXX";
|
|
317
|
+
char* dir = mkdtemp(dir_template);
|
|
318
|
+
MU_ASSERT(dir != NULL);
|
|
319
|
+
|
|
320
|
+
/* Create public/index.html (maps to /) */
|
|
321
|
+
char file_path[PATH_MAX];
|
|
322
|
+
snprintf(file_path, sizeof(file_path), "%s/index.html", dir);
|
|
323
|
+
MU_ASSERT_EQ_INT(0, write_file(file_path, "root index", 10));
|
|
324
|
+
|
|
325
|
+
/* Create public/about/about.html (maps to /about or /about/) */
|
|
326
|
+
char sub_dir[PATH_MAX];
|
|
327
|
+
snprintf(sub_dir, sizeof(sub_dir), "%s/about", dir);
|
|
328
|
+
MU_ASSERT_EQ_INT(0, mkdir(sub_dir, 0700));
|
|
329
|
+
|
|
330
|
+
char file_path2[PATH_MAX];
|
|
331
|
+
snprintf(file_path2, sizeof(file_path2), "%s/about/about.html", dir);
|
|
332
|
+
MU_ASSERT_EQ_INT(0, write_file(file_path2, "about content", 13));
|
|
333
|
+
|
|
334
|
+
/* Create public/about/index.html (should NOT map to /about, since index.html under subdirectory
|
|
335
|
+
* doesn't alias) */
|
|
336
|
+
char file_path3[PATH_MAX];
|
|
337
|
+
snprintf(file_path3, sizeof(file_path3), "%s/about/index.html", dir);
|
|
338
|
+
MU_ASSERT_EQ_INT(0, write_file(file_path3, "about index", 11));
|
|
339
|
+
|
|
340
|
+
cerver_server_t srv;
|
|
341
|
+
cerver_init(&srv, 8080, 1);
|
|
342
|
+
cerver_set_public_dir(&srv, dir);
|
|
343
|
+
|
|
344
|
+
/* Test / -> index.html fallback */
|
|
345
|
+
{
|
|
346
|
+
cerver_request_t req;
|
|
347
|
+
cerver_response_t res;
|
|
348
|
+
memset(&req, 0, sizeof(req));
|
|
349
|
+
memset(&res, 0, sizeof(res));
|
|
350
|
+
strcpy(req.method, "GET");
|
|
351
|
+
strcpy(req.path, "/");
|
|
352
|
+
|
|
353
|
+
MU_ASSERT_EQ_INT(0, cerver_serve_static(&srv, &req, &res));
|
|
354
|
+
MU_ASSERT_EQ_SIZE(10, res.body_len);
|
|
355
|
+
if (res._body_owned == 3 && res._file_fd >= 0) close(res._file_fd);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Test /about -> about/about.html fallback */
|
|
359
|
+
{
|
|
360
|
+
cerver_request_t req;
|
|
361
|
+
cerver_response_t res;
|
|
362
|
+
memset(&req, 0, sizeof(req));
|
|
363
|
+
memset(&res, 0, sizeof(res));
|
|
364
|
+
strcpy(req.method, "GET");
|
|
365
|
+
strcpy(req.path, "/about");
|
|
366
|
+
|
|
367
|
+
MU_ASSERT_EQ_INT(0, cerver_serve_static(&srv, &req, &res));
|
|
368
|
+
MU_ASSERT_EQ_SIZE(13, res.body_len);
|
|
369
|
+
if (res._body_owned == 3 && res._file_fd >= 0) close(res._file_fd);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Test /about/ -> about/about.html fallback */
|
|
373
|
+
{
|
|
374
|
+
cerver_request_t req;
|
|
375
|
+
cerver_response_t res;
|
|
376
|
+
memset(&req, 0, sizeof(req));
|
|
377
|
+
memset(&res, 0, sizeof(res));
|
|
378
|
+
strcpy(req.method, "GET");
|
|
379
|
+
strcpy(req.path, "/about/");
|
|
380
|
+
|
|
381
|
+
MU_ASSERT_EQ_INT(0, cerver_serve_static(&srv, &req, &res));
|
|
382
|
+
MU_ASSERT_EQ_SIZE(13, res.body_len);
|
|
383
|
+
if (res._body_owned == 3 && res._file_fd >= 0) close(res._file_fd);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
unlink(file_path);
|
|
387
|
+
unlink(file_path2);
|
|
388
|
+
unlink(file_path3);
|
|
389
|
+
rmdir(sub_dir);
|
|
390
|
+
rmdir(dir);
|
|
391
|
+
}
|
|
392
|
+
|
|
299
393
|
static void test_static_filesystem_small(void) {
|
|
300
394
|
char dir_template[] = "/tmp/cerver-test-XXXXXX";
|
|
301
395
|
char* dir = mkdtemp(dir_template);
|
|
@@ -318,10 +412,27 @@ static void test_static_filesystem_small(void) {
|
|
|
318
412
|
|
|
319
413
|
MU_ASSERT_EQ_INT(0, cerver_serve_static(&srv, &req, &res));
|
|
320
414
|
MU_ASSERT_EQ_SIZE(5, res.body_len);
|
|
321
|
-
MU_ASSERT(
|
|
322
|
-
MU_ASSERT_EQ_INT(
|
|
415
|
+
MU_ASSERT(res.body == NULL);
|
|
416
|
+
MU_ASSERT_EQ_INT(3, res._body_owned);
|
|
417
|
+
MU_ASSERT(res._file_fd >= 0);
|
|
418
|
+
|
|
419
|
+
int fds[2];
|
|
420
|
+
MU_ASSERT(pipe(fds) == 0);
|
|
421
|
+
MU_ASSERT_EQ_INT(0, cerver_write_response(fds[1], &res, 1));
|
|
422
|
+
close(fds[1]);
|
|
423
|
+
|
|
424
|
+
char out[1024];
|
|
425
|
+
ssize_t n = read_all(fds[0], out, sizeof(out));
|
|
426
|
+
MU_ASSERT(n > 0);
|
|
427
|
+
close(fds[0]);
|
|
428
|
+
|
|
429
|
+
MU_ASSERT(strstr(out, "HTTP/1.1 200 OK\r\n") != NULL);
|
|
430
|
+
MU_ASSERT(strstr(out, "Content-Length: 5\r\n") != NULL);
|
|
431
|
+
MU_ASSERT(strstr(out, "\r\nsmall") != NULL);
|
|
323
432
|
|
|
324
|
-
|
|
433
|
+
if (res._body_owned == 3 && res._file_fd >= 0) {
|
|
434
|
+
close(res._file_fd);
|
|
435
|
+
}
|
|
325
436
|
unlink(file_path);
|
|
326
437
|
rmdir(dir);
|
|
327
438
|
}
|
|
@@ -334,10 +445,10 @@ static void test_static_filesystem_large(void) {
|
|
|
334
445
|
char file_path[PATH_MAX];
|
|
335
446
|
snprintf(file_path, sizeof(file_path), "%s/large.bin", dir);
|
|
336
447
|
|
|
337
|
-
char* payload = (char*)malloc(
|
|
448
|
+
char* payload = (char*)malloc(32000);
|
|
338
449
|
MU_ASSERT(payload != NULL);
|
|
339
|
-
memset(payload, 'a',
|
|
340
|
-
MU_ASSERT_EQ_INT(0, write_file(file_path, payload,
|
|
450
|
+
memset(payload, 'a', 32000);
|
|
451
|
+
MU_ASSERT_EQ_INT(0, write_file(file_path, payload, 32000));
|
|
341
452
|
free(payload);
|
|
342
453
|
|
|
343
454
|
cerver_server_t srv;
|
|
@@ -352,11 +463,27 @@ static void test_static_filesystem_large(void) {
|
|
|
352
463
|
strcpy(req.path, "/large.bin");
|
|
353
464
|
|
|
354
465
|
MU_ASSERT_EQ_INT(0, cerver_serve_static(&srv, &req, &res));
|
|
355
|
-
MU_ASSERT_EQ_SIZE(
|
|
356
|
-
|
|
357
|
-
|
|
466
|
+
MU_ASSERT_EQ_SIZE(32000, res.body_len);
|
|
467
|
+
MU_ASSERT(res.body == NULL);
|
|
468
|
+
MU_ASSERT_EQ_INT(3, res._body_owned);
|
|
469
|
+
MU_ASSERT(res._file_fd >= 0);
|
|
470
|
+
|
|
471
|
+
int fds[2];
|
|
472
|
+
MU_ASSERT(pipe(fds) == 0);
|
|
473
|
+
MU_ASSERT_EQ_INT(0, cerver_write_response(fds[1], &res, 1));
|
|
474
|
+
close(fds[1]);
|
|
475
|
+
|
|
476
|
+
char out[35000];
|
|
477
|
+
ssize_t n = read_all(fds[0], out, sizeof(out));
|
|
478
|
+
MU_ASSERT(n > 0);
|
|
479
|
+
close(fds[0]);
|
|
358
480
|
|
|
359
|
-
|
|
481
|
+
MU_ASSERT(strstr(out, "HTTP/1.1 200 OK\r\n") != NULL);
|
|
482
|
+
MU_ASSERT(strstr(out, "Content-Length: 32000\r\n") != NULL);
|
|
483
|
+
|
|
484
|
+
if (res._body_owned == 3 && res._file_fd >= 0) {
|
|
485
|
+
close(res._file_fd);
|
|
486
|
+
}
|
|
360
487
|
unlink(file_path);
|
|
361
488
|
rmdir(dir);
|
|
362
489
|
}
|
|
@@ -402,11 +529,13 @@ int main(void) {
|
|
|
402
529
|
mu_run("write_response_force_close", test_write_response_force_close);
|
|
403
530
|
mu_run("route_match_and_dispatch", test_route_match_and_dispatch);
|
|
404
531
|
mu_run("route_match_mismatch_resets_params", test_route_match_mismatch_resets_params);
|
|
532
|
+
mu_run("route_match_multi_segment", test_route_match_multi_segment);
|
|
405
533
|
mu_run("request_header_helpers", test_request_header_helpers);
|
|
406
534
|
mu_run("static_embedded_prefers_br", test_static_embedded_prefers_br);
|
|
407
535
|
mu_run("static_embedded_index_fallback", test_static_embedded_index_fallback);
|
|
408
536
|
mu_run("static_filesystem_small", test_static_filesystem_small);
|
|
409
537
|
mu_run("static_filesystem_large", test_static_filesystem_large);
|
|
538
|
+
mu_run("static_filesystem_directory_fallback", test_static_filesystem_directory_fallback);
|
|
410
539
|
mu_run("static_rejects_unsafe_path", test_static_rejects_unsafe_path);
|
|
411
540
|
mu_run("stat_cache_store_lookup", test_stat_cache_store_lookup);
|
|
412
541
|
mu_run("cerver_init_fields", test_cerver_init_fields);
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
pull_request:
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
tests:
|
|
9
|
-
name: Tests
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout
|
|
14
|
-
uses: actions/checkout@v6
|
|
15
|
-
|
|
16
|
-
- name: Setup pnpm
|
|
17
|
-
uses: pnpm/action-setup@v6
|
|
18
|
-
with:
|
|
19
|
-
version: 10
|
|
20
|
-
run_install: false
|
|
21
|
-
|
|
22
|
-
- name: Setup Node.js
|
|
23
|
-
uses: actions/setup-node@v6
|
|
24
|
-
with:
|
|
25
|
-
node-version: 24
|
|
26
|
-
cache: pnpm
|
|
27
|
-
|
|
28
|
-
- name: Install dependencies
|
|
29
|
-
run: pnpm install --frozen-lockfile
|
|
30
|
-
|
|
31
|
-
- name: Run runtime tests
|
|
32
|
-
run: make test-runtime
|
|
33
|
-
|
|
34
|
-
- name: Run JS tests
|
|
35
|
-
run: pnpm test
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
name: Publish package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- "v*"
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: write
|
|
11
|
-
id-token: write
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
publish:
|
|
15
|
-
name: Publish to npm
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
|
|
18
|
-
steps:
|
|
19
|
-
- name: Checkout
|
|
20
|
-
uses: actions/checkout@v6
|
|
21
|
-
|
|
22
|
-
- name: Setup pnpm
|
|
23
|
-
uses: pnpm/action-setup@v6
|
|
24
|
-
with:
|
|
25
|
-
version: 10
|
|
26
|
-
run_install: false
|
|
27
|
-
|
|
28
|
-
- name: Setup Node.js
|
|
29
|
-
uses: actions/setup-node@v6
|
|
30
|
-
with:
|
|
31
|
-
node-version: 24
|
|
32
|
-
registry-url: https://registry.npmjs.org
|
|
33
|
-
cache: pnpm
|
|
34
|
-
|
|
35
|
-
- name: Install dependencies
|
|
36
|
-
run: pnpm install --frozen-lockfile
|
|
37
|
-
|
|
38
|
-
- name: Run runtime tests
|
|
39
|
-
run: make test-runtime
|
|
40
|
-
|
|
41
|
-
- name: Run tests
|
|
42
|
-
run: pnpm test
|
|
43
|
-
|
|
44
|
-
- name: Verify package contents
|
|
45
|
-
run: npm pack --dry-run
|
|
46
|
-
|
|
47
|
-
- name: Publish to npm
|
|
48
|
-
run: npm publish --access public --provenance
|
|
49
|
-
env:
|
|
50
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|