@mokup/cli 1.0.11 → 1.1.1

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.mjs CHANGED
@@ -1,13 +1,18 @@
1
1
  import { promises } from 'node:fs';
2
2
  import process, { cwd } from 'node:process';
3
- import { join, normalize, dirname, resolve, isAbsolute, basename, extname, relative } from '@mokup/shared/pathe';
3
+ import { join, relative, extname, dirname, resolve } from '@mokup/shared/pathe';
4
+ import { resolveDirectoryConfig as resolveDirectoryConfig$1, isPromise } from '@mokup/shared/config-utils';
5
+ import { loadModule } from '@mokup/shared/module-loader';
6
+ import { configExtensions } from '@mokup/shared/route-constants';
7
+ import { collectFiles, isSupportedFile } from '@mokup/shared/mock-files';
8
+ import { resolveDirs as resolveDirs$1, normalizeIgnorePrefix } from '@mokup/shared/scan-utils';
9
+ import { toPosix, hasIgnoredPrefix, matchesFilter } from '@mokup/shared/path-utils';
4
10
  import { Buffer } from 'node:buffer';
5
- import { createRequire } from 'node:module';
6
- import { pathToFileURL } from 'node:url';
7
11
  import { build } from '@mokup/shared/esbuild';
8
- import { toPosix, hasIgnoredPrefix, matchesFilter } from '@mokup/shared/path-utils';
9
- import { parseRouteTemplate, compareRouteScore } from '@mokup/runtime';
10
- import { parse } from '@mokup/shared/jsonc-parser';
12
+ import { compareRouteScore, parseRouteTemplate } from '@mokup/runtime';
13
+ import { createRouteUtils } from '@mokup/shared/route-utils';
14
+ import { loadRules as loadRules$1 } from '@mokup/shared/load-rules';
15
+ import { createDefineConfig } from '@mokup/shared/define-config';
11
16
  import { createFetchServer, serve } from '@mokup/server/node';
12
17
  import { createLogger } from '@mokup/shared/logger';
13
18
  import { Command } from 'commander';
@@ -68,259 +73,48 @@ async function writeManifestModule(outDir, manifest) {
68
73
  await promises.writeFile(join(outDir, "mokup.manifest.d.mts"), dts.join("\n"), "utf8");
69
74
  }
70
75
 
71
- const configExtensions = [".ts", ".js", ".mjs", ".cjs"];
72
- const middlewareSymbol$1 = Symbol.for("mokup.config.middlewares");
73
- async function loadModule$1(file) {
74
- const ext = configExtensions.find((extension) => file.endsWith(extension));
75
- if (ext === ".cjs") {
76
- const require = createRequire(import.meta.url);
77
- delete require.cache[file];
78
- return require(file);
79
- }
80
- if (ext === ".js" || ext === ".mjs") {
81
- return import(`${pathToFileURL(file).href}?t=${Date.now()}`);
82
- }
83
- if (ext === ".ts") {
84
- const result = await build({
85
- entryPoints: [file],
86
- bundle: true,
87
- format: "esm",
88
- platform: "node",
89
- sourcemap: "inline",
90
- target: "es2020",
91
- write: false
92
- });
93
- const output = result.outputFiles[0];
94
- const code = output?.text ?? "";
95
- const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString(
96
- "base64"
97
- )}`;
98
- return import(`${dataUrl}#${Date.now()}`);
99
- }
100
- return null;
101
- }
102
- function getConfigFileCandidates(dir) {
103
- return configExtensions.map((extension) => join(dir, `index.config${extension}`));
104
- }
105
- async function findConfigFile(dir, cache) {
106
- const cached = cache.get(dir);
107
- if (cached !== void 0) {
108
- return cached;
109
- }
110
- for (const candidate of getConfigFileCandidates(dir)) {
111
- try {
112
- await promises.stat(candidate);
113
- cache.set(dir, candidate);
114
- return candidate;
115
- } catch {
116
- continue;
117
- }
118
- }
119
- cache.set(dir, null);
120
- return null;
121
- }
122
76
  async function loadConfig(file) {
123
- const mod = await loadModule$1(file);
77
+ const mod = await loadModule(file);
124
78
  if (!mod) {
125
79
  return null;
126
80
  }
127
- const value = mod?.default ?? mod;
81
+ const raw = mod?.default ?? mod;
82
+ const value = isPromise(raw) ? await raw : raw;
128
83
  if (!value || typeof value !== "object") {
129
84
  return null;
130
85
  }
131
86
  return value;
132
87
  }
