acrylblends 0.6.2 → 0.6.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.
- package/README.md +5 -1
- package/docs/framework/README.md +3 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
- package/docs/framework/getting-started/basic.md +1 -0
- package/docs/framework/getting-started/desktop.md +41 -0
- package/docs/framework/guides/jsx-dom.md +0 -3
- package/docs/framework/guides/jsx.md +24 -3
- package/docs/framework/helpers/css.md +0 -3
- package/docs/framework/helpers/ssg.md +3 -6
- package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
- package/docs/framework/upstream.json +3 -3
- package/lib/accepts.d.ts +1 -1
- package/lib/acryl-base.d.ts +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/{app-BdrVHBlJ.d.ts → app-D8riatvv.d.ts} +1 -1
- package/lib/base-BpQy2Rk7.d.ts +783 -0
- package/lib/base-ddiVmiE7.js +721 -0
- package/lib/basic-auth.d.ts +1 -1
- package/lib/bearer-auth.d.ts +1 -1
- package/lib/body-limit.d.ts +1 -1
- package/lib/cache.d.ts +1 -1
- package/lib/cache.js +4 -4
- package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
- package/lib/client.d.ts +2 -2
- package/lib/client.js +2 -1
- package/lib/combine.d.ts +1 -1
- package/lib/common-D8Z4JRCw.d.ts +46 -0
- package/lib/components-0o0CBYNv.js +268 -0
- package/lib/components-DdI7-ns7.js +24 -0
- package/lib/compress.d.ts +1 -1
- package/lib/conninfo.d.ts +2 -2
- package/lib/constants-BR9jM9BC.js +9 -0
- package/lib/{context-B5Y1lLha.d.ts → context-DS3fYyG-.d.ts} +2 -2
- package/lib/context-DxFdY_un.d.ts +250 -0
- package/lib/context-storage.d.ts +1 -1
- package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
- package/lib/cookie.d.ts +3 -3
- package/lib/cors.d.ts +1 -1
- package/lib/csrf.d.ts +1 -1
- package/lib/css-D2jb7X8b.js +320 -0
- package/lib/css.d.ts +82 -0
- package/lib/css.js +134 -0
- package/lib/dev.d.ts +2 -2
- package/lib/etag.d.ts +1 -1
- package/lib/factory.d.ts +2 -2
- package/lib/hooks-BMw6BAyP.js +762 -0
- package/lib/html-SllAe0YT.d.ts +46 -0
- package/lib/html.d.ts +1 -1
- package/lib/{index-m12zcmph.d.ts → index-Bb7YJKCq.d.ts} +3 -3
- package/lib/{index-Kqou8Fjh.d.ts → index-BvFh1Gfx.d.ts} +2 -2
- package/lib/index-C850MeBA.d.ts +94 -0
- package/lib/{index-DtEoak1i.d.ts → index-CTy4N7ng.d.ts} +2 -2
- package/lib/index.d.ts +4 -4
- package/lib/ip-restriction.d.ts +2 -2
- package/lib/jsx/dom/client.d.ts +30 -0
- package/lib/jsx/dom/client.js +49 -0
- package/lib/jsx/dom/css.d.ts +84 -0
- package/lib/jsx/dom/css.js +2 -0
- package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
- package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
- package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
- package/lib/jsx/dom/jsx-runtime.js +2 -0
- package/lib/jsx/dom/server.d.ts +105 -0
- package/lib/jsx/dom/server.js +37 -0
- package/lib/jsx/dom.d.ts +5 -0
- package/lib/jsx/dom.js +74 -0
- package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
- package/lib/jsx/jsx-dev-runtime.js +14 -0
- package/lib/jsx/jsx-runtime.d.ts +10 -0
- package/lib/jsx/jsx-runtime.js +30 -0
- package/lib/jsx/streaming.d.ts +2 -0
- package/lib/jsx/streaming.js +2 -0
- package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
- package/lib/jsx-dev-runtime-D18Y35TI.js +354 -0
- package/lib/jsx-renderer.d.ts +75 -0
- package/lib/jsx-renderer.js +99 -0
- package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
- package/lib/jsx.d.ts +3 -0
- package/lib/jsx.js +47 -0
- package/lib/jwk.d.ts +4 -4
- package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
- package/lib/jwt.d.ts +4 -4
- package/lib/language.d.ts +1 -1
- package/lib/logger.d.ts +1 -1
- package/lib/loopback-only.d.ts +1 -1
- package/lib/method-not-allowed.d.ts +2 -2
- package/lib/method-override.d.ts +2 -2
- package/lib/mount.d.ts +1 -1
- package/lib/node-server/conninfo.d.ts +1 -1
- package/lib/node-server/serve-static.d.ts +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -1
- package/lib/node-server.js +1 -1
- package/lib/powered-by.d.ts +1 -1
- package/lib/pretty-json.d.ts +1 -1
- package/lib/proxy.d.ts +1 -1
- package/lib/quick.d.ts +1 -1
- package/lib/request-id.d.ts +1 -1
- package/lib/request.d.ts +1 -1
- package/lib/route.d.ts +1 -1
- package/lib/schema-validator.d.ts +1 -1
- package/lib/secure-headers.d.ts +1 -1
- package/lib/{serve-static-Dw_m6KxR.js → serve-static-B14MF1Va.js} +2 -0
- package/lib/serve-static.d.ts +1 -1
- package/lib/ssg-BwjcA-Ge.js +389 -0
- package/lib/ssg.d.ts +156 -0
- package/lib/ssg.js +2 -0
- package/lib/streaming.d.ts +1 -1
- package/lib/testing.d.ts +4 -4
- package/lib/testing.js +1 -1
- package/lib/timeout.d.ts +1 -1
- package/lib/timing.d.ts +1 -1
- package/lib/tiny.d.ts +1 -1
- package/lib/trailing-slash.d.ts +1 -1
- package/lib/{types-ZM9gIiKh.d.ts → types-Dv_nV2cv.d.ts} +1 -1
- package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
- package/lib/{types-Bd29gh2o.d.ts → types-zNxyEpG2.d.ts} +3 -3
- package/lib/types.d.ts +1 -1
- package/lib/utils/body.d.ts +1 -1
- package/lib/utils/cookie.d.ts +1 -1
- package/lib/utils/crypto.d.ts +1 -1
- package/lib/utils/html.d.ts +1 -45
- package/lib/utils/ipaddr.d.ts +1 -1
- package/lib/utils/jwt.d.ts +1 -1
- package/lib/utils/types.d.ts +2 -2
- package/lib/utils-Cx61GFMC.js +125 -0
- package/lib/validator.d.ts +2 -2
- package/lib/ws.d.ts +1 -1
- package/package.json +53 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/upstream.json +1 -1
- package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
- package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
- package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
- package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
- package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
- package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
- package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
- package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
- package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
- package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
- package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
- package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
- package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
- package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
- package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
- package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
- package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
- package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
- package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
- package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
- package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
- package/plugins/acryl-router/src/core/utils/types.ts +6 -0
- package/plugins/acryl-router/src/helpers/index.ts +2 -0
- package/plugins/acryl-router/src/middleware/index.ts +1 -0
- package/plugins/acryl-router/upstream.json +3 -3
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/resolve.ts +4 -0
- package/plugins/save/plugin.json +1 -1
- package/shared/AGENTS.core.md +4 -0
- package/templates/agent/tsconfig.json +7 -1
- package/templates/todo/upstream.json +1 -1
- package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
- package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
- package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
- package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
- package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
- package/plugins/acryl-router/src/core/app.test.ts +0 -3833
- package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
- package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
- package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
- package/plugins/acryl-router/src/core/compose.test.ts +0 -751
- package/plugins/acryl-router/src/core/context.test.ts +0 -629
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
- package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
- package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
- package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
- package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
- package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
- package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
- package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
- package/plugins/acryl-router/src/core/request.test.ts +0 -751
- package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
- package/plugins/acryl-router/src/core/types.test.ts +0 -4134
- package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
- package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
- package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
- package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
- package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
- package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
- package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
- package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
- package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
- package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
- package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
- package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
- package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
- package/plugins/acryl-router/tests/loader.spec.ts +0 -72
- package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
- package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
- package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
- package/plugins/acryl-router/tests/service.spec.ts +0 -426
- package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
- package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
- package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
- package/plugins/acryl-state/src/react.ts +0 -11
- package/plugins/acryl-state/tests/cell.spec.ts +0 -71
- package/plugins/acryl-state/tests/react.spec.ts +0 -39
- package/plugins/acryl-state/tests/service.spec.ts +0 -132
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { html } from '../html/index.ts'
|
|
2
|
+
import type { SSGPlugin } from './ssg.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The default plugin that defines the recommended behavior.
|
|
6
|
+
*
|
|
7
|
+
* @experimental
|
|
8
|
+
* `defaultPlugin` is an experimental feature.
|
|
9
|
+
* The API might be changed.
|
|
10
|
+
*/
|
|
11
|
+
export const defaultPlugin = (): SSGPlugin => {
|
|
12
|
+
return {
|
|
13
|
+
afterResponseHook: (res) => {
|
|
14
|
+
if (res.status !== 200) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
return res
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308])
|
|
23
|
+
|
|
24
|
+
const generateRedirectHtml = (location: string) => {
|
|
25
|
+
// prettier-ignore
|
|
26
|
+
const content = html`<!DOCTYPE html>
|
|
27
|
+
<title>Redirecting to: ${location}</title>
|
|
28
|
+
<meta http-equiv="refresh" content="0;url=${location}" />
|
|
29
|
+
<meta name="robots" content="noindex" />
|
|
30
|
+
<link rel="canonical" href="${location}" />
|
|
31
|
+
<body>
|
|
32
|
+
<a href="${location}">Redirecting to <code>${location}</code></a>
|
|
33
|
+
</body>
|
|
34
|
+
`
|
|
35
|
+
return content.toString().replace(/\n/g, '')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The redirect plugin that generates HTML redirect pages for HTTP redirect responses for status codes 301, 302, 303, 307 and 308.
|
|
40
|
+
*
|
|
41
|
+
* When used with `defaultPlugin`, place `redirectPlugin` before it, because `defaultPlugin` skips non-200 responses.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* // ✅ Will work as expected
|
|
45
|
+
* toSSG(app, fs, { plugins: [redirectPlugin(), defaultPlugin()] })
|
|
46
|
+
*
|
|
47
|
+
* // ❌ Will not work as expected
|
|
48
|
+
* toSSG(app, fs, { plugins: [defaultPlugin(), redirectPlugin()] })
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @experimental
|
|
52
|
+
* `redirectPlugin` is an experimental feature.
|
|
53
|
+
* The API might be changed.
|
|
54
|
+
*/
|
|
55
|
+
export const redirectPlugin = (): SSGPlugin => {
|
|
56
|
+
return {
|
|
57
|
+
afterResponseHook: (res) => {
|
|
58
|
+
if (REDIRECT_STATUS_CODES.has(res.status)) {
|
|
59
|
+
const location = res.headers.get('Location')
|
|
60
|
+
if (!location) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
const htmlBody = generateRedirectHtml(location)
|
|
64
|
+
return new Response(htmlBody, {
|
|
65
|
+
status: 200,
|
|
66
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
return res
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { replaceUrlParam } from '../../client/utils.ts'
|
|
2
|
+
import type { Acryl } from '../../app.ts'
|
|
3
|
+
import type { Env, Schema } from '../../types.ts'
|
|
4
|
+
import { createPool } from '../../utils/concurrent.ts'
|
|
5
|
+
import { getExtension } from '../../utils/mime.ts'
|
|
6
|
+
import type { AddedSSGDataRequest, SSGParams } from './middleware.ts'
|
|
7
|
+
import { SSG_CONTEXT, X_ACRYL_DISABLE_SSG_HEADER_KEY } from './middleware.ts'
|
|
8
|
+
import { defaultPlugin } from './plugins.ts'
|
|
9
|
+
import {
|
|
10
|
+
dirname,
|
|
11
|
+
ensureWithinOutDir,
|
|
12
|
+
filterStaticGenerateRoutes,
|
|
13
|
+
isDynamicRoute,
|
|
14
|
+
joinPaths,
|
|
15
|
+
} from './utils.ts'
|
|
16
|
+
|
|
17
|
+
const DEFAULT_CONCURRENCY = 2 // default concurrency for ssg
|
|
18
|
+
|
|
19
|
+
// 'default_content_type' is designed according to Bun's performance optimization,
|
|
20
|
+
// which omits Content-Type by default for text responses.
|
|
21
|
+
// This is based on benchmarks showing performance gains without Content-Type.
|
|
22
|
+
// In Acryl, using `c.text()` without a Content-Type implicitly assumes 'text/plain; charset=UTF-8'.
|
|
23
|
+
// This approach maintains performance consistency across different environments.
|
|
24
|
+
// For details, see GitHub issues: oven-sh/bun#8530
|
|
25
|
+
const DEFAULT_CONTENT_TYPE = 'text/plain'
|
|
26
|
+
|
|
27
|
+
export const DEFAULT_OUTPUT_DIR = './static'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @experimental
|
|
31
|
+
* `FileSystemModule` is an experimental feature.
|
|
32
|
+
* The API might be changed.
|
|
33
|
+
*/
|
|
34
|
+
export interface FileSystemModule {
|
|
35
|
+
writeFile(path: string, data: string | Uint8Array): Promise<void>
|
|
36
|
+
mkdir(path: string, options: { recursive: boolean }): Promise<void | string>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @experimental
|
|
41
|
+
* `ToSSGResult` is an experimental feature.
|
|
42
|
+
* The API might be changed.
|
|
43
|
+
*/
|
|
44
|
+
export interface ToSSGResult {
|
|
45
|
+
success: boolean
|
|
46
|
+
files: string[]
|
|
47
|
+
error?: Error
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const generateFilePath = (
|
|
51
|
+
routePath: string,
|
|
52
|
+
outDir: string,
|
|
53
|
+
mimeType: string,
|
|
54
|
+
extensionMap?: Record<string, string>
|
|
55
|
+
): string => {
|
|
56
|
+
const extension = determineExtension(mimeType, extensionMap)
|
|
57
|
+
|
|
58
|
+
let filePath: string
|
|
59
|
+
if (routePath.endsWith(`.${extension}`)) {
|
|
60
|
+
filePath = joinPaths(outDir, routePath)
|
|
61
|
+
} else if (routePath === '/') {
|
|
62
|
+
filePath = joinPaths(outDir, `index.${extension}`)
|
|
63
|
+
} else if (routePath.endsWith('/')) {
|
|
64
|
+
filePath = joinPaths(outDir, routePath, `index.${extension}`)
|
|
65
|
+
} else {
|
|
66
|
+
filePath = joinPaths(outDir, `${routePath}.${extension}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ensureWithinOutDir(outDir, filePath)
|
|
70
|
+
|
|
71
|
+
return filePath
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parseResponseContent = async (response: Response): Promise<string | ArrayBuffer> => {
|
|
75
|
+
const contentType = response.headers.get('Content-Type')
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
if (contentType?.includes('text') || contentType?.includes('json')) {
|
|
79
|
+
return await response.text()
|
|
80
|
+
} else {
|
|
81
|
+
return await response.arrayBuffer()
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Error processing response: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const defaultExtensionMap: Record<string, string> = {
|
|
91
|
+
'text/html': 'html',
|
|
92
|
+
'text/xml': 'xml',
|
|
93
|
+
'application/xml': 'xml',
|
|
94
|
+
'application/atom+xml': 'xml',
|
|
95
|
+
'application/rss+xml': 'xml',
|
|
96
|
+
'application/yaml': 'yaml',
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const determineExtension = (
|
|
100
|
+
mimeType: string,
|
|
101
|
+
userExtensionMap?: Record<string, string>
|
|
102
|
+
): string => {
|
|
103
|
+
const extensionMap = userExtensionMap || defaultExtensionMap
|
|
104
|
+
if (mimeType in extensionMap) {
|
|
105
|
+
return extensionMap[mimeType]
|
|
106
|
+
}
|
|
107
|
+
return getExtension(mimeType) || 'html'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type BeforeRequestHook = (req: Request) => Request | false | Promise<Request | false>
|
|
111
|
+
export type AfterResponseHook = (res: Response) => Response | false | Promise<Response | false>
|
|
112
|
+
export type AfterGenerateHook = (
|
|
113
|
+
result: ToSSGResult,
|
|
114
|
+
fsModule: FileSystemModule,
|
|
115
|
+
options?: ToSSGOptions
|
|
116
|
+
) => void | Promise<void>
|
|
117
|
+
|
|
118
|
+
export const combineBeforeRequestHooks = (
|
|
119
|
+
hooks: BeforeRequestHook | BeforeRequestHook[]
|
|
120
|
+
): BeforeRequestHook => {
|
|
121
|
+
if (!Array.isArray(hooks)) {
|
|
122
|
+
return hooks
|
|
123
|
+
}
|
|
124
|
+
return async (req: Request): Promise<Request | false> => {
|
|
125
|
+
let currentReq = req
|
|
126
|
+
for (const hook of hooks) {
|
|
127
|
+
const result = await hook(currentReq)
|
|
128
|
+
if (result === false) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
if (result instanceof Request) {
|
|
132
|
+
currentReq = result
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return currentReq
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const combineAfterResponseHooks = (
|
|
140
|
+
hooks: AfterResponseHook | AfterResponseHook[]
|
|
141
|
+
): AfterResponseHook => {
|
|
142
|
+
if (!Array.isArray(hooks)) {
|
|
143
|
+
return hooks
|
|
144
|
+
}
|
|
145
|
+
return async (res: Response): Promise<Response | false> => {
|
|
146
|
+
let currentRes = res
|
|
147
|
+
for (const hook of hooks) {
|
|
148
|
+
const result = await hook(currentRes)
|
|
149
|
+
if (result === false) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
if (result instanceof Response) {
|
|
153
|
+
currentRes = result
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return currentRes
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const combineAfterGenerateHooks = (
|
|
161
|
+
hooks: AfterGenerateHook | AfterGenerateHook[],
|
|
162
|
+
fsModule: FileSystemModule,
|
|
163
|
+
options?: ToSSGOptions
|
|
164
|
+
): AfterGenerateHook => {
|
|
165
|
+
if (!Array.isArray(hooks)) {
|
|
166
|
+
return hooks
|
|
167
|
+
}
|
|
168
|
+
return async (result: ToSSGResult): Promise<void> => {
|
|
169
|
+
for (const hook of hooks) {
|
|
170
|
+
await hook(result, fsModule, options)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface SSGPlugin {
|
|
176
|
+
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
|
|
177
|
+
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
|
|
178
|
+
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ToSSGOptions {
|
|
182
|
+
dir?: string
|
|
183
|
+
/**
|
|
184
|
+
* @deprecated Use plugins[].beforeRequestHook instead.
|
|
185
|
+
*/
|
|
186
|
+
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
|
|
187
|
+
/**
|
|
188
|
+
* @deprecated Use plugins[].afterResponseHook instead.
|
|
189
|
+
*/
|
|
190
|
+
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
|
|
191
|
+
/**
|
|
192
|
+
* @deprecated Use plugins[].afterGenerateHook instead.
|
|
193
|
+
*/
|
|
194
|
+
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
|
|
195
|
+
concurrency?: number
|
|
196
|
+
extensionMap?: Record<string, string>
|
|
197
|
+
plugins?: SSGPlugin[]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @experimental
|
|
202
|
+
* `fetchRoutesContent` is an experimental feature.
|
|
203
|
+
* The API might be changed.
|
|
204
|
+
*/
|
|
205
|
+
export const fetchRoutesContent = function* <
|
|
206
|
+
E extends Env = Env,
|
|
207
|
+
S extends Schema = {},
|
|
208
|
+
BasePath extends string = '/',
|
|
209
|
+
>(
|
|
210
|
+
app: Acryl<E, S, BasePath>,
|
|
211
|
+
beforeRequestHook?: BeforeRequestHook,
|
|
212
|
+
afterResponseHook?: AfterResponseHook,
|
|
213
|
+
concurrency?: number
|
|
214
|
+
): Generator<
|
|
215
|
+
Promise<
|
|
216
|
+
| Generator<
|
|
217
|
+
Promise<{ routePath: string; mimeType: string; content: string | ArrayBuffer } | undefined>
|
|
218
|
+
>
|
|
219
|
+
| undefined
|
|
220
|
+
>
|
|
221
|
+
> {
|
|
222
|
+
const baseURL = 'http://localhost'
|
|
223
|
+
const pool = createPool({ concurrency })
|
|
224
|
+
|
|
225
|
+
for (const route of filterStaticGenerateRoutes(app)) {
|
|
226
|
+
// GET Route Info
|
|
227
|
+
const thisRouteBaseURL = new URL(route.path, baseURL).toString()
|
|
228
|
+
|
|
229
|
+
let forGetInfoURLRequest = new Request(thisRouteBaseURL) as AddedSSGDataRequest
|
|
230
|
+
|
|
231
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
232
|
+
yield new Promise(async (resolveGetInfo, rejectGetInfo) => {
|
|
233
|
+
try {
|
|
234
|
+
if (beforeRequestHook) {
|
|
235
|
+
const maybeRequest = await beforeRequestHook(forGetInfoURLRequest)
|
|
236
|
+
if (!maybeRequest) {
|
|
237
|
+
resolveGetInfo(undefined)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
forGetInfoURLRequest = maybeRequest as unknown as AddedSSGDataRequest
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await pool.run(() => app.fetch(forGetInfoURLRequest, { [SSG_CONTEXT]: true }))
|
|
244
|
+
|
|
245
|
+
if (!forGetInfoURLRequest.ssgParams) {
|
|
246
|
+
if (isDynamicRoute(route.path)) {
|
|
247
|
+
resolveGetInfo(undefined)
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
forGetInfoURLRequest.ssgParams = [{}]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const requestInit = {
|
|
254
|
+
method: forGetInfoURLRequest.method,
|
|
255
|
+
headers: forGetInfoURLRequest.headers,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
resolveGetInfo(
|
|
259
|
+
(function* () {
|
|
260
|
+
for (const param of forGetInfoURLRequest.ssgParams as SSGParams) {
|
|
261
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
262
|
+
yield new Promise(async (resolveReq, rejectReq) => {
|
|
263
|
+
try {
|
|
264
|
+
const replacedUrlParam = replaceUrlParam(route.path, param)
|
|
265
|
+
let response = await pool.run(() =>
|
|
266
|
+
app.request(replacedUrlParam, requestInit, {
|
|
267
|
+
[SSG_CONTEXT]: true,
|
|
268
|
+
})
|
|
269
|
+
)
|
|
270
|
+
if (response.headers.get(X_ACRYL_DISABLE_SSG_HEADER_KEY)) {
|
|
271
|
+
resolveReq(undefined)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
if (afterResponseHook) {
|
|
275
|
+
const maybeResponse = await afterResponseHook(response)
|
|
276
|
+
if (!maybeResponse) {
|
|
277
|
+
resolveReq(undefined)
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
response = maybeResponse
|
|
281
|
+
}
|
|
282
|
+
const mimeType =
|
|
283
|
+
response.headers.get('Content-Type')?.split(';')[0] || DEFAULT_CONTENT_TYPE
|
|
284
|
+
const content = await parseResponseContent(response)
|
|
285
|
+
resolveReq({
|
|
286
|
+
routePath: replacedUrlParam,
|
|
287
|
+
mimeType,
|
|
288
|
+
content,
|
|
289
|
+
})
|
|
290
|
+
} catch (error) {
|
|
291
|
+
rejectReq(error)
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
})()
|
|
296
|
+
)
|
|
297
|
+
} catch (error) {
|
|
298
|
+
rejectGetInfo(error)
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @experimental
|
|
306
|
+
* `saveContentToFile` is an experimental feature.
|
|
307
|
+
* The API might be changed.
|
|
308
|
+
*/
|
|
309
|
+
const createdDirs: Set<string> = new Set()
|
|
310
|
+
export const saveContentToFile = async (
|
|
311
|
+
data: Promise<{ routePath: string; content: string | ArrayBuffer; mimeType: string } | undefined>,
|
|
312
|
+
fsModule: FileSystemModule,
|
|
313
|
+
outDir: string,
|
|
314
|
+
extensionMap?: Record<string, string>
|
|
315
|
+
): Promise<string | undefined> => {
|
|
316
|
+
const awaitedData = await data
|
|
317
|
+
if (!awaitedData) {
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
const { routePath, content, mimeType } = awaitedData
|
|
321
|
+
const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap)
|
|
322
|
+
const dirPath = dirname(filePath)
|
|
323
|
+
|
|
324
|
+
// `dirPath` is empty when outDir itself is the current directory
|
|
325
|
+
if (dirPath !== '' && !createdDirs.has(dirPath)) {
|
|
326
|
+
await fsModule.mkdir(dirPath, { recursive: true })
|
|
327
|
+
createdDirs.add(dirPath)
|
|
328
|
+
}
|
|
329
|
+
if (typeof content === 'string') {
|
|
330
|
+
await fsModule.writeFile(filePath, content)
|
|
331
|
+
} else if (content instanceof ArrayBuffer) {
|
|
332
|
+
await fsModule.writeFile(filePath, new Uint8Array(content))
|
|
333
|
+
}
|
|
334
|
+
return filePath
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @experimental
|
|
339
|
+
* `ToSSGInterface` is an experimental feature.
|
|
340
|
+
* The API might be changed.
|
|
341
|
+
*/
|
|
342
|
+
export interface ToSSGInterface {
|
|
343
|
+
(
|
|
344
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
345
|
+
app: Acryl<any, any, any>,
|
|
346
|
+
fsModule: FileSystemModule,
|
|
347
|
+
options?: ToSSGOptions
|
|
348
|
+
): Promise<ToSSGResult>
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @experimental
|
|
353
|
+
* `ToSSGAdaptorInterface` is an experimental feature.
|
|
354
|
+
* The API might be changed.
|
|
355
|
+
*/
|
|
356
|
+
export interface ToSSGAdaptorInterface<
|
|
357
|
+
E extends Env = Env,
|
|
358
|
+
S extends Schema = {},
|
|
359
|
+
BasePath extends string = '/',
|
|
360
|
+
> {
|
|
361
|
+
(app: Acryl<E, S, BasePath>, options?: ToSSGOptions): Promise<ToSSGResult>
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @experimental
|
|
366
|
+
* `toSSG` is an experimental feature.
|
|
367
|
+
* The API might be changed.
|
|
368
|
+
*/
|
|
369
|
+
export const toSSG: ToSSGInterface = async (app, fs, options) => {
|
|
370
|
+
let result: ToSSGResult | undefined
|
|
371
|
+
const getInfoPromises: Promise<unknown>[] = []
|
|
372
|
+
const savePromises: Promise<string | undefined>[] = []
|
|
373
|
+
const plugins = options?.plugins || [defaultPlugin()]
|
|
374
|
+
const beforeRequestHooks: BeforeRequestHook[] = []
|
|
375
|
+
const afterResponseHooks: AfterResponseHook[] = []
|
|
376
|
+
const afterGenerateHooks: AfterGenerateHook[] = []
|
|
377
|
+
if (options?.beforeRequestHook) {
|
|
378
|
+
beforeRequestHooks.push(
|
|
379
|
+
...(Array.isArray(options.beforeRequestHook)
|
|
380
|
+
? options.beforeRequestHook
|
|
381
|
+
: [options.beforeRequestHook])
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
if (options?.afterResponseHook) {
|
|
385
|
+
afterResponseHooks.push(
|
|
386
|
+
...(Array.isArray(options.afterResponseHook)
|
|
387
|
+
? options.afterResponseHook
|
|
388
|
+
: [options.afterResponseHook])
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
if (options?.afterGenerateHook) {
|
|
392
|
+
afterGenerateHooks.push(
|
|
393
|
+
...(Array.isArray(options.afterGenerateHook)
|
|
394
|
+
? options.afterGenerateHook
|
|
395
|
+
: [options.afterGenerateHook])
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
for (const plugin of plugins) {
|
|
399
|
+
if (plugin.beforeRequestHook) {
|
|
400
|
+
beforeRequestHooks.push(
|
|
401
|
+
...(Array.isArray(plugin.beforeRequestHook)
|
|
402
|
+
? plugin.beforeRequestHook
|
|
403
|
+
: [plugin.beforeRequestHook])
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
if (plugin.afterResponseHook) {
|
|
407
|
+
afterResponseHooks.push(
|
|
408
|
+
...(Array.isArray(plugin.afterResponseHook)
|
|
409
|
+
? plugin.afterResponseHook
|
|
410
|
+
: [plugin.afterResponseHook])
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
if (plugin.afterGenerateHook) {
|
|
414
|
+
afterGenerateHooks.push(
|
|
415
|
+
...(Array.isArray(plugin.afterGenerateHook)
|
|
416
|
+
? plugin.afterGenerateHook
|
|
417
|
+
: [plugin.afterGenerateHook])
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR
|
|
423
|
+
const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY
|
|
424
|
+
|
|
425
|
+
const combinedBeforeRequestHook = combineBeforeRequestHooks(
|
|
426
|
+
beforeRequestHooks.length > 0 ? beforeRequestHooks : [(req) => req]
|
|
427
|
+
)
|
|
428
|
+
const combinedAfterResponseHook = combineAfterResponseHooks(
|
|
429
|
+
afterResponseHooks.length > 0 ? afterResponseHooks : [(req) => req]
|
|
430
|
+
)
|
|
431
|
+
const getInfoGen = fetchRoutesContent(
|
|
432
|
+
app,
|
|
433
|
+
combinedBeforeRequestHook,
|
|
434
|
+
combinedAfterResponseHook,
|
|
435
|
+
concurrency
|
|
436
|
+
)
|
|
437
|
+
for (const getInfo of getInfoGen) {
|
|
438
|
+
getInfoPromises.push(
|
|
439
|
+
getInfo.then((getContentGen) => {
|
|
440
|
+
if (!getContentGen) {
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
for (const content of getContentGen) {
|
|
444
|
+
savePromises.push(
|
|
445
|
+
saveContentToFile(content, fs, outputDir, options?.extensionMap).catch((e) => e)
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
await Promise.all(getInfoPromises)
|
|
452
|
+
const files: string[] = []
|
|
453
|
+
for (const savePromise of savePromises) {
|
|
454
|
+
const fileOrError = await savePromise
|
|
455
|
+
if (typeof fileOrError === 'string') {
|
|
456
|
+
files.push(fileOrError)
|
|
457
|
+
} else if (fileOrError) {
|
|
458
|
+
throw fileOrError
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
result = { success: true, files }
|
|
462
|
+
} catch (error) {
|
|
463
|
+
const errorObj = error instanceof Error ? error : new Error(String(error))
|
|
464
|
+
result = { success: false, files: [], error: errorObj }
|
|
465
|
+
}
|
|
466
|
+
if (afterGenerateHooks.length > 0) {
|
|
467
|
+
const combinedAfterGenerateHooks = combineAfterGenerateHooks(afterGenerateHooks, fs, options)
|
|
468
|
+
await combinedAfterGenerateHooks(result, fs, options)
|
|
469
|
+
}
|
|
470
|
+
return result
|
|
471
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Acryl } from '../../app.ts'
|
|
2
|
+
import { METHOD_NAME_ALL } from '../../router.ts'
|
|
3
|
+
import type { Env, RouterRoute } from '../../types.ts'
|
|
4
|
+
import { findTargetHandler, isMiddleware } from '../../utils/handler.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get dirname
|
|
8
|
+
* @param path File Path
|
|
9
|
+
* @returns Parent dir path
|
|
10
|
+
*/
|
|
11
|
+
export const dirname = (path: string): string => {
|
|
12
|
+
const separatedPath = path.split(/[\/\\]/)
|
|
13
|
+
return separatedPath.slice(0, -1).join('/') // Windows supports slash path
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const normalizePath = (path: string): string => {
|
|
17
|
+
return path.replace(/(\\)/g, '/').replace(/\/$/g, '')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getUncRoot = (path: string): string | undefined => {
|
|
21
|
+
const uncRoot = path.replace(/\\/g, '/').match(/^\/\/([^/]+)\/([^/]+)/)
|
|
22
|
+
if (uncRoot) {
|
|
23
|
+
return `${uncRoot[1].toLowerCase()}/${uncRoot[2].toLowerCase()}`
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleParent = (resultPaths: string[]): void => {
|
|
28
|
+
if (resultPaths.length === 0 || resultPaths[resultPaths.length - 1] === '..') {
|
|
29
|
+
resultPaths.push('..')
|
|
30
|
+
} else {
|
|
31
|
+
resultPaths.pop()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleNonDot = (path: string, resultPaths: string[]): void => {
|
|
36
|
+
path = path.replace(/^\.(?!.)/, '')
|
|
37
|
+
if (path !== '') {
|
|
38
|
+
resultPaths.push(path)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleSegments = (paths: string[], resultPaths: string[]): void => {
|
|
43
|
+
for (const path of paths) {
|
|
44
|
+
// Handle `..`
|
|
45
|
+
if (path === '..') {
|
|
46
|
+
handleParent(resultPaths)
|
|
47
|
+
} else {
|
|
48
|
+
// Handle `.` or `abc`
|
|
49
|
+
handleNonDot(path, resultPaths)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const joinPaths = (...paths: string[]): string => {
|
|
55
|
+
const hasUncPrefix = getUncRoot(paths[0]) !== undefined
|
|
56
|
+
paths = paths.map(normalizePath)
|
|
57
|
+
const resultPaths: string[] = []
|
|
58
|
+
handleSegments(paths.join('/').split('/'), resultPaths)
|
|
59
|
+
return (hasUncPrefix ? '//' : paths[0][0] === '/' ? '/' : '') + resultPaths.join('/')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface FilterStaticGenerateRouteData {
|
|
63
|
+
path: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const filterStaticGenerateRoutes = <E extends Env>(
|
|
67
|
+
acryl: Acryl<E>
|
|
68
|
+
): FilterStaticGenerateRouteData[] => {
|
|
69
|
+
return acryl.routes.reduce((acc, { method, handler, path }: RouterRoute) => {
|
|
70
|
+
const targetHandler = findTargetHandler(handler)
|
|
71
|
+
if (['GET', METHOD_NAME_ALL].includes(method) && !isMiddleware(targetHandler)) {
|
|
72
|
+
acc.push({ path })
|
|
73
|
+
}
|
|
74
|
+
return acc
|
|
75
|
+
}, [] as FilterStaticGenerateRouteData[])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const isDynamicRoute = (path: string): boolean => {
|
|
79
|
+
return path.split('/').some((segment) => segment.startsWith(':') || segment.includes('*'))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const toSegments = (path: string): string[] => (path === '' ? [] : path.split('/'))
|
|
83
|
+
|
|
84
|
+
const getPathRoot = (path: string): string => {
|
|
85
|
+
const normalizedPath = path.replace(/\\/g, '/')
|
|
86
|
+
const uncRoot = getUncRoot(normalizedPath)
|
|
87
|
+
if (uncRoot) {
|
|
88
|
+
return `unc:${uncRoot}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const driveRoot = normalizedPath.match(/^([A-Za-z]):/)
|
|
92
|
+
if (driveRoot) {
|
|
93
|
+
const kind = normalizedPath[2] === '/' ? 'drive-absolute' : 'drive-relative'
|
|
94
|
+
return `${kind}:${driveRoot[1].toLowerCase()}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return normalizedPath.startsWith('/') ? 'absolute' : 'relative'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const ensureWithinOutDir = (outDir: string, filePath: string): void => {
|
|
101
|
+
const outDirSegments = toSegments(joinPaths(outDir))
|
|
102
|
+
const filePathSegments = toSegments(joinPaths(filePath))
|
|
103
|
+
|
|
104
|
+
const hasMismatchedPathRoot = getPathRoot(outDir) !== getPathRoot(filePath)
|
|
105
|
+
|
|
106
|
+
// `joinPaths` collects every remaining `..` at the head, so a `..` right after
|
|
107
|
+
// the outDir segments means the file path climbs above outDir
|
|
108
|
+
const climbsAboveOutDir = filePathSegments[outDirSegments.length] === '..'
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
hasMismatchedPathRoot ||
|
|
112
|
+
filePathSegments.length <= outDirSegments.length ||
|
|
113
|
+
!outDirSegments.every((segment, i) => segment === filePathSegments[i]) ||
|
|
114
|
+
climbsAboveOutDir
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(`Path traversal detected: "${filePath}" is outside the output directory`)
|
|
117
|
+
}
|
|
118
|
+
}
|