@moku-labs/web 0.1.0-alpha.1 → 0.1.0-alpha.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/dist/bin/moku.cjs +575 -1
- package/dist/bin/moku.mjs +576 -2
- package/dist/{factory-CixCpR9C.cjs → factory-DVcAQYEZ.cjs} +1 -1
- package/dist/{index-CWdZdegx.d.mts → index-CddOHo8I.d.mts} +115 -51
- package/dist/index.cjs +4 -3
- package/dist/index.d.cts +37 -37
- package/dist/index.d.mts +39 -39
- package/dist/index.mjs +3 -2
- package/dist/plugins/head/build.d.mts +1 -1
- package/dist/plugins/spa/index.cjs +1 -1
- package/dist/plugins/spa/index.d.mts +1 -1
- package/dist/{project-C1vtMxE8.cjs → project-1pAh4RxJ.cjs} +194 -5
- package/dist/{project-BTNUWbGQ.mjs → project-BaG_ipVz.mjs} +190 -7
- package/dist/{route-builder-Lv6HUVvP.d.cts → route-builder-CKvvehVO.d.cts} +113 -49
- package/dist/test.cjs +2 -2
- package/dist/test.d.cts +1 -1
- package/dist/test.d.mts +1 -1
- package/dist/test.mjs +1 -1
- package/dist/wrangler-BHdkyMRj.cjs +423 -0
- package/dist/wrangler-Coyrznz4.mjs +369 -0
- package/package.json +9 -1
- /package/dist/{factory-BBVQO5ZG.d.mts → factory-BHhulW27.d.mts} +0 -0
- /package/dist/{primitives-kuZFxqV7.d.mts → primitives-Dlfi3JTx.d.mts} +0 -0
|
@@ -25,7 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
|
-
const require_factory = require('./factory-
|
|
28
|
+
const require_factory = require('./factory-DVcAQYEZ.cjs');
|
|
29
|
+
const require_wrangler = require('./wrangler-BHdkyMRj.cjs');
|
|
29
30
|
let node_fs = require("node:fs");
|
|
30
31
|
let node_fs_promises = require("node:fs/promises");
|
|
31
32
|
let node_path = require("node:path");
|
|
@@ -442,7 +443,7 @@ const wireContentApi = (ctx) => {
|
|
|
442
443
|
//#endregion
|
|
443
444
|
//#region src/plugins/content/index.ts
|
|
444
445
|
/** @file content plugin: markdown pipeline + invalidate(). Complex tier. */
|
|
445
|
-
const defaultConfig$
|
|
446
|
+
const defaultConfig$2 = {
|
|
446
447
|
dir: "",
|
|
447
448
|
trustedContent: false,
|
|
448
449
|
rehypePlugins: [],
|
|
@@ -450,7 +451,7 @@ const defaultConfig$1 = {
|
|
|
450
451
|
};
|
|
451
452
|
const content = require_factory.createPlugin("content", {
|
|
452
453
|
depends: [require_factory.i18n],
|
|
453
|
-
config: defaultConfig$
|
|
454
|
+
config: defaultConfig$2,
|
|
454
455
|
events: (register) => ({
|
|
455
456
|
"content:ready": register("Articles loaded"),
|
|
456
457
|
"content:invalidated": register("Paths invalidated")
|
|
@@ -981,7 +982,7 @@ const validateBuildConfig = (ctx) => {
|
|
|
981
982
|
//#endregion
|
|
982
983
|
//#region src/plugins/build/index.ts
|
|
983
984
|
/** @file build plugin: app.build.run() orchestrator + in-memory manifest. Complex tier. */
|
|
984
|
-
const defaultConfig = {
|
|
985
|
+
const defaultConfig$1 = {
|
|
985
986
|
outdir: "dist",
|
|
986
987
|
mode: "production",
|
|
987
988
|
renderMode: "hybrid"
|
|
@@ -993,7 +994,7 @@ const build = require_factory.createPlugin("build", {
|
|
|
993
994
|
require_factory.router,
|
|
994
995
|
require_factory.head
|
|
995
996
|
],
|
|
996
|
-
config: defaultConfig,
|
|
997
|
+
config: defaultConfig$1,
|
|
997
998
|
events: (register) => ({
|
|
998
999
|
"build:phase": register("Build phase started"),
|
|
999
1000
|
"build:complete": register("Build pipeline complete")
|
|
@@ -1003,6 +1004,188 @@ const build = require_factory.createPlugin("build", {
|
|
|
1003
1004
|
onInit: validateBuildConfig
|
|
1004
1005
|
});
|
|
1005
1006
|
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/plugins/deploy/api.ts
|
|
1009
|
+
/** @file deploy plugin API factory — orchestrates app.deploy.run(). */
|
|
1010
|
+
/** Free-tier Cloudflare Pages limit. The paid tier (100,000) requires extra env. */
|
|
1011
|
+
const FREE_TIER_FILE_LIMIT = 2e4;
|
|
1012
|
+
/** Per-file size cap for Cloudflare Pages (25 MiB). */
|
|
1013
|
+
const MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
|
|
1014
|
+
const sanitizedProcessEnv = () => {
|
|
1015
|
+
const out = {};
|
|
1016
|
+
for (const [k, v] of Object.entries(process.env)) if (typeof v === "string") out[k] = v;
|
|
1017
|
+
return out;
|
|
1018
|
+
};
|
|
1019
|
+
const checkFile = (path, state) => {
|
|
1020
|
+
state.count += 1;
|
|
1021
|
+
if ((0, node_fs.statSync)(path).size > MAX_FILE_SIZE_BYTES) state.oversize = path;
|
|
1022
|
+
};
|
|
1023
|
+
const walkOutdir = (current, state) => {
|
|
1024
|
+
for (const entry of (0, node_fs.readdirSync)(current, { withFileTypes: true })) {
|
|
1025
|
+
if (state.oversize !== null) return;
|
|
1026
|
+
const path = (0, node_path.join)(current, entry.name);
|
|
1027
|
+
if (entry.isDirectory()) walkOutdir(path, state);
|
|
1028
|
+
else if (entry.isFile()) checkFile(path, state);
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
/** Recursively count files and check max size under `dir`. Short-circuits on first oversize file. */
|
|
1032
|
+
const inspectOutdir = (dir) => {
|
|
1033
|
+
const state = {
|
|
1034
|
+
count: 0,
|
|
1035
|
+
oversize: null
|
|
1036
|
+
};
|
|
1037
|
+
walkOutdir(dir, state);
|
|
1038
|
+
return {
|
|
1039
|
+
fileCount: state.count,
|
|
1040
|
+
oversize: state.oversize
|
|
1041
|
+
};
|
|
1042
|
+
};
|
|
1043
|
+
/** Throw if any pre-flight constraint on the resolved outdir fails. */
|
|
1044
|
+
const assertOutdirReady = (outdir, outdirAbsolute) => {
|
|
1045
|
+
if (!(0, node_fs.existsSync)(outdirAbsolute)) throw new Error(`deploy: outdir "${outdir}" does not exist — run \`moku build\` first (or pass --build).`);
|
|
1046
|
+
const inspection = inspectOutdir(outdirAbsolute);
|
|
1047
|
+
if (inspection.fileCount === 0) throw new Error(`deploy: outdir "${outdir}" is empty — nothing to deploy.`);
|
|
1048
|
+
if (inspection.fileCount > FREE_TIER_FILE_LIMIT) throw new Error(`deploy: outdir contains ${inspection.fileCount} files; free tier limit is ${FREE_TIER_FILE_LIMIT}. Paid tier supports up to 100,000 files (requires PAGES_WRANGLER_MAJOR_VERSION=4 in CI env).`);
|
|
1049
|
+
if (inspection.oversize !== null) throw new Error(`deploy: file "${inspection.oversize}" exceeds Cloudflare Pages per-file limit (25 MiB).`);
|
|
1050
|
+
return inspection;
|
|
1051
|
+
};
|
|
1052
|
+
/** Resolve outdir from `wrangler.jsonc#pages_build_output_dir` or throw if missing. */
|
|
1053
|
+
const resolveOutdir = (cwd) => {
|
|
1054
|
+
const wrangler = require_wrangler.readWranglerConfig(cwd);
|
|
1055
|
+
if (wrangler === null) throw new Error("deploy: wrangler.jsonc not found — run `moku deploy init` first");
|
|
1056
|
+
return wrangler.pages_build_output_dir;
|
|
1057
|
+
};
|
|
1058
|
+
/**
|
|
1059
|
+
* Build the public `app.deploy` API surface.
|
|
1060
|
+
*
|
|
1061
|
+
* `run()` reads outdir from `wrangler.jsonc#pages_build_output_dir` (the
|
|
1062
|
+
* deploy-time SSoT — `DeployConfig.outdir` is only the init-time default).
|
|
1063
|
+
* Errors with a "run `moku deploy init` first" message if `wrangler.jsonc` is
|
|
1064
|
+
* missing.
|
|
1065
|
+
*
|
|
1066
|
+
* @param ctx - The deploy plugin context.
|
|
1067
|
+
* @param deps - Optional injectables for testing.
|
|
1068
|
+
* @returns The {@link DeployApi}.
|
|
1069
|
+
*/
|
|
1070
|
+
/** Trigger the optional pre-build hook for `--build`. */
|
|
1071
|
+
const maybeRunBuild = async (deps, build) => {
|
|
1072
|
+
if (build !== true) return;
|
|
1073
|
+
if (deps.runBuild === void 0) throw new Error("deploy: --build flag set but no `runBuild` hook provided to createDeployApi. Wire `app.build.run` through the plugin index.");
|
|
1074
|
+
await deps.runBuild();
|
|
1075
|
+
};
|
|
1076
|
+
/** Invoke wrangler and shape its output into a {@link DeployResult}. */
|
|
1077
|
+
const invokeWranglerForDeploy = async (ctx, deps, outdir, branch, startedAt, now) => {
|
|
1078
|
+
const env = deps.env ?? sanitizedProcessEnv();
|
|
1079
|
+
const info = require_wrangler.extractDeploymentInfo((await require_wrangler.runWrangler(require_wrangler.buildWranglerArgs(ctx.config.target, outdir, branch), env, deps.spawn)).ndJson);
|
|
1080
|
+
if (info === null) ctx.log.warn("deploy:no-deployment-info", { note: "wrangler exited 0 but no deployment URL/id was found in ND-JSON output." });
|
|
1081
|
+
return {
|
|
1082
|
+
url: info?.url ?? "",
|
|
1083
|
+
deploymentId: info?.deploymentId ?? "",
|
|
1084
|
+
branch,
|
|
1085
|
+
durationMs: now() - startedAt
|
|
1086
|
+
};
|
|
1087
|
+
};
|
|
1088
|
+
/**
|
|
1089
|
+
* Build the public `app.deploy` API surface.
|
|
1090
|
+
*
|
|
1091
|
+
* `run()` reads outdir from `wrangler.jsonc#pages_build_output_dir` (the
|
|
1092
|
+
* deploy-time SSoT — `DeployConfig.outdir` is only the init-time default).
|
|
1093
|
+
* Errors with a "run `moku deploy init` first" message if `wrangler.jsonc` is
|
|
1094
|
+
* missing.
|
|
1095
|
+
*
|
|
1096
|
+
* @param ctx - The deploy plugin context.
|
|
1097
|
+
* @param deps - Optional injectables for testing.
|
|
1098
|
+
* @returns The {@link DeployApi}.
|
|
1099
|
+
*/
|
|
1100
|
+
const createDeployApi = (ctx, deps = {}) => {
|
|
1101
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1102
|
+
const now = deps.now ?? Date.now;
|
|
1103
|
+
const run = async (options = {}) => {
|
|
1104
|
+
await maybeRunBuild(deps, options.build);
|
|
1105
|
+
const outdir = resolveOutdir(cwd);
|
|
1106
|
+
const branch = options.branch ?? ctx.config.productionBranch;
|
|
1107
|
+
const inspection = assertOutdirReady(outdir, (0, node_path.join)(cwd, outdir));
|
|
1108
|
+
const deployResult = await invokeWranglerForDeploy(ctx, deps, outdir, branch, now(), now);
|
|
1109
|
+
ctx.state.lastDeployment = deployResult;
|
|
1110
|
+
ctx.log.info("deploy:complete", {
|
|
1111
|
+
url: deployResult.url,
|
|
1112
|
+
branch,
|
|
1113
|
+
durationMs: deployResult.durationMs,
|
|
1114
|
+
files: inspection.fileCount
|
|
1115
|
+
});
|
|
1116
|
+
return deployResult;
|
|
1117
|
+
};
|
|
1118
|
+
const getLastDeployment = () => ctx.state.lastDeployment;
|
|
1119
|
+
return {
|
|
1120
|
+
run,
|
|
1121
|
+
getLastDeployment
|
|
1122
|
+
};
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
//#endregion
|
|
1126
|
+
//#region src/plugins/deploy/state.ts
|
|
1127
|
+
/**
|
|
1128
|
+
* Create a fresh deploy state.
|
|
1129
|
+
*
|
|
1130
|
+
* `lastDeployment` starts as `null` and is set after the first successful
|
|
1131
|
+
* `app.deploy.run()` call. State is per-`createApp` instance — never a module
|
|
1132
|
+
* singleton — so parallel vitest workers do not share state.
|
|
1133
|
+
*
|
|
1134
|
+
* @returns A new {@link DeployState}.
|
|
1135
|
+
*/
|
|
1136
|
+
const createDeployState = () => ({ lastDeployment: null });
|
|
1137
|
+
|
|
1138
|
+
//#endregion
|
|
1139
|
+
//#region src/plugins/deploy/validate.ts
|
|
1140
|
+
/** @file deploy plugin onInit hook — config-shape validation only. NEVER spawns wrangler. */
|
|
1141
|
+
const VALID_TARGETS = new Set(["pages", "workers"]);
|
|
1142
|
+
/**
|
|
1143
|
+
* Validate `DeployConfig` shape at `onInit` time.
|
|
1144
|
+
*
|
|
1145
|
+
* Checks: `target` is in the valid enum; `outdir` is a non-empty string and
|
|
1146
|
+
* does not escape the current working directory via `..` traversal;
|
|
1147
|
+
* `productionBranch` is a non-empty string.
|
|
1148
|
+
*
|
|
1149
|
+
* Does NOT check wrangler presence — `ensureWrangler()` in `wrangler.ts` is
|
|
1150
|
+
* lazy and called only on first `run()`/`init()` invocation. This keeps
|
|
1151
|
+
* `moku dev`, `moku build`, and `moku preview` unaffected by wrangler absence.
|
|
1152
|
+
*
|
|
1153
|
+
* @param ctx - The deploy plugin context.
|
|
1154
|
+
* @throws Error when any config field is structurally invalid.
|
|
1155
|
+
*/
|
|
1156
|
+
const validateDeployConfig = (ctx) => {
|
|
1157
|
+
const { target, outdir, productionBranch } = ctx.config;
|
|
1158
|
+
if (!VALID_TARGETS.has(target)) throw new Error(`deploy: target must be "pages" or "workers" (got "${target}")`);
|
|
1159
|
+
if (typeof outdir !== "string" || outdir === "") throw new Error("deploy: outdir must be a non-empty string");
|
|
1160
|
+
const resolved = (0, node_path.isAbsolute)(outdir) ? outdir : (0, node_path.resolve)(process.cwd(), outdir);
|
|
1161
|
+
const cwd = process.cwd();
|
|
1162
|
+
if (!resolved.startsWith(cwd)) throw new Error(`deploy: outdir "${outdir}" must resolve inside the project root (got "${resolved}")`);
|
|
1163
|
+
if (typeof productionBranch !== "string" || productionBranch === "") throw new Error("deploy: productionBranch must be a non-empty string");
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
//#endregion
|
|
1167
|
+
//#region src/plugins/deploy/index.ts
|
|
1168
|
+
/** @file deploy plugin: Cloudflare Pages publishing via wrangler subprocess. Standard tier.
|
|
1169
|
+
*
|
|
1170
|
+
* Wave 4 placement alongside `build`. `depends: []` — no plugin-graph edge to `build`;
|
|
1171
|
+
* ordering via the framework's plugin registration array is sufficient. Consumers who want
|
|
1172
|
+
* auto-outdir resolution must register the `build` plugin alongside this one; otherwise the
|
|
1173
|
+
* `'dist'` default fallback applies.
|
|
1174
|
+
*
|
|
1175
|
+
* @see README.md
|
|
1176
|
+
*/
|
|
1177
|
+
const defaultConfig = {
|
|
1178
|
+
target: "pages",
|
|
1179
|
+
outdir: "dist",
|
|
1180
|
+
productionBranch: "main"
|
|
1181
|
+
};
|
|
1182
|
+
const deploy = require_factory.createPlugin("deploy", {
|
|
1183
|
+
config: defaultConfig,
|
|
1184
|
+
createState: createDeployState,
|
|
1185
|
+
api: createDeployApi,
|
|
1186
|
+
onInit: validateDeployConfig
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1006
1189
|
//#endregion
|
|
1007
1190
|
//#region src/project.ts
|
|
1008
1191
|
/**
|
|
@@ -1073,6 +1256,12 @@ Object.defineProperty(exports, 'content', {
|
|
|
1073
1256
|
return content;
|
|
1074
1257
|
}
|
|
1075
1258
|
});
|
|
1259
|
+
Object.defineProperty(exports, 'deploy', {
|
|
1260
|
+
enumerable: true,
|
|
1261
|
+
get: function () {
|
|
1262
|
+
return deploy;
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1076
1265
|
Object.defineProperty(exports, 'project', {
|
|
1077
1266
|
enumerable: true,
|
|
1078
1267
|
get: function () {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { l as site, o as head, p as createPlugin, s as router, u as i18n } from "./factory-DwpBwjDk.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as runWrangler, c as readWranglerConfig, i as extractDeploymentInfo, r as buildWranglerArgs } from "./wrangler-Coyrznz4.mjs";
|
|
3
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
4
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
|
-
import { resolve, sep } from "node:path";
|
|
5
|
+
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
5
6
|
import matter from "gray-matter";
|
|
6
7
|
import rehypeShiki from "@shikijs/rehype";
|
|
7
8
|
import rehypeRaw from "rehype-raw";
|
|
@@ -404,7 +405,7 @@ const wireContentApi = (ctx) => {
|
|
|
404
405
|
//#endregion
|
|
405
406
|
//#region src/plugins/content/index.ts
|
|
406
407
|
/** @file content plugin: markdown pipeline + invalidate(). Complex tier. */
|
|
407
|
-
const defaultConfig$
|
|
408
|
+
const defaultConfig$2 = {
|
|
408
409
|
dir: "",
|
|
409
410
|
trustedContent: false,
|
|
410
411
|
rehypePlugins: [],
|
|
@@ -412,7 +413,7 @@ const defaultConfig$1 = {
|
|
|
412
413
|
};
|
|
413
414
|
const content = createPlugin("content", {
|
|
414
415
|
depends: [i18n],
|
|
415
|
-
config: defaultConfig$
|
|
416
|
+
config: defaultConfig$2,
|
|
416
417
|
events: (register) => ({
|
|
417
418
|
"content:ready": register("Articles loaded"),
|
|
418
419
|
"content:invalidated": register("Paths invalidated")
|
|
@@ -943,7 +944,7 @@ const validateBuildConfig = (ctx) => {
|
|
|
943
944
|
//#endregion
|
|
944
945
|
//#region src/plugins/build/index.ts
|
|
945
946
|
/** @file build plugin: app.build.run() orchestrator + in-memory manifest. Complex tier. */
|
|
946
|
-
const defaultConfig = {
|
|
947
|
+
const defaultConfig$1 = {
|
|
947
948
|
outdir: "dist",
|
|
948
949
|
mode: "production",
|
|
949
950
|
renderMode: "hybrid"
|
|
@@ -955,7 +956,7 @@ const build = createPlugin("build", {
|
|
|
955
956
|
router,
|
|
956
957
|
head
|
|
957
958
|
],
|
|
958
|
-
config: defaultConfig,
|
|
959
|
+
config: defaultConfig$1,
|
|
959
960
|
events: (register) => ({
|
|
960
961
|
"build:phase": register("Build phase started"),
|
|
961
962
|
"build:complete": register("Build pipeline complete")
|
|
@@ -965,6 +966,188 @@ const build = createPlugin("build", {
|
|
|
965
966
|
onInit: validateBuildConfig
|
|
966
967
|
});
|
|
967
968
|
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region src/plugins/deploy/api.ts
|
|
971
|
+
/** @file deploy plugin API factory — orchestrates app.deploy.run(). */
|
|
972
|
+
/** Free-tier Cloudflare Pages limit. The paid tier (100,000) requires extra env. */
|
|
973
|
+
const FREE_TIER_FILE_LIMIT = 2e4;
|
|
974
|
+
/** Per-file size cap for Cloudflare Pages (25 MiB). */
|
|
975
|
+
const MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
|
|
976
|
+
const sanitizedProcessEnv = () => {
|
|
977
|
+
const out = {};
|
|
978
|
+
for (const [k, v] of Object.entries(process.env)) if (typeof v === "string") out[k] = v;
|
|
979
|
+
return out;
|
|
980
|
+
};
|
|
981
|
+
const checkFile = (path, state) => {
|
|
982
|
+
state.count += 1;
|
|
983
|
+
if (statSync(path).size > MAX_FILE_SIZE_BYTES) state.oversize = path;
|
|
984
|
+
};
|
|
985
|
+
const walkOutdir = (current, state) => {
|
|
986
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
987
|
+
if (state.oversize !== null) return;
|
|
988
|
+
const path = join(current, entry.name);
|
|
989
|
+
if (entry.isDirectory()) walkOutdir(path, state);
|
|
990
|
+
else if (entry.isFile()) checkFile(path, state);
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
/** Recursively count files and check max size under `dir`. Short-circuits on first oversize file. */
|
|
994
|
+
const inspectOutdir = (dir) => {
|
|
995
|
+
const state = {
|
|
996
|
+
count: 0,
|
|
997
|
+
oversize: null
|
|
998
|
+
};
|
|
999
|
+
walkOutdir(dir, state);
|
|
1000
|
+
return {
|
|
1001
|
+
fileCount: state.count,
|
|
1002
|
+
oversize: state.oversize
|
|
1003
|
+
};
|
|
1004
|
+
};
|
|
1005
|
+
/** Throw if any pre-flight constraint on the resolved outdir fails. */
|
|
1006
|
+
const assertOutdirReady = (outdir, outdirAbsolute) => {
|
|
1007
|
+
if (!existsSync(outdirAbsolute)) throw new Error(`deploy: outdir "${outdir}" does not exist — run \`moku build\` first (or pass --build).`);
|
|
1008
|
+
const inspection = inspectOutdir(outdirAbsolute);
|
|
1009
|
+
if (inspection.fileCount === 0) throw new Error(`deploy: outdir "${outdir}" is empty — nothing to deploy.`);
|
|
1010
|
+
if (inspection.fileCount > FREE_TIER_FILE_LIMIT) throw new Error(`deploy: outdir contains ${inspection.fileCount} files; free tier limit is ${FREE_TIER_FILE_LIMIT}. Paid tier supports up to 100,000 files (requires PAGES_WRANGLER_MAJOR_VERSION=4 in CI env).`);
|
|
1011
|
+
if (inspection.oversize !== null) throw new Error(`deploy: file "${inspection.oversize}" exceeds Cloudflare Pages per-file limit (25 MiB).`);
|
|
1012
|
+
return inspection;
|
|
1013
|
+
};
|
|
1014
|
+
/** Resolve outdir from `wrangler.jsonc#pages_build_output_dir` or throw if missing. */
|
|
1015
|
+
const resolveOutdir = (cwd) => {
|
|
1016
|
+
const wrangler = readWranglerConfig(cwd);
|
|
1017
|
+
if (wrangler === null) throw new Error("deploy: wrangler.jsonc not found — run `moku deploy init` first");
|
|
1018
|
+
return wrangler.pages_build_output_dir;
|
|
1019
|
+
};
|
|
1020
|
+
/**
|
|
1021
|
+
* Build the public `app.deploy` API surface.
|
|
1022
|
+
*
|
|
1023
|
+
* `run()` reads outdir from `wrangler.jsonc#pages_build_output_dir` (the
|
|
1024
|
+
* deploy-time SSoT — `DeployConfig.outdir` is only the init-time default).
|
|
1025
|
+
* Errors with a "run `moku deploy init` first" message if `wrangler.jsonc` is
|
|
1026
|
+
* missing.
|
|
1027
|
+
*
|
|
1028
|
+
* @param ctx - The deploy plugin context.
|
|
1029
|
+
* @param deps - Optional injectables for testing.
|
|
1030
|
+
* @returns The {@link DeployApi}.
|
|
1031
|
+
*/
|
|
1032
|
+
/** Trigger the optional pre-build hook for `--build`. */
|
|
1033
|
+
const maybeRunBuild = async (deps, build) => {
|
|
1034
|
+
if (build !== true) return;
|
|
1035
|
+
if (deps.runBuild === void 0) throw new Error("deploy: --build flag set but no `runBuild` hook provided to createDeployApi. Wire `app.build.run` through the plugin index.");
|
|
1036
|
+
await deps.runBuild();
|
|
1037
|
+
};
|
|
1038
|
+
/** Invoke wrangler and shape its output into a {@link DeployResult}. */
|
|
1039
|
+
const invokeWranglerForDeploy = async (ctx, deps, outdir, branch, startedAt, now) => {
|
|
1040
|
+
const env = deps.env ?? sanitizedProcessEnv();
|
|
1041
|
+
const info = extractDeploymentInfo((await runWrangler(buildWranglerArgs(ctx.config.target, outdir, branch), env, deps.spawn)).ndJson);
|
|
1042
|
+
if (info === null) ctx.log.warn("deploy:no-deployment-info", { note: "wrangler exited 0 but no deployment URL/id was found in ND-JSON output." });
|
|
1043
|
+
return {
|
|
1044
|
+
url: info?.url ?? "",
|
|
1045
|
+
deploymentId: info?.deploymentId ?? "",
|
|
1046
|
+
branch,
|
|
1047
|
+
durationMs: now() - startedAt
|
|
1048
|
+
};
|
|
1049
|
+
};
|
|
1050
|
+
/**
|
|
1051
|
+
* Build the public `app.deploy` API surface.
|
|
1052
|
+
*
|
|
1053
|
+
* `run()` reads outdir from `wrangler.jsonc#pages_build_output_dir` (the
|
|
1054
|
+
* deploy-time SSoT — `DeployConfig.outdir` is only the init-time default).
|
|
1055
|
+
* Errors with a "run `moku deploy init` first" message if `wrangler.jsonc` is
|
|
1056
|
+
* missing.
|
|
1057
|
+
*
|
|
1058
|
+
* @param ctx - The deploy plugin context.
|
|
1059
|
+
* @param deps - Optional injectables for testing.
|
|
1060
|
+
* @returns The {@link DeployApi}.
|
|
1061
|
+
*/
|
|
1062
|
+
const createDeployApi = (ctx, deps = {}) => {
|
|
1063
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1064
|
+
const now = deps.now ?? Date.now;
|
|
1065
|
+
const run = async (options = {}) => {
|
|
1066
|
+
await maybeRunBuild(deps, options.build);
|
|
1067
|
+
const outdir = resolveOutdir(cwd);
|
|
1068
|
+
const branch = options.branch ?? ctx.config.productionBranch;
|
|
1069
|
+
const inspection = assertOutdirReady(outdir, join(cwd, outdir));
|
|
1070
|
+
const deployResult = await invokeWranglerForDeploy(ctx, deps, outdir, branch, now(), now);
|
|
1071
|
+
ctx.state.lastDeployment = deployResult;
|
|
1072
|
+
ctx.log.info("deploy:complete", {
|
|
1073
|
+
url: deployResult.url,
|
|
1074
|
+
branch,
|
|
1075
|
+
durationMs: deployResult.durationMs,
|
|
1076
|
+
files: inspection.fileCount
|
|
1077
|
+
});
|
|
1078
|
+
return deployResult;
|
|
1079
|
+
};
|
|
1080
|
+
const getLastDeployment = () => ctx.state.lastDeployment;
|
|
1081
|
+
return {
|
|
1082
|
+
run,
|
|
1083
|
+
getLastDeployment
|
|
1084
|
+
};
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
//#endregion
|
|
1088
|
+
//#region src/plugins/deploy/state.ts
|
|
1089
|
+
/**
|
|
1090
|
+
* Create a fresh deploy state.
|
|
1091
|
+
*
|
|
1092
|
+
* `lastDeployment` starts as `null` and is set after the first successful
|
|
1093
|
+
* `app.deploy.run()` call. State is per-`createApp` instance — never a module
|
|
1094
|
+
* singleton — so parallel vitest workers do not share state.
|
|
1095
|
+
*
|
|
1096
|
+
* @returns A new {@link DeployState}.
|
|
1097
|
+
*/
|
|
1098
|
+
const createDeployState = () => ({ lastDeployment: null });
|
|
1099
|
+
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/plugins/deploy/validate.ts
|
|
1102
|
+
/** @file deploy plugin onInit hook — config-shape validation only. NEVER spawns wrangler. */
|
|
1103
|
+
const VALID_TARGETS = new Set(["pages", "workers"]);
|
|
1104
|
+
/**
|
|
1105
|
+
* Validate `DeployConfig` shape at `onInit` time.
|
|
1106
|
+
*
|
|
1107
|
+
* Checks: `target` is in the valid enum; `outdir` is a non-empty string and
|
|
1108
|
+
* does not escape the current working directory via `..` traversal;
|
|
1109
|
+
* `productionBranch` is a non-empty string.
|
|
1110
|
+
*
|
|
1111
|
+
* Does NOT check wrangler presence — `ensureWrangler()` in `wrangler.ts` is
|
|
1112
|
+
* lazy and called only on first `run()`/`init()` invocation. This keeps
|
|
1113
|
+
* `moku dev`, `moku build`, and `moku preview` unaffected by wrangler absence.
|
|
1114
|
+
*
|
|
1115
|
+
* @param ctx - The deploy plugin context.
|
|
1116
|
+
* @throws Error when any config field is structurally invalid.
|
|
1117
|
+
*/
|
|
1118
|
+
const validateDeployConfig = (ctx) => {
|
|
1119
|
+
const { target, outdir, productionBranch } = ctx.config;
|
|
1120
|
+
if (!VALID_TARGETS.has(target)) throw new Error(`deploy: target must be "pages" or "workers" (got "${target}")`);
|
|
1121
|
+
if (typeof outdir !== "string" || outdir === "") throw new Error("deploy: outdir must be a non-empty string");
|
|
1122
|
+
const resolved = isAbsolute(outdir) ? outdir : resolve(process.cwd(), outdir);
|
|
1123
|
+
const cwd = process.cwd();
|
|
1124
|
+
if (!resolved.startsWith(cwd)) throw new Error(`deploy: outdir "${outdir}" must resolve inside the project root (got "${resolved}")`);
|
|
1125
|
+
if (typeof productionBranch !== "string" || productionBranch === "") throw new Error("deploy: productionBranch must be a non-empty string");
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
//#endregion
|
|
1129
|
+
//#region src/plugins/deploy/index.ts
|
|
1130
|
+
/** @file deploy plugin: Cloudflare Pages publishing via wrangler subprocess. Standard tier.
|
|
1131
|
+
*
|
|
1132
|
+
* Wave 4 placement alongside `build`. `depends: []` — no plugin-graph edge to `build`;
|
|
1133
|
+
* ordering via the framework's plugin registration array is sufficient. Consumers who want
|
|
1134
|
+
* auto-outdir resolution must register the `build` plugin alongside this one; otherwise the
|
|
1135
|
+
* `'dist'` default fallback applies.
|
|
1136
|
+
*
|
|
1137
|
+
* @see README.md
|
|
1138
|
+
*/
|
|
1139
|
+
const defaultConfig = {
|
|
1140
|
+
target: "pages",
|
|
1141
|
+
outdir: "dist",
|
|
1142
|
+
productionBranch: "main"
|
|
1143
|
+
};
|
|
1144
|
+
const deploy = createPlugin("deploy", {
|
|
1145
|
+
config: defaultConfig,
|
|
1146
|
+
createState: createDeployState,
|
|
1147
|
+
api: createDeployApi,
|
|
1148
|
+
onInit: validateDeployConfig
|
|
1149
|
+
});
|
|
1150
|
+
|
|
968
1151
|
//#endregion
|
|
969
1152
|
//#region src/project.ts
|
|
970
1153
|
/**
|
|
@@ -1017,4 +1200,4 @@ function extractSpecs(routes) {
|
|
|
1017
1200
|
}
|
|
1018
1201
|
|
|
1019
1202
|
//#endregion
|
|
1020
|
-
export {
|
|
1203
|
+
export { content as i, deploy as n, build as r, project as t };
|
|
@@ -150,6 +150,54 @@ type I18nApi<TLocales extends readonly string[] = readonly string[]> = {
|
|
|
150
150
|
t(locale: TLocales[number], key: string): string;
|
|
151
151
|
};
|
|
152
152
|
//#endregion
|
|
153
|
+
//#region src/plugins/content/types.d.ts
|
|
154
|
+
type Frontmatter = {
|
|
155
|
+
title: string;
|
|
156
|
+
date: string;
|
|
157
|
+
description: string;
|
|
158
|
+
tags: string[];
|
|
159
|
+
language: string;
|
|
160
|
+
author?: string;
|
|
161
|
+
draft?: boolean;
|
|
162
|
+
[key: string]: unknown;
|
|
163
|
+
};
|
|
164
|
+
type ComputedFields = {
|
|
165
|
+
contentId: string;
|
|
166
|
+
readingTimeMinutes: number;
|
|
167
|
+
wordCount: number;
|
|
168
|
+
};
|
|
169
|
+
type Article = {
|
|
170
|
+
slug: string;
|
|
171
|
+
locale: string;
|
|
172
|
+
frontmatter: Frontmatter;
|
|
173
|
+
html: string;
|
|
174
|
+
computed: ComputedFields;
|
|
175
|
+
};
|
|
176
|
+
type RehypePluginEntry = readonly [unknown, Record<string, unknown>?];
|
|
177
|
+
type RemarkPluginEntry = readonly [unknown, Record<string, unknown>?];
|
|
178
|
+
type ContentConfig = {
|
|
179
|
+
dir: string;
|
|
180
|
+
defaultAuthor?: string;
|
|
181
|
+
trustedContent: boolean;
|
|
182
|
+
rehypePlugins?: RehypePluginEntry[];
|
|
183
|
+
remarkPlugins?: RemarkPluginEntry[];
|
|
184
|
+
};
|
|
185
|
+
type ContentState = {
|
|
186
|
+
processor: Processor | null;
|
|
187
|
+
articles: Map<string, Map<string, Article>>;
|
|
188
|
+
slugs: string[] | null;
|
|
189
|
+
lastPaths: Set<string>; /** Paths marked stale by `invalidate()` — re-read on next `loadAll()` then cleared. */
|
|
190
|
+
dirtyPaths: Set<string>;
|
|
191
|
+
};
|
|
192
|
+
type ContentApi = {
|
|
193
|
+
loadAll(): Promise<Map<string, Article[]>>;
|
|
194
|
+
load(slug: string, locale: string): Promise<Article | null>;
|
|
195
|
+
discoverSlugs(): Promise<string[]>;
|
|
196
|
+
render(markdown: string): Promise<string>;
|
|
197
|
+
invalidate(paths: string[]): void;
|
|
198
|
+
reset(): void;
|
|
199
|
+
};
|
|
200
|
+
//#endregion
|
|
153
201
|
//#region src/plugins/router/types.d.ts
|
|
154
202
|
/** @file router plugin types — RouteSpec (non-accumulating), RouteBuilder, RouterApi. */
|
|
155
203
|
/** Render context passed to route handlers. */
|
|
@@ -222,54 +270,6 @@ type RouterApi<Routes extends Record<string, RouteSpec> = Record<string, RouteSp
|
|
|
222
270
|
entries(): ReadonlyArray<RouteEntry>;
|
|
223
271
|
};
|
|
224
272
|
//#endregion
|
|
225
|
-
//#region src/plugins/content/types.d.ts
|
|
226
|
-
type Frontmatter = {
|
|
227
|
-
title: string;
|
|
228
|
-
date: string;
|
|
229
|
-
description: string;
|
|
230
|
-
tags: string[];
|
|
231
|
-
language: string;
|
|
232
|
-
author?: string;
|
|
233
|
-
draft?: boolean;
|
|
234
|
-
[key: string]: unknown;
|
|
235
|
-
};
|
|
236
|
-
type ComputedFields = {
|
|
237
|
-
contentId: string;
|
|
238
|
-
readingTimeMinutes: number;
|
|
239
|
-
wordCount: number;
|
|
240
|
-
};
|
|
241
|
-
type Article = {
|
|
242
|
-
slug: string;
|
|
243
|
-
locale: string;
|
|
244
|
-
frontmatter: Frontmatter;
|
|
245
|
-
html: string;
|
|
246
|
-
computed: ComputedFields;
|
|
247
|
-
};
|
|
248
|
-
type RehypePluginEntry = readonly [unknown, Record<string, unknown>?];
|
|
249
|
-
type RemarkPluginEntry = readonly [unknown, Record<string, unknown>?];
|
|
250
|
-
type ContentConfig = {
|
|
251
|
-
dir: string;
|
|
252
|
-
defaultAuthor?: string;
|
|
253
|
-
trustedContent: boolean;
|
|
254
|
-
rehypePlugins?: RehypePluginEntry[];
|
|
255
|
-
remarkPlugins?: RemarkPluginEntry[];
|
|
256
|
-
};
|
|
257
|
-
type ContentState = {
|
|
258
|
-
processor: Processor | null;
|
|
259
|
-
articles: Map<string, Map<string, Article>>;
|
|
260
|
-
slugs: string[] | null;
|
|
261
|
-
lastPaths: Set<string>; /** Paths marked stale by `invalidate()` — re-read on next `loadAll()` then cleared. */
|
|
262
|
-
dirtyPaths: Set<string>;
|
|
263
|
-
};
|
|
264
|
-
type ContentApi = {
|
|
265
|
-
loadAll(): Promise<Map<string, Article[]>>;
|
|
266
|
-
load(slug: string, locale: string): Promise<Article | null>;
|
|
267
|
-
discoverSlugs(): Promise<string[]>;
|
|
268
|
-
render(markdown: string): Promise<string>;
|
|
269
|
-
invalidate(paths: string[]): void;
|
|
270
|
-
reset(): void;
|
|
271
|
-
};
|
|
272
|
-
//#endregion
|
|
273
273
|
//#region src/plugins/build/types.d.ts
|
|
274
274
|
type BuildConfig = {
|
|
275
275
|
outdir: string;
|
|
@@ -299,6 +299,70 @@ type BuildApi = {
|
|
|
299
299
|
getManifest(): Readonly<BundleManifest> | null;
|
|
300
300
|
};
|
|
301
301
|
//#endregion
|
|
302
|
+
//#region src/plugins/deploy/types.d.ts
|
|
303
|
+
/** @file deploy plugin types — DeployConfig, DeployState, DeployResult, RunOptions, DeployApi. */
|
|
304
|
+
/**
|
|
305
|
+
* Deployment target. Phase 1 ships `'pages'` only; `'workers'` is reserved
|
|
306
|
+
* for the Workers Static Assets expansion path and `buildWranglerArgs`
|
|
307
|
+
* throws "not implemented in Phase 1" if invoked with it.
|
|
308
|
+
*/
|
|
309
|
+
type DeployTarget = 'pages' | 'workers';
|
|
310
|
+
/**
|
|
311
|
+
* deploy plugin config.
|
|
312
|
+
*
|
|
313
|
+
* `outdir` precedence at deploy time: `wrangler.jsonc#pages_build_output_dir`
|
|
314
|
+
* (written by `moku deploy init`) — this is the deploy-time single source of
|
|
315
|
+
* truth, NOT `DeployConfig.outdir`. The config field is the *init-time* default;
|
|
316
|
+
* once `wrangler.jsonc` exists, it owns the value.
|
|
317
|
+
*
|
|
318
|
+
* Forbidden: reading the `build` plugin's config via `ctx.has(build)` /
|
|
319
|
+
* `ctx.require(build).config.outdir`. The core `HasFunction` takes a name
|
|
320
|
+
* string and returns a non-narrowing boolean; `RequireFunction` returns the
|
|
321
|
+
* API surface only (`BuildApi = { run, getManifest }`). The optional coupling
|
|
322
|
+
* is a `ctx.log.warn` at init time when divergence is detectable through the
|
|
323
|
+
* directly-imported `build` plugin instance, never via the core API.
|
|
324
|
+
*/
|
|
325
|
+
type DeployConfig = {
|
|
326
|
+
/** Deployment target. Default: `'pages'`. */target: DeployTarget; /** Build output directory used at init only. Default: `'dist'` (matches build plugin default). */
|
|
327
|
+
outdir: string; /** Production branch name. Default: `'main'`. Overwritten by `init` via `git symbolic-ref`. */
|
|
328
|
+
productionBranch: string; /** Optional explicit override for the Cloudflare Pages project name. Default: derived from `site.name`. */
|
|
329
|
+
projectName?: string;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* In-memory deploy plugin state.
|
|
333
|
+
*
|
|
334
|
+
* `lastDeployment` is reset per `createApp()` call and captures the most
|
|
335
|
+
* recent deploy's metadata (URL, deployment ID, branch, duration). Used by
|
|
336
|
+
* the future REST-API-based rollback command (deferred from Phase 1).
|
|
337
|
+
*/
|
|
338
|
+
type DeployState = {
|
|
339
|
+
lastDeployment: DeployResult | null;
|
|
340
|
+
};
|
|
341
|
+
/** Result of a successful `app.deploy.run()` invocation. */
|
|
342
|
+
type DeployResult = {
|
|
343
|
+
/** Production URL (e.g., `https://<project>.pages.dev`) or branch-alias preview URL. */url: string; /** Cloudflare deployment ID — captured for future rollback support. */
|
|
344
|
+
deploymentId: string; /** Branch the deploy was published to. */
|
|
345
|
+
branch: string; /** Total subprocess duration in milliseconds (init + upload + finalize). */
|
|
346
|
+
durationMs: number;
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* Options accepted by `app.deploy.run()`.
|
|
350
|
+
*
|
|
351
|
+
* `branch` overrides the production branch from wrangler.jsonc (defaults to
|
|
352
|
+
* `DeployConfig.productionBranch`). `build` runs `app.build.run()` first
|
|
353
|
+
* when true — used only for local convenience; the generated CI workflow
|
|
354
|
+
* always issues the two-step `moku build` then `moku deploy` pattern.
|
|
355
|
+
*/
|
|
356
|
+
type RunOptions = {
|
|
357
|
+
/** Override the production branch for this deploy. */branch?: string; /** Run `app.build.run()` first (local convenience). Default: `false`. */
|
|
358
|
+
build?: boolean;
|
|
359
|
+
};
|
|
360
|
+
/** Public deploy plugin API surface, attached as `app.deploy`. */
|
|
361
|
+
type DeployApi = {
|
|
362
|
+
/** Run a deploy. Reads outdir from wrangler.jsonc; errors if missing. */run(options?: RunOptions): Promise<DeployResult>; /** Read the last deployment result without re-running. */
|
|
363
|
+
getLastDeployment(): Readonly<DeployResult> | null;
|
|
364
|
+
};
|
|
365
|
+
//#endregion
|
|
302
366
|
//#region src/project.d.ts
|
|
303
367
|
/**
|
|
304
368
|
* Map each consumer `RouteBuilder` to the `RouteSpec` shape the router stores.
|
|
@@ -346,4 +410,4 @@ type WebAppConfig<Routes extends Record<string, RouteBuilder>> = {
|
|
|
346
410
|
*/
|
|
347
411
|
declare function route(pattern: string): RouteBuilder;
|
|
348
412
|
//#endregion
|
|
349
|
-
export {
|
|
413
|
+
export { EnvVarSpec as A, SiteState as C, EnvConfig as D, EnvApi as E, LogState as M, EnvProvider as O, SiteApi as S, Events as T, ContentConfig as _, DeployConfig as a, I18nConfig as b, BuildConfig as c, RouteSpec as d, RouterApi as f, ContentApi as g, Article as h, DeployApi as i, LogApi as j, EnvState as k, BuildState as l, RouterState as m, RouteSpecMap as n, DeployState as o, RouterConfig as p, WebAppConfig as r, BuildApi as s, route as t, RouteBuilder as u, ContentState as v, Config as w, I18nState as x, I18nApi as y };
|
package/dist/test.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_project = require('./project-
|
|
3
|
-
const require_factory = require('./factory-
|
|
2
|
+
const require_project = require('./project-1pAh4RxJ.cjs');
|
|
3
|
+
const require_factory = require('./factory-DVcAQYEZ.cjs');
|
|
4
4
|
const require_index = require('./index.cjs');
|
|
5
5
|
let _moku_labs_core = require("@moku-labs/core");
|
|
6
6
|
|