133
- function normalizeMiddlewares(value, source, log, position) {
134
- if (!value) {
135
- return [];
136
- }
137
- const list = Array.isArray(value) ? value : [value];
138
- const middlewares = [];
139
- list.forEach((entry, index) => {
140
- if (typeof entry !== "function") {
141
- log?.(`Invalid middleware in ${source}`);
142
- return;
143
- }
144
- middlewares.push({ file: source, index, position });
145
- });
146
- return middlewares;
147
- }
148
- function readMiddlewareMeta(config) {
149
- const value = config[middlewareSymbol$1];
150
- if (!value || typeof value !== "object") {
151
- return null;
152
- }
153
- const meta = value;
154
- return {
155
- pre: Array.isArray(meta.pre) ? meta.pre : [],
156
- normal: Array.isArray(meta.normal) ? meta.normal : [],
157
- post: Array.isArray(meta.post) ? meta.post : []
158
- };
159
- }
160
88
  async function resolveDirectoryConfig(params) {
161
89
  const { file, rootDir, log, configCache, fileCache } = params;
162
- const resolvedRoot = normalize(rootDir);
163
- const resolvedFileDir = normalize(dirname(file));
164
- const chain = [];
165
- let current = resolvedFileDir;
166
- while (true) {
167
- chain.push(current);
168
- if (current === resolvedRoot) {
169
- break;
170
- }
171
- const parent = dirname(current);
172
- if (parent === current) {
173
- break;
174
- }
175
- current = parent;
176
- }
177
- chain.reverse();
178
- const merged = {};
179
- const preMiddlewares = [];
180
- const normalMiddlewares = [];
181
- const postMiddlewares = [];
182
- for (const dir of chain) {
183
- const configPath = await findConfigFile(dir, fileCache);
184
- if (!configPath) {
185
- continue;
186
- }
187
- let config = configCache.get(configPath);
188
- if (config === void 0) {
189
- config = await loadConfig(configPath);
190
- configCache.set(configPath, config);
191
- }
192
- if (!config) {
193
- log?.(`Invalid config in ${configPath}`);
194
- continue;
195
- }
196
- if (config.headers) {
197
- merged.headers = { ...merged.headers ?? {}, ...config.headers };
198
- }
199
- if (typeof config.status === "number") {
200
- merged.status = config.status;
201
- }
202
- if (typeof config.delay === "number") {
203
- merged.delay = config.delay;
204
- }
205
- if (typeof config.enabled === "boolean") {
206
- merged.enabled = config.enabled;
207
- }
208
- if (typeof config.ignorePrefix !== "undefined") {
209
- merged.ignorePrefix = config.ignorePrefix;
210
- }
211
- if (typeof config.include !== "undefined") {
212
- merged.include = config.include;
213
- }
214
- if (typeof config.exclude !== "undefined") {
215
- merged.exclude = config.exclude;
216
- }
217
- const meta = readMiddlewareMeta(config);
218
- const normalizedPre = normalizeMiddlewares(
219
- meta?.pre,
220
- configPath,
221
- log,
222
- "pre"
223
- );
224
- const normalizedNormal = normalizeMiddlewares(
225
- meta?.normal,
226
- configPath,
227
- log,
228
- "normal"
229
- );
230
- const normalizedLegacy = normalizeMiddlewares(
231
- config.middleware,
232
- configPath,
233
- log,
234
- "normal"
235
- );
236
- const normalizedPost = normalizeMiddlewares(
237
- meta?.post,
238
- configPath,
239
- log,
240
- "post"
241
- );
242
- if (normalizedPre.length > 0) {
243
- preMiddlewares.push(...normalizedPre);
244
- }
245
- if (normalizedNormal.length > 0) {
246
- normalMiddlewares.push(...normalizedNormal);
247
- }
248
- if (normalizedLegacy.length > 0) {
249
- normalMiddlewares.push(...normalizedLegacy);
250
- }
251
- if (normalizedPost.length > 0) {
252
- postMiddlewares.push(...normalizedPost);
253
- }
254
- }
90
+ const resolved = await resolveDirectoryConfig$1({
91
+ file,
92
+ rootDir,
93
+ configExtensions,
94
+ configCache,
95
+ fileCache,
96
+ loadConfig,
97
+ warn: log,
98
+ mapMiddleware: (_handler, index, position, source) => ({
99
+ file: source,
100
+ index,
101
+ position
102
+ })
103
+ });
255
104
  return {
256
- ...merged,
257
- middlewares: [...preMiddlewares, ...normalMiddlewares, ...postMiddlewares]
105
+ headers: resolved.headers,
106
+ status: resolved.status,
107
+ delay: resolved.delay,
108
+ enabled: resolved.enabled,
109
+ ignorePrefix: resolved.ignorePrefix,
110
+ include: resolved.include,
111
+ exclude: resolved.exclude,
112
+ middlewares: resolved.middlewares
258
113
  };
259
114
  }
