@mokup/cli 1.0.5 → 1.0.6

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/index.cjs CHANGED
@@ -10,6 +10,7 @@ const esbuild = require('@mokup/shared/esbuild');
10
10
  const runtime = require('@mokup/runtime');
11
11
  const jsoncParser = require('@mokup/shared/jsonc-parser');
12
12
  const node = require('@mokup/server/node');
13
+ const logger$1 = require('@mokup/shared/logger');
13
14
  const commander = require('commander');
14
15
 
15
16
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -74,6 +75,7 @@ async function writeManifestModule(outDir, manifest) {
74
75
  }
75
76
 
76
77
  const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
78
+ const middlewareSymbol$1 = Symbol.for("mokup.config.middlewares");
77
79
  async function loadModule$1(file) {
78
80
  const ext = configExtensions.find((extension) => file.endsWith(extension));
79
81
  if (ext === ".cjs") {
@@ -134,7 +136,7 @@ async function loadConfig(file) {
134
136
  }
135
137
  return value;
136
138
  }
137
- function normalizeMiddlewares(value, source, log) {
139
+ function normalizeMiddlewares(value, source, log, position) {
138
140
  if (!value) {
139
141
  return [];
140
142
  }
@@ -145,10 +147,22 @@ function normalizeMiddlewares(value, source, log) {
145
147
  log?.(`Invalid middleware in ${source}`);
146
148
  return;
147
149
  }
148
- middlewares.push({ file: source, index });
150
+ middlewares.push({ file: source, index, position });
149
151
  });
150
152
  return middlewares;
151
153
  }