260
115
 
261
- const supportedExtensions = /* @__PURE__ */ new Set([
262
- ".json",
263
- ".jsonc",
264
- ".ts",
265
- ".js",
266
- ".mjs",
267
- ".cjs"
268
- ]);
269
- async function exists(path) {
270
- try {
271
- await promises.stat(path);
272
- return true;
273
- } catch {
274
- return false;
275
- }
276
- }
277
- async function walkDir(dir, rootDir, files) {
278
- const entries = await promises.readdir(dir, { withFileTypes: true });
279
- for (const entry of entries) {
280
- if (entry.name === "node_modules" || entry.name === ".git") {
281
- continue;
282
- }
283
- const fullPath = join(dir, entry.name);
284
- if (entry.isDirectory()) {
285
- await walkDir(fullPath, rootDir, files);
286
- continue;
287
- }
288
- if (entry.isFile()) {
289
- files.push({ file: fullPath, rootDir });
290
- }
291
- }
292
- }
293
- async function collectFiles(dirs) {
294
- const files = [];
295
- for (const dir of dirs) {
296
- if (!await exists(dir)) {
297
- continue;
298
- }
299
- await walkDir(dir, dir, files);
300
- }
301
- return files;
302
- }
303
116
  function resolveDirs(dir, root) {
304
- const raw = dir;
305
- const resolved = Array.isArray(raw) ? raw : raw ? [raw] : ["mock"];
306
- const normalized = resolved.map(
307
- (entry) => isAbsolute(entry) ? entry : resolve(root, entry)
308
- );
309
- return Array.from(new Set(normalized));
310
- }
311
- function normalizeIgnorePrefix(value, fallback = ["."]) {
312
- const list = typeof value === "undefined" ? fallback : Array.isArray(value) ? value : [value];
313
- return list.filter((entry) => typeof entry === "string" && entry.length > 0);
314
- }
315
- function isSupportedFile(file) {
316
- if (file.endsWith(".d.ts")) {
317
- return false;
318
- }
319
- if (basename(file).startsWith("index.config.")) {
320
- return false;
321
- }
322
- const ext = extname(file).toLowerCase();
323
- return supportedExtensions.has(ext);
117
+ return resolveDirs$1(dir, root);
324
118
  }
325
119
 
326
120
  function getHandlerModulePath(file, handlersDir, root) {
@@ -422,119 +216,28 @@ async function bundleHandlers(files, root, handlersDir) {
422
216
  });
423
217
  }
424
218
 
425
- const methodSet = /* @__PURE__ */ new Set([
426
- "GET",
427
- "POST",
428
- "PUT",
429
- "PATCH",
430
- "DELETE",
431
- "OPTIONS",
432
- "HEAD"
433
- ]);
434
- const methodSuffixSet = new Set(
435
- Array.from(methodSet, (method) => method.toLowerCase())
436
- );
437
- const jsonExtensions = /* @__PURE__ */ new Set([".json", ".jsonc"]);
438
- function normalizePrefix(prefix) {
439
- if (!prefix) {
440
- return "";
441
- }
442
- const normalized = prefix.startsWith("/") ? prefix : `/${prefix}`;
443
- return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
444
- }
445
- function resolveTemplate(template, prefix) {
446
- const normalized = template.startsWith("/") ? template : `/${template}`;
447
- if (!prefix) {
448
- return normalized;
449
- }
450
- const normalizedPrefix = normalizePrefix(prefix);
451
- if (!normalizedPrefix) {
452
- return normalized;
453
- }
454
- if (normalized === normalizedPrefix || normalized.startsWith(`${normalizedPrefix}/`)) {
455
- return normalized;
456
- }
457
- if (normalized === "/") {
458
- return `${normalizedPrefix}/`;
459
- }
460
- return `${normalizedPrefix}${normalized}`;
461
- }
462
- function stripMethodSuffix(base) {
463
- const segments = base.split(".");
464
- const last = segments.at(-1);
465
- if (last && methodSuffixSet.has(last.toLowerCase())) {
466
- segments.pop();
467
- return {
468
- name: segments.join("."),
469
- method: last.toUpperCase()
470
- };
471
- }
472
- return {
473
- name: base,
474
- method: void 0
475
- };
476
- }
219
+ const routeUtils = createRouteUtils({
220
+ parseRouteTemplate,
221
+ compareRouteScore
222
+ });
477
223
  function deriveRouteFromFile(file, rootDir, log) {
478
- const rel = toPosix(relative(rootDir, file));
479
- const ext = extname(rel);
480
- const withoutExt = rel.slice(0, rel.length - ext.length);
481
- const dir = dirname(withoutExt);
482
- const base = basename(withoutExt);
483
- const { name, method } = stripMethodSuffix(base);
484
- const resolvedMethod = method ?? (jsonExtensions.has(ext) ? "GET" : void 0);
485
- if (!resolvedMethod) {
486
- log?.(`Skip mock without method suffix: ${file}`);
487
- return null;
488
- }
489
- if (!name) {
490
- log?.(`Skip mock with empty route name: ${file}`);
491
- return null;
492
- }
493
- const joined = dir === "." ? name : join(dir, name);
494
- const segments = toPosix(joined).split("/");
495
- if (segments.at(-1) === "index") {
496
- segments.pop();
497
- }
498
- const template = segments.length === 0 ? "/" : `/${segments.join("/")}`;
499
- const parsed = parseRouteTemplate(template);
500
- if (parsed.errors.length > 0) {
501
- for (const error of parsed.errors) {
502
- log?.(`${error} in ${file}`);
503
- }
504
- return null;
505
- }
506
- for (const warning of parsed.warnings) {
507
- log?.(`${warning} in ${file}`);
508
- }
509
- return {
510
- template: parsed.template,
511
- method: resolvedMethod,
512
- tokens: parsed.tokens,
513
- score: parsed.score
514
- };
224
+ return routeUtils.deriveRouteFromFile(file, rootDir, log);
515
225
  }
516
226
  function resolveRule(params) {
517
- const method = params.derivedMethod;
518
- if (!method) {
519
- return null;
520
- }
521
- const template = resolveTemplate(params.derivedTemplate, params.prefix);
522
- const parsed = parseRouteTemplate(template);
523
- if (parsed.errors.length > 0) {
524
- for (const error of parsed.errors) {
525
- params.log?.(`${error} in ${params.file}`);
526
- }
527
- return null;
528
- }
529
- for (const warning of parsed.warnings) {
530
- params.log?.(`${warning} in ${params.file}`);
531
- }
532
- return {
533
- method,
534
- template: parsed.template,
535
- tokens: parsed.tokens,
536
- score: parsed.score
537
- };
227
+ return routeUtils.resolveRule({
228
+ rule: params.rule,
229
+ derivedTemplate: params.derivedTemplate,
230
+ derivedMethod: params.derivedMethod,
231
+ prefix: params.prefix,
232
+ file: params.file,
233
+ warn: params.log,
234
+ build: (base) => ({
235
+ method: base.method,
236
+ template: base.template,
237
+ tokens: base.tokens,
238
+ score: base.score
239
+ })
240
+ });
538
241
  }