154
+ function readMiddlewareMeta(config) {
155
+ const value = config[middlewareSymbol$1];
156
+ if (!value || typeof value !== "object") {
157
+ return null;
158
+ }
159
+ const meta = value;
160
+ return {
161
+ pre: Array.isArray(meta.pre) ? meta.pre : [],
162
+ normal: Array.isArray(meta.normal) ? meta.normal : [],
163
+ post: Array.isArray(meta.post) ? meta.post : []
164
+ };
165
+ }
152
166
  async function resolveDirectoryConfig(params) {
153
167
  const { file, rootDir, log, configCache, fileCache } = params;
154
168
  const resolvedRoot = pathe.normalize(rootDir);
@@ -167,7 +181,10 @@ async function resolveDirectoryConfig(params) {
167
181
  current = parent;
168
182
  }
169
183
  chain.reverse();
170
- const merged = { middlewares: [] };
184
+ const merged = {};
185
+ const preMiddlewares = [];
186
+ const normalMiddlewares = [];
187
+ const postMiddlewares = [];
171
188
  for (const dir of chain) {
172
189
  const configPath = await findConfigFile(dir, fileCache);
173
190
  if (!configPath) {
@@ -203,12 +220,48 @@ async function resolveDirectoryConfig(params) {
203
220
  if (typeof config.exclude !== "undefined") {
204
221
  merged.exclude = config.exclude;
205
222
  }
206
- const normalized = normalizeMiddlewares(config.middleware, configPath, log);
207
- if (normalized.length > 0) {
208
- merged.middlewares.push(...normalized);
223
+ const meta = readMiddlewareMeta(config);
224
+ const normalizedPre = normalizeMiddlewares(
225
+ meta?.pre,
226
+ configPath,
227
+ log,
228
+ "pre"
229
+ );
230
+ const normalizedNormal = normalizeMiddlewares(
231
+ meta?.normal,
232
+ configPath,
233
+ log,
234
+ "normal"
235
+ );
236
+ const normalizedLegacy = normalizeMiddlewares(
237
+ config.middleware,
238
+ configPath,
239
+ log,
240
+ "normal"
241
+ );
242
+ const normalizedPost = normalizeMiddlewares(
243
+ meta?.post,
244
+ configPath,
245
+ log,
246
+ "post"
247
+ );
248
+ if (normalizedPre.length > 0) {
249
+ preMiddlewares.push(...normalizedPre);
250
+ }
251
+ if (normalizedNormal.length > 0) {
252
+ normalMiddlewares.push(...normalizedNormal);
253
+ }
254
+ if (normalizedLegacy.length > 0) {
255
+ normalMiddlewares.push(...normalizedLegacy);
256
+ }
257
+ if (normalizedPost.length > 0) {
258
+ postMiddlewares.push(...normalizedPost);
209
259
  }
210
260
  }
211
- return merged;
261
+ return {
262
+ ...merged,
263
+ middlewares: [...preMiddlewares, ...normalMiddlewares, ...postMiddlewares]
264
+ };
212
265
  }
213
266
 
214
267
  function toPosix(value) {
@@ -763,6 +816,40 @@ async function buildManifest(options = {}) {
763
816
  };
764
817
  }
765
818
 
819
+ const middlewareSymbol = Symbol.for("mokup.config.middlewares");
820
+ function createRegistry(list) {
821
+ return {
822
+ use: (...handlers) => {
823
+ list.push(...handlers);
824
+ }
825
+ };
826
+ }
827
+ function attachMetadata(config, meta) {
828
+ Object.defineProperty(config, middlewareSymbol, {
829
+ value: meta,
830
+ enumerable: false
831
+ });
832
+ return config;
833
+ }
834
+ function defineConfig(input) {
835
+ if (typeof input === "function") {
836
+ const pre = [];
837
+ const normal = [];
838
+ const post = [];
839
+ const context = {
840
+ pre: createRegistry(pre),
841
+ normal: createRegistry(normal),
842
+ post: createRegistry(post)
843
+ };
844
+ const result = input(context);
845
+ const config2 = result && typeof result === "object" ? result : {};
846
+ return attachMetadata(config2, { pre, normal, post });
847
+ }
848
+ const config = input && typeof input === "object" ? input : {};
849
+ return attachMetadata(config, { pre: [], normal: [], post: [] });
850
+ }
851
+
852
+ const logger = logger$1.createLogger();
766
853
  function collectValues(value, previous) {
767
854
  return [...previous ?? [], value];
768
855
  }
@@ -775,7 +862,7 @@ function toBuildOptions(options) {
775
862
  const buildOptions = {
776
863
  handlers: options.handlers !== false,
777
864
  log: (message) => {
778
- console.log(message);
865
+ logger.info(message);
779
866
  }
780
867
  };
781
868
  if (options.dir && options.dir.length > 0) {
@@ -846,7 +933,7 @@ function createCli() {
846
933
  const host = entry.host ?? "localhost";
847
934
  const port = entry.port ?? 8080;
848
935
  const playgroundEnabled = playground !== false;
849
- const playgroundPath = "/_mokup";
936
+ const playgroundPath = "/__mokup";
850
937
  const server = await node.createFetchServer({
851
938
  entries: entry,
852
939
  playground
@@ -860,9 +947,9 @@ function createCli() {
860
947
  (info) => {
861
948
  const resolvedHost = typeof info === "string" ? host : info?.address ?? host;
862
949
  const resolvedPort = typeof info === "string" ? port : info?.port ?? port;
863
- console.log(`Mock server ready at http://${resolvedHost}:${resolvedPort}`);
950
+ logger.info(`Mock server ready at http://${resolvedHost}:${resolvedPort}`);
864
951
  if (playgroundEnabled) {
865
- console.log(`Playground at http://${resolvedHost}:${resolvedPort}${playgroundPath}`);
952
+ logger.info(`Playground at http://${resolvedHost}:${resolvedPort}${playgroundPath}`);
866
953
  }
867
954
  }
868
955
  );
@@ -904,4 +991,5 @@ async function runCli(argv = process__default.argv) {
904
991
 
905
992
  exports.buildManifest = buildManifest;
906
993
  exports.createCli = createCli;
994
+ exports.defineConfig = defineConfig;
907
995
  exports.runCli = runCli;
package/dist/index.d.cts CHANGED
@@ -1,25 +1,264 @@
1
1
  import { Manifest } from '@mokup/runtime';
2
+ import { MiddlewareHandler } from '@mokup/shared/hono';
2
3
  import { Command } from 'commander';
3
4
 
5
+ /**
6
+ * Options for building a mokup manifest.
7
+ *
8
+ * @example
9
+ * import type { BuildOptions } from '@mokup/cli'
10
+ *
11
+ * const options: BuildOptions = {
12
+ * dir: 'mock',
13
+ * outDir: '.mokup',
14
+ * }
15
+ */
4
16
  interface BuildOptions {
17
+ /**
18
+ * Directory or directories to scan for mock files.
19
+ *
20
+ * @default "mock"
21
+ */
5
22
  dir?: string | string[];
23
+ /**
24
+ * Output directory for manifest artifacts.
25
+ *
26
+ * @default ".mokup"
27
+ */
6
28
  outDir?: string;
29
+ /**
30
+ * URL prefix to apply to generated routes.
31
+ *
32
+ * @default ""
33
+ */
7
34
  prefix?: string;
35
+ /**
36
+ * Include filter for files.
37
+ *
38
+ * @default undefined
39
+ */
8
40
  include?: RegExp | RegExp[];
41
+ /**
42
+ * Exclude filter for files.
43
+ *
44
+ * @default undefined
45
+ */
9
46
  exclude?: RegExp | RegExp[];
47
+ /**
48
+ * Ignore file or folder prefixes.
49
+ *
50
+ * @default ["."]
51
+ */
10
52
  ignorePrefix?: string | string[];
53
+ /**
54
+ * Emit handler bundles for module-based responses.
55
+ *
56
+ * @default true
57
+ */
11
58
  handlers?: boolean;
59
+ /**
60
+ * Project root used to resolve paths.
61
+ *
62
+ * @default process.cwd()
63
+ */
12
64
  root?: string;
65
+ /**
66
+ * Optional logger for build messages.
67
+ *
68
+ * @default undefined
69
+ */
13
70
  log?: (message: string) => void;
14
71
  }
72
+ /**
73
+ * Route rule shape used in build-time resolution.
74
+ *
75
+ * @example
76
+ * import type { RouteRule } from '@mokup/cli'
77
+ *
78
+ * const rule: RouteRule = { handler: { ok: true } }
79
+ */
80
+ interface RouteRule {
81
+ /** Route handler or static value. */
82
+ handler: unknown;
83
+ /**
84
+ * Enable or disable this rule.
85
+ *
86
+ * @default true
87
+ */
88
+ enabled?: boolean;
89
+ /**
90
+ * Override response status code.
91
+ *
92
+ * @default 200
93
+ */
94
+ status?: number;
95
+ /**
96
+ * Additional response headers.
97
+ *
98
+ * @default {}
99
+ */
100
+ headers?: Record<string, string>;
101
+ /**
102
+ * Delay in milliseconds before responding.
103
+ *
104
+ * @default 0
105
+ */
106
+ delay?: number;
107
+ }
108
+ /**
109
+ * Directory-level config used during manifest build.
110
+ *
111
+ * @example
112
+ * import type { RouteDirectoryConfig } from '@mokup/cli'
113
+ *
114
+ * const config: RouteDirectoryConfig = {
115
+ * headers: { 'x-mokup': 'dir' },
116
+ * }
117
+ */
118
+ interface RouteDirectoryConfig {
119
+ /**
120
+ * Headers applied to routes in this directory.
121
+ *
122
+ * @default {}
123
+ */
124
+ headers?: Record<string, string>;
125
+ /**
126
+ * Default status code override.
127
+ *
128
+ * @default 200
129
+ */
130
+ status?: number;
131
+ /**
132
+ * Default delay in milliseconds.
133
+ *
134
+ * @default 0
135
+ */
136
+ delay?: number;
137
+ /**
138
+ * Enable or disable this directory.
139
+ *
140
+ * @default true
141
+ */
142
+ enabled?: boolean;
143
+ /**
144
+ * Ignore prefixes applied to files in this directory.
145
+ *
146
+ * @default ["."]
147
+ */
148
+ ignorePrefix?: string | string[];
149
+ /**
150
+ * Include filter for files.
151
+ *
152
+ * @default undefined
153
+ */
154
+ include?: RegExp | RegExp[];
155
+ /**
156
+ * Exclude filter for files.
157
+ *
158
+ * @default undefined
159
+ */
160
+ exclude?: RegExp | RegExp[];
161
+ /**
162
+ * Middleware for the directory.
163
+ *
164
+ * @default undefined
165
+ */
166
+ middleware?: MiddlewareHandler | MiddlewareHandler[];
167
+ }
168
+ /**
169
+ * Middleware execution position.
170
+ *
171
+ * @example
172
+ * import type { MiddlewarePosition } from '@mokup/cli'
173
+ *
174
+ * const position: MiddlewarePosition = 'pre'
175
+ */
176
+ type MiddlewarePosition = 'pre' | 'normal' | 'post';
177
+ /**
178
+ * Middleware registry used by defineConfig.
179
+ *
180
+ * @example
181
+ * import type { MiddlewareRegistry } from '@mokup/cli'
182
+ *
183
+ * const registry: MiddlewareRegistry = { use: () => {} }
184
+ */
185
+ interface MiddlewareRegistry {
186
+ use: (...handlers: MiddlewareHandler[]) => void;
187
+ }
15
188
 
189
+ /**
190
+ * Build and write a mokup manifest to disk.
191
+ *
192
+ * @param options - Build options.
193
+ * @returns Build output metadata.
194
+ *
195
+ * @example
196
+ * import { buildManifest } from '@mokup/cli'
197
+ *
198
+ * const result = await buildManifest({ dir: 'mock', outDir: '.mokup' })
199
+ */
16
200
  declare function buildManifest(options?: BuildOptions): Promise<{
17
201
  manifest: Manifest;
18
202
  manifestPath: string;
19
203
  }>;
20
204
 
205
+ type DefineConfigFactory = (context: {
206
+ pre: MiddlewareRegistry;
207
+ normal: MiddlewareRegistry;
208
+ post: MiddlewareRegistry;
209
+ }) => RouteDirectoryConfig | void;
210
+ /**
211
+ * Define a directory config with Hono-style middleware registration.
212
+ *
213
+ * @param input - Config object or factory callback.
214
+ * @returns Route directory config with middleware metadata.
215
+ *
216
+ * @example
217
+ * import { defineConfig } from '@mokup/cli'
218
+ *
219
+ * export default defineConfig(({ pre, normal, post }) => {
220
+ * pre.use(async (c, next) => {
221
+ * c.header('x-before', '1')
222
+ * await next()
223
+ * })
224
+ *
225
+ * normal.use(async (_c, next) => {
226
+ * await next()
227
+ * })
228
+ *
229
+ * post.use(async (c, next) => {
230
+ * await next()
231
+ * c.header('x-after', '1')
232
+ * })
233
+ *
234
+ * return { delay: 120 }
235
+ * })
236
+ */
237
+ declare function defineConfig(input: RouteDirectoryConfig | DefineConfigFactory): RouteDirectoryConfig;
238
+
239
+ /**
240
+ * Create the mokup CLI program instance.
241
+ *
242
+ * @returns A configured CLI program.
243
+ *
244
+ * @example
245
+ * import { createCli } from '@mokup/cli'
246
+ *
247
+ * const cli = createCli()
248
+ */
21
249
  declare function createCli(): Command;
250
+ /**
251
+ * Run the mokup CLI with the provided argv.
252
+ *
253
+ * @param argv - CLI arguments.
254
+ * @returns Exit code or void.
255
+ *
256
+ * @example
257
+ * import { runCli } from '@mokup/cli'
258
+ *
259
+ * await runCli(['node', 'mokup', 'build', '--dir', 'mock'])
260
+ */
22
261
  declare function runCli(argv?: string[]): Promise<void>;
23
262
 
24
- export { buildManifest, createCli, runCli };
25
- export type { BuildOptions };
263
+ export { buildManifest, createCli, defineConfig, runCli };
264
+ export type { BuildOptions, MiddlewarePosition, MiddlewareRegistry, RouteDirectoryConfig, RouteRule };
package/dist/index.d.mts CHANGED
@@ -1,25 +1,264 @@
1
1
  import { Manifest } from '@mokup/runtime';
2
+ import { MiddlewareHandler } from '@mokup/shared/hono';
2
3
  import { Command } from 'commander';
3
4
 
5
+ /**
6
+ * Options for building a mokup manifest.
7
+ *
8
+ * @example
9
+ * import type { BuildOptions } from '@mokup/cli'
10
+ *
11
+ * const options: BuildOptions = {
12
+ * dir: 'mock',
13
+ * outDir: '.mokup',
14
+ * }
15
+ */
4
16
  interface BuildOptions {
17
+ /**
18
+ * Directory or directories to scan for mock files.
19
+ *
20
+ * @default "mock"
21
+ */
5
22
  dir?: string | string[];
23
+ /**
24
+ * Output directory for manifest artifacts.
25
+ *
26
+ * @default ".mokup"
27
+ */
6
28
  outDir?: string;
29
+ /**
30
+ * URL prefix to apply to generated routes.
31
+ *
32
+ * @default ""
33
+ */
7
34
  prefix?: string;
35
+ /**
36
+ * Include filter for files.
37
+ *
38
+ * @default undefined
39
+ */
8
40
  include?: RegExp | RegExp[];
41
+ /**
42
+ * Exclude filter for files.
43
+ *
44
+ * @default undefined
45
+ */
9
46
  exclude?: RegExp | RegExp[];
47
+ /**
48
+ * Ignore file or folder prefixes.
49
+ *
50
+ * @default ["."]
51
+ */
10
52
  ignorePrefix?: string | string[];
53
+ /**
54
+ * Emit handler bundles for module-based responses.
55
+ *
56
+ * @default true
57
+ */
11
58
  handlers?: boolean;
59
+ /**
60
+ * Project root used to resolve paths.
61
+ *
62
+ * @default process.cwd()
63
+ */
12
64
  root?: string;
65
+ /**
66
+ * Optional logger for build messages.
67
+ *
68
+ * @default undefined
69
+ */
13
70
  log?: (message: string) => void;
14
71
  }
72
+ /**
73
+ * Route rule shape used in build-time resolution.
74
+ *
75
+ * @example
76
+ * import type { RouteRule } from '@mokup/cli'
77
+ *
78
+ * const rule: RouteRule = { handler: { ok: true } }
79
+ */
80
+ interface RouteRule {
81
+ /** Route handler or static value. */
82
+ handler: unknown;
83
+ /**
84
+ * Enable or disable this rule.
85
+ *
86
+ * @default true
87
+ */
88
+ enabled?: boolean;
89
+ /**
90
+ * Override response status code.
91
+ *
92
+ * @default 200
93
+ */
94
+ status?: number;
95
+ /**
96
+ * Additional response headers.
97
+ *
98
+ * @default {}
99
+ */
100
+ headers?: Record<string, string>;
101
+ /**
102
+ * Delay in milliseconds before responding.
103
+ *
104
+ * @default 0
105
+ */
106
+ delay?: number;
107
+ }
108
+ /**
109
+ * Directory-level config used during manifest build.
110
+ *
111
+ * @example
112
+ * import type { RouteDirectoryConfig } from '@mokup/cli'
113
+ *
114
+ * const config: RouteDirectoryConfig = {
115
+ * headers: { 'x-mokup': 'dir' },
116
+ * }
117
+ */
118
+ interface RouteDirectoryConfig {
119
+ /**
120
+ * Headers applied to routes in this directory.
121
+ *
122
+ * @default {}
123
+ */
124
+ headers?: Record<string, string>;
125
+ /**
126
+ * Default status code override.
127
+ *
128
+ * @default 200
129
+ */
130
+ status?: number;
131
+ /**
132
+ * Default delay in milliseconds.
133
+ *
134
+ * @default 0
135
+ */
136
+ delay?: number;
137
+ /**
138
+ * Enable or disable this directory.
139
+ *
140
+ * @default true
141
+ */
142
+ enabled?: boolean;
143
+ /**
144
+ * Ignore prefixes applied to files in this directory.
145
+ *
146
+ * @default ["."]
147
+ */
148
+ ignorePrefix?: string | string[];
149
+ /**
150
+ * Include filter for files.
151
+ *
152
+ * @default undefined
153
+ */
154
+ include?: RegExp | RegExp[];
155
+ /**
156
+ * Exclude filter for files.
157
+ *
158
+ * @default undefined
159
+ */
160
+ exclude?: RegExp | RegExp[];
161
+ /**
162
+ * Middleware for the directory.
163
+ *
164
+ * @default undefined
165
+ */
166
+ middleware?: MiddlewareHandler | MiddlewareHandler[];
167
+ }
168
+ /**
169
+ * Middleware execution position.
170
+ *
171
+ * @example
172
+ * import type { MiddlewarePosition } from '@mokup/cli'
173
+ *
174
+ * const position: MiddlewarePosition = 'pre'
175
+ */
176
+ type MiddlewarePosition = 'pre' | 'normal' | 'post';
177
+ /**
178
+ * Middleware registry used by defineConfig.
179
+ *
180
+ * @example
181
+ * import type { MiddlewareRegistry } from '@mokup/cli'
182
+ *
183
+ * const registry: MiddlewareRegistry = { use: () => {} }
184
+ */
185
+ interface MiddlewareRegistry {
186
+ use: (...handlers: MiddlewareHandler[]) => void;
187
+ }
15
188
 
189
+ /**
190
+ * Build and write a mokup manifest to disk.
191
+ *
192
+ * @param options - Build options.
193
+ * @returns Build output metadata.
194
+ *
195
+ * @example
196
+ * import { buildManifest } from '@mokup/cli'
197
+ *
198
+ * const result = await buildManifest({ dir: 'mock', outDir: '.mokup' })
199
+ */
16
200
  declare function buildManifest(options?: BuildOptions): Promise<{
17
201
  manifest: Manifest;
18
202
  manifestPath: string;
19
203
  }>;
20
204
 
205
+ type DefineConfigFactory = (context: {
206
+ pre: MiddlewareRegistry;
207
+ normal: MiddlewareRegistry;
208
+ post: MiddlewareRegistry;
209
+ }) => RouteDirectoryConfig | void;
210
+ /**
211
+ * Define a directory config with Hono-style middleware registration.
212
+ *
213
+ * @param input - Config object or factory callback.
214
+ * @returns Route directory config with middleware metadata.
215
+ *
216
+ * @example
217
+ * import { defineConfig } from '@mokup/cli'
218
+ *
219
+ * export default defineConfig(({ pre, normal, post }) => {
220
+ * pre.use(async (c, next) => {
221
+ * c.header('x-before', '1')
222
+ * await next()
223
+ * })
224
+ *
225
+ * normal.use(async (_c, next) => {
226
+ * await next()
227
+ * })
228
+ *
229
+ * post.use(async (c, next) => {
230
+ * await next()
231
+ * c.header('x-after', '1')
232
+ * })
233
+ *
234
+ * return { delay: 120 }
235
+ * })
236
+ */
237
+ declare function defineConfig(input: RouteDirectoryConfig | DefineConfigFactory): RouteDirectoryConfig;
238
+
239
+ /**
240
+ * Create the mokup CLI program instance.
241
+ *
242
+ * @returns A configured CLI program.
243
+ *
244
+ * @example
245
+ * import { createCli } from '@mokup/cli'
246
+ *
247
+ * const cli = createCli()
248
+ */
21
249
  declare function createCli(): Command;
250
+ /**
251
+ * Run the mokup CLI with the provided argv.
252
+ *
253
+ * @param argv - CLI arguments.
254
+ * @returns Exit code or void.
255
+ *
256
+ * @example
257
+ * import { runCli } from '@mokup/cli'
258
+ *
259
+ * await runCli(['node', 'mokup', 'build', '--dir', 'mock'])
260
+ */
22
261
  declare function runCli(argv?: string[]): Promise<void>;
23
262
 
24
- export { buildManifest, createCli, runCli };
25
- export type { BuildOptions };
263
+ export { buildManifest, createCli, defineConfig, runCli };
264
+ export type { BuildOptions, MiddlewarePosition, MiddlewareRegistry, RouteDirectoryConfig, RouteRule };
package/dist/index.d.ts CHANGED
@@ -1,25 +1,264 @@
1
1
  import { Manifest } from '@mokup/runtime';
2
+ import { MiddlewareHandler } from '@mokup/shared/hono';
2
3
  import { Command } from 'commander';
3
4
 
5
+ /**
6
+ * Options for building a mokup manifest.
7
+ *
8
+ * @example
9
+ * import type { BuildOptions } from '@mokup/cli'
10
+ *
11
+ * const options: BuildOptions = {
12
+ * dir: 'mock',
13
+ * outDir: '.mokup',
14
+ * }
15
+ */
4
16
  interface BuildOptions {
17
+ /**
18
+ * Directory or directories to scan for mock files.
19
+ *
20
+ * @default "mock"
21
+ */
5
22
  dir?: string | string[];
23
+ /**
24
+ * Output directory for manifest artifacts.
25
+ *
26
+ * @default ".mokup"
27
+ */
6
28
  outDir?: string;
29
+ /**
30
+ * URL prefix to apply to generated routes.
31
+ *
32
+ * @default ""
33
+ */
7
34
  prefix?: string;
35
+ /**
36
+ * Include filter for files.
37
+ *
38
+ * @default undefined
39
+ */
8
40
  include?: RegExp | RegExp[];
41
+ /**
42
+ * Exclude filter for files.
43
+ *
44
+ * @default undefined
45
+ */
9
46
  exclude?: RegExp | RegExp[];
47
+ /**
48
+ * Ignore file or folder prefixes.
49
+ *
50
+ * @default ["."]
51
+ */
10
52
  ignorePrefix?: string | string[];
53
+ /**
54
+ * Emit handler bundles for module-based responses.
55
+ *
56
+ * @default true
57
+ */
11
58
  handlers?: boolean;
59
+ /**
60
+ * Project root used to resolve paths.
61
+ *
62
+ * @default process.cwd()
63
+ */
12
64
  root?: string;
65
+ /**
66
+ * Optional logger for build messages.
67
+ *
68
+ * @default undefined
69
+ */
13
70
  log?: (message: string) => void;
14
71
  }
72
+ /**
73
+ * Route rule shape used in build-time resolution.
74
+ *
75
+ * @example
76
+ * import type { RouteRule } from '@mokup/cli'
77
+ *
78
+ * const rule: RouteRule = { handler: { ok: true } }
79
+ */
80
+ interface RouteRule {
81
+ /** Route handler or static value. */
82
+ handler: unknown;
83
+ /**
84
+ * Enable or disable this rule.
85
+ *
86
+ * @default true
87
+ */
88
+ enabled?: boolean;
89
+ /**
90
+ * Override response status code.
91
+ *
92
+ * @default 200
93
+ */
94
+ status?: number;
95
+ /**
96
+ * Additional response headers.
97
+ *
98
+ * @default {}
99
+ */
100
+ headers?: Record<string, string>;
101
+ /**
102
+ * Delay in milliseconds before responding.
103
+ *
104
+ * @default 0
105
+ */
106
+ delay?: number;
107
+ }
108
+ /**
109
+ * Directory-level config used during manifest build.
110
+ *
111
+ * @example
112
+ * import type { RouteDirectoryConfig } from '@mokup/cli'
113
+ *
114
+ * const config: RouteDirectoryConfig = {
115
+ * headers: { 'x-mokup': 'dir' },
116
+ * }
117
+ */
118
+ interface RouteDirectoryConfig {
119
+ /**
120
+ * Headers applied to routes in this directory.
121
+ *
122
+ * @default {}
123
+ */
124
+ headers?: Record<string, string>;
125
+ /**
126
+ * Default status code override.
127
+ *
128
+ * @default 200
129
+ */
130
+ status?: number;
131
+ /**
132
+ * Default delay in milliseconds.
133
+ *
134
+ * @default 0
135
+ */
136
+ delay?: number;
137
+ /**
138
+ * Enable or disable this directory.
139
+ *
140
+ * @default true
141
+ */
142
+ enabled?: boolean;
143
+ /**
144
+ * Ignore prefixes applied to files in this directory.
145
+ *
146
+ * @default ["."]
147
+ */
148
+ ignorePrefix?: string | string[];
149
+ /**
150
+ * Include filter for files.
151
+ *
152
+ * @default undefined
153
+ */
154
+ include?: RegExp | RegExp[];
155
+ /**
156
+ * Exclude filter for files.
157
+ *
158
+ * @default undefined
159
+ */
160
+ exclude?: RegExp | RegExp[];
161
+ /**
162
+ * Middleware for the directory.
163
+ *
164
+ * @default undefined
165
+ */
166
+ middleware?: MiddlewareHandler | MiddlewareHandler[];
167
+ }
168
+ /**
169
+ * Middleware execution position.
170
+ *
171
+ * @example
172
+ * import type { MiddlewarePosition } from '@mokup/cli'
173
+ *
174
+ * const position: MiddlewarePosition = 'pre'
175
+ */
176
+ type MiddlewarePosition = 'pre' | 'normal' | 'post';
177
+ /**
178
+ * Middleware registry used by defineConfig.
179
+ *
180
+ * @example
181
+ * import type { MiddlewareRegistry } from '@mokup/cli'
182
+ *
183
+ * const registry: MiddlewareRegistry = { use: () => {} }
184
+ */
185
+ interface MiddlewareRegistry {
186
+ use: (...handlers: MiddlewareHandler[]) => void;
187
+ }
15
188
 
189
+ /**
190
+ * Build and write a mokup manifest to disk.
191
+ *
192
+ * @param options - Build options.
193
+ * @returns Build output metadata.
194
+ *
195
+ * @example
196
+ * import { buildManifest } from '@mokup/cli'
197
+ *
198
+ * const result = await buildManifest({ dir: 'mock', outDir: '.mokup' })
199
+ */
16
200
  declare function buildManifest(options?: BuildOptions): Promise<{
17
201
  manifest: Manifest;
18
202
  manifestPath: string;
19
203
  }>;
20
204
 
205
+ type DefineConfigFactory = (context: {
206
+ pre: MiddlewareRegistry;
207
+ normal: MiddlewareRegistry;
208
+ post: MiddlewareRegistry;
209
+ }) => RouteDirectoryConfig | void;
210
+ /**
211
+ * Define a directory config with Hono-style middleware registration.
212
+ *
213
+ * @param input - Config object or factory callback.
214
+ * @returns Route directory config with middleware metadata.
215
+ *
216
+ * @example
217
+ * import { defineConfig } from '@mokup/cli'
218
+ *
219
+ * export default defineConfig(({ pre, normal, post }) => {
220
+ * pre.use(async (c, next) => {
221
+ * c.header('x-before', '1')
222
+ * await next()
223
+ * })
224
+ *
225
+ * normal.use(async (_c, next) => {
226
+ * await next()
227
+ * })
228
+ *
229
+ * post.use(async (c, next) => {
230
+ * await next()
231
+ * c.header('x-after', '1')
232
+ * })
233
+ *
234
+ * return { delay: 120 }
235
+ * })
236
+ */
237
+ declare function defineConfig(input: RouteDirectoryConfig | DefineConfigFactory): RouteDirectoryConfig;
238
+
239
+ /**
240
+ * Create the mokup CLI program instance.
241
+ *
242
+ * @returns A configured CLI program.
243
+ *
244
+ * @example
245
+ * import { createCli } from '@mokup/cli'
246
+ *
247
+ * const cli = createCli()
248
+ */
21
249
  declare function createCli(): Command;
250
+ /**
251
+ * Run the mokup CLI with the provided argv.
252
+ *
253
+ * @param argv - CLI arguments.
254
+ * @returns Exit code or void.
255
+ *
256
+ * @example
257
+ * import { runCli } from '@mokup/cli'
258
+ *
259
+ * await runCli(['node', 'mokup', 'build', '--dir', 'mock'])
260
+ */
22
261
  declare function runCli(argv?: string[]): Promise<void>;
23
262
 
24
- export { buildManifest, createCli, runCli };
25
- export type { BuildOptions };
263
+ export { buildManifest, createCli, defineConfig, runCli };
264
+ export type { BuildOptions, MiddlewarePosition, MiddlewareRegistry, RouteDirectoryConfig, RouteRule };
package/dist/index.mjs CHANGED
@@ -8,6 +8,7 @@ import { build } from '@mokup/shared/esbuild';
8
8
  import { parseRouteTemplate, compareRouteScore } from '@mokup/runtime';
9
9
  import { parse } from '@mokup/shared/jsonc-parser';
10
10
  import { createFetchServer, serve } from '@mokup/server/node';
11
+ import { createLogger } from '@mokup/shared/logger';
11
12
  import { Command } from 'commander';
12
13
 
13
14
  async function writeBundle(outDir, hasHandlers) {
@@ -67,6 +68,7 @@ async function writeManifestModule(outDir, manifest) {
67
68
  }
68
69
 
69
70
  const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
71
+ const middlewareSymbol$1 = Symbol.for("mokup.config.middlewares");
70
72
  async function loadModule$1(file) {
71
73
  const ext = configExtensions.find((extension) => file.endsWith(extension));
72
74
  if (ext === ".cjs") {
@@ -127,7 +129,7 @@ async function loadConfig(file) {
127
129
  }
128
130
  return value;
129
131
  }
130
- function normalizeMiddlewares(value, source, log) {
132
+ function normalizeMiddlewares(value, source, log, position) {
131
133
  if (!value) {
132
134
  return [];
133
135
  }
@@ -138,10 +140,22 @@ function normalizeMiddlewares(value, source, log) {
138
140
  log?.(`Invalid middleware in ${source}`);
139
141
  return;
140
142
  }
141
- middlewares.push({ file: source, index });
143
+ middlewares.push({ file: source, index, position });
142
144
  });
143
145
  return middlewares;
144
146
  }
147
+ function readMiddlewareMeta(config) {
148
+ const value = config[middlewareSymbol$1];
149
+ if (!value || typeof value !== "object") {
150
+ return null;
151
+ }
152
+ const meta = value;
153
+ return {
154
+ pre: Array.isArray(meta.pre) ? meta.pre : [],
155
+ normal: Array.isArray(meta.normal) ? meta.normal : [],
156
+ post: Array.isArray(meta.post) ? meta.post : []
157
+ };
158
+ }
145
159
  async function resolveDirectoryConfig(params) {
146
160
  const { file, rootDir, log, configCache, fileCache } = params;
147
161
  const resolvedRoot = normalize(rootDir);
@@ -160,7 +174,10 @@ async function resolveDirectoryConfig(params) {
160
174
  current = parent;
161
175
  }
162
176
  chain.reverse();
163
- const merged = { middlewares: [] };
177
+ const merged = {};
178
+ const preMiddlewares = [];
179
+ const normalMiddlewares = [];
180
+ const postMiddlewares = [];
164
181
  for (const dir of chain) {
165
182
  const configPath = await findConfigFile(dir, fileCache);
166
183
  if (!configPath) {
@@ -196,12 +213,48 @@ async function resolveDirectoryConfig(params) {
196
213
  if (typeof config.exclude !== "undefined") {
197
214
  merged.exclude = config.exclude;
198
215
  }
199
- const normalized = normalizeMiddlewares(config.middleware, configPath, log);
200
- if (normalized.length > 0) {
201
- merged.middlewares.push(...normalized);
216
+ const meta = readMiddlewareMeta(config);
217
+ const normalizedPre = normalizeMiddlewares(
218
+ meta?.pre,
219
+ configPath,
220
+ log,
221
+ "pre"
222
+ );
223
+ const normalizedNormal = normalizeMiddlewares(
224
+ meta?.normal,
225
+ configPath,
226
+ log,
227
+ "normal"
228
+ );
229
+ const normalizedLegacy = normalizeMiddlewares(
230
+ config.middleware,
231
+ configPath,
232
+ log,
233
+ "normal"
234
+ );
235
+ const normalizedPost = normalizeMiddlewares(
236
+ meta?.post,
237
+ configPath,
238
+ log,
239
+ "post"
240
+ );
241
+ if (normalizedPre.length > 0) {
242
+ preMiddlewares.push(...normalizedPre);
243
+ }
244
+ if (normalizedNormal.length > 0) {
245
+ normalMiddlewares.push(...normalizedNormal);
246
+ }
247
+ if (normalizedLegacy.length > 0) {
248
+ normalMiddlewares.push(...normalizedLegacy);
249
+ }
250
+ if (normalizedPost.length > 0) {
251
+ postMiddlewares.push(...normalizedPost);
202
252
  }
203
253
  }
204
- return merged;
254
+ return {
255
+ ...merged,
256
+ middlewares: [...preMiddlewares, ...normalMiddlewares, ...postMiddlewares]
257
+ };
205
258
  }
206
259
 
207
260
  function toPosix(value) {
@@ -756,6 +809,40 @@ async function buildManifest(options = {}) {
756
809
  };
757
810
  }
758
811
 
812
+ const middlewareSymbol = Symbol.for("mokup.config.middlewares");
813
+ function createRegistry(list) {
814
+ return {
815
+ use: (...handlers) => {
816
+ list.push(...handlers);
817
+ }
818
+ };
819
+ }
820
+ function attachMetadata(config, meta) {
821
+ Object.defineProperty(config, middlewareSymbol, {
822
+ value: meta,
823
+ enumerable: false
824
+ });
825
+ return config;
826
+ }
827
+ function defineConfig(input) {
828
+ if (typeof input === "function") {
829
+ const pre = [];
830
+ const normal = [];
831
+ const post = [];
832
+ const context = {
833
+ pre: createRegistry(pre),
834
+ normal: createRegistry(normal),
835
+ post: createRegistry(post)
836
+ };
837
+ const result = input(context);
838
+ const config2 = result && typeof result === "object" ? result : {};
839
+ return attachMetadata(config2, { pre, normal, post });
840
+ }
841
+ const config = input && typeof input === "object" ? input : {};
842
+ return attachMetadata(config, { pre: [], normal: [], post: [] });
843
+ }
844
+
845
+ const logger = createLogger();
759
846
  function collectValues(value, previous) {
760
847
  return [...previous ?? [], value];
761
848
  }
@@ -768,7 +855,7 @@ function toBuildOptions(options) {
768
855
  const buildOptions = {
769
856
  handlers: options.handlers !== false,
770
857
  log: (message) => {
771
- console.log(message);
858
+ logger.info(message);
772
859
  }
773
860
  };
774
861
  if (options.dir && options.dir.length > 0) {
@@ -839,7 +926,7 @@ function createCli() {
839
926
  const host = entry.host ?? "localhost";
840
927
  const port = entry.port ?? 8080;
841
928
  const playgroundEnabled = playground !== false;
842
- const playgroundPath = "/_mokup";
929
+ const playgroundPath = "/__mokup";
843
930
  const server = await createFetchServer({
844
931
  entries: entry,
845
932
  playground
@@ -853,9 +940,9 @@ function createCli() {
853
940
  (info) => {
854
941
  const resolvedHost = typeof info === "string" ? host : info?.address ?? host;
855
942
  const resolvedPort = typeof info === "string" ? port : info?.port ?? port;
856
- console.log(`Mock server ready at http://${resolvedHost}:${resolvedPort}`);
943
+ logger.info(`Mock server ready at http://${resolvedHost}:${resolvedPort}`);
857
944
  if (playgroundEnabled) {
858
- console.log(`Playground at http://${resolvedHost}:${resolvedPort}${playgroundPath}`);
945
+ logger.info(`Playground at http://${resolvedHost}:${resolvedPort}${playgroundPath}`);
859
946
  }
860
947
  }
861
948
  );
@@ -895,4 +982,4 @@ async function runCli(argv = process.argv) {
895
982
  await program.parseAsync(argv);
896
983
  }
897
984
 
898
- export { buildManifest, createCli, runCli };
985
+ export { buildManifest, createCli, defineConfig, runCli };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mokup/cli",
3
3
  "type": "module",
4
- "version": "1.0.5",
4
+ "version": "1.0.6",
5
5
  "description": "CLI for building mokup manifests and handlers.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://mokup.icebreaker.top",
@@ -28,9 +28,9 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "commander": "^14.0.0",
31
- "@mokup/runtime": "1.0.2",
32
- "@mokup/shared": "1.0.1",
33
- "@mokup/server": "1.1.2"
31
+ "@mokup/runtime": "1.0.3",
32
+ "@mokup/server": "1.1.3",
33
+ "@mokup/shared": "1.0.2"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^25.0.10",