539
242
  function sortRoutes(routes) {
540
243
  return routes.sort((a, b) => {
@@ -545,80 +248,8 @@ function sortRoutes(routes) {
545
248
  });
546
249
  }
547
250
 
548
- async function readJsonFile(file) {
549
- try {
550
- const content = await promises.readFile(file, "utf8");
551
- const errors = [];
552
- const data = parse(content, errors, {
553
- allowTrailingComma: true,
554
- disallowComments: false
555
- });
556
- if (errors.length > 0) {
557
- return void 0;
558
- }
559
- return data;
560
- } catch {
561
- return void 0;
562
- }
563
- }
564
- async function loadModule(file) {
565
- const ext = extname(file).toLowerCase();
566
- if (ext === ".cjs") {
567
- const require = createRequire(import.meta.url);
568
- delete require.cache[file];
569
- return require(file);
570
- }
571
- if (ext === ".js" || ext === ".mjs") {
572
- return import(`${pathToFileURL(file).href}?t=${Date.now()}`);
573
- }
574
- if (ext === ".ts") {
575
- const result = await build({
576
- entryPoints: [file],
577
- bundle: true,
578
- format: "esm",
579
- platform: "node",
580
- sourcemap: "inline",
581
- target: "es2020",
582
- write: false
583
- });
584
- const output = result.outputFiles[0];
585
- const code = output?.text ?? "";
586
- const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString(
587
- "base64"
588
- )}`;
589
- return import(`${dataUrl}#${Date.now()}`);
590
- }
591
- return null;
592
- }
593
251
  async function loadRules(file) {
594
- const ext = extname(file).toLowerCase();
595
- if (ext === ".json" || ext === ".jsonc") {
596
- const json = await readJsonFile(file);
597
- if (typeof json === "undefined") {
598
- return [];
599
- }
600
- return [
601
- {
602
- handler: json
603
- }
604
- ];
605
- }
606
- const mod = await loadModule(file);
607
- const value = mod?.default ?? mod;
608
- if (!value) {
609
- return [];
610
- }
611
- if (Array.isArray(value)) {
612
- return value;
613
- }
614
- if (typeof value === "function") {
615
- return [
616
- {
617
- handler: value
618
- }
619
- ];
620
- }
621
- return [value];
252
+ return loadRules$1(file, { loadModule });
622
253
  }
623
254
 
624
255
  async function buildManifest(options = {}) {
@@ -782,38 +413,12 @@ async function buildManifest(options = {}) {
782
413
  };
783
414
  }
784
415
 
785
- const middlewareSymbol = Symbol.for("mokup.config.middlewares");
786
- function createRegistry(list) {
787
- return {
788
- use: (...handlers) => {
789
- list.push(...handlers);
790
- }
791
- };
792
- }
793
- function attachMetadata(config, meta) {
794
- Object.defineProperty(config, middlewareSymbol, {
795
- value: meta,
796
- enumerable: false
797
- });
798
- return config;
799
- }
800
- function defineConfig(input) {
801
- if (typeof input === "function") {
802
- const pre = [];
803
- const normal = [];
804
- const post = [];
805
- const context = {
806
- pre: createRegistry(pre),
807
- normal: createRegistry(normal),
808
- post: createRegistry(post)
809
- };
810
- const result = input(context);
811
- const config2 = result && typeof result === "object" ? result : {};
812
- return attachMetadata(config2, { pre, normal, post });
813
- }
814
- const config = input && typeof input === "object" ? input : {};
815
- return attachMetadata(config, { pre: [], normal: [], post: [] });
816
- }
416
+ const shared = createDefineConfig({
417
+ logPrefix: "[@mokup/cli]"
418
+ });
419
+ const defineConfig = shared.defineConfig;
420
+ const onBeforeAll = shared.onBeforeAll;
421
+ const onAfterAll = shared.onAfterAll;
817
422
 
818
423
  const logger = createLogger();
819
424
  function collectValues(value, previous) {
@@ -955,4 +560,4 @@ async function runCli(argv = process.argv) {
955
560
  await program.parseAsync(argv);
956
561
  }
957
562
 
958
- export { buildManifest, createCli, defineConfig, runCli };
563
+ export { buildManifest, createCli, defineConfig, onAfterAll, onBeforeAll, runCli };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mokup/cli",
3
3
  "type": "module",
4
- "version": "1.0.11",
4
+ "version": "1.1.1",
5
5
  "description": "CLI for building mokup manifests and handlers.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://mokup.icebreaker.top",
@@ -28,19 +28,15 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "commander": "^14.0.0",
31
- "@mokup/runtime": "1.0.6",
32
- "@mokup/server": "1.1.8",
33
- "@mokup/shared": "1.1.1"
34
- },
35
- "devDependencies": {
36
- "@types/node": "^25.0.10",
37
- "typescript": "^5.9.3",
38
- "unbuild": "^3.6.1"
31
+ "@mokup/runtime": "1.0.7",
32
+ "@mokup/server": "1.2.1",
33
+ "@mokup/shared": "1.1.2"
39
34
  },
40
35
  "scripts": {
41
36
  "build": "unbuild",
42
37
  "dev": "unbuild --stub",
43
38
  "lint": "eslint .",
44
- "typecheck": "tsc -p tsconfig.json --noEmit"
39
+ "typecheck": "tsc -p tsconfig.json --noEmit",
40
+ "test:types": "tsd"
45
41
  }
46
42
  }