@specbook/vite 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +60 -14
- package/package.json +3 -3
- package/src/examples.ts +2 -3
- package/src/index.ts +27 -6
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdir as mkdir2, readFile as readFile6, writeFile as writeFile2 } from
|
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
import { basename, dirname, join as join5, relative as relative4, resolve as resolve2, sep as sep4 } from "path";
|
|
6
6
|
|
|
7
|
-
// ../core/src/faker.ts
|
|
7
|
+
// ../core/src/faker-runtime.ts
|
|
8
8
|
var KEY = "__specbookFaker";
|
|
9
9
|
var store = globalThis;
|
|
10
10
|
var faker = new Proxy({}, {
|
|
@@ -18,6 +18,16 @@ var faker = new Proxy({}, {
|
|
|
18
18
|
return current[key];
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
|
+
function fakerLocaleNames(config = {}, locale) {
|
|
22
|
+
const name = locale ?? config.default ?? "en";
|
|
23
|
+
return [.../* @__PURE__ */ new Set([...config.locales?.[name] ?? [name, "en"], "base"])];
|
|
24
|
+
}
|
|
25
|
+
function fakerLocaleExports(config = {}) {
|
|
26
|
+
const names = /* @__PURE__ */ new Set(["en", ...fakerLocaleNames(config)]);
|
|
27
|
+
for (const name of Object.keys(config.locales ?? {}))
|
|
28
|
+
for (const n of fakerLocaleNames(config, name)) names.add(n);
|
|
29
|
+
return [...names].sort();
|
|
30
|
+
}
|
|
21
31
|
|
|
22
32
|
// ../core/src/template.ts
|
|
23
33
|
var NAME = String.raw`[\w.$\[\]-]+`;
|
|
@@ -37,15 +47,25 @@ function isDeferred(name) {
|
|
|
37
47
|
return DEFERRED_NAMESPACES.includes(ns) && name.includes(".");
|
|
38
48
|
}
|
|
39
49
|
var isFakerName = (name) => name.startsWith("faker.");
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
cur = cur?.[part];
|
|
46
|
-
if (cur === void 0) return { found: false };
|
|
50
|
+
var FakerPlaceholderError = class extends TemplateError {
|
|
51
|
+
constructor(variable, message) {
|
|
52
|
+
super(variable);
|
|
53
|
+
this.message = message;
|
|
54
|
+
this.name = "FakerPlaceholderError";
|
|
47
55
|
}
|
|
48
|
-
|
|
56
|
+
message;
|
|
57
|
+
};
|
|
58
|
+
function fromFaker(path) {
|
|
59
|
+
const [, group, method, ...rest] = path.split(".");
|
|
60
|
+
const unknown = () => new FakerPlaceholderError(
|
|
61
|
+
path,
|
|
62
|
+
`Unknown faker generator "{{${path}}}": use faker.<module>.<method>`
|
|
63
|
+
);
|
|
64
|
+
if (!group || !method || rest.length > 0) throw unknown();
|
|
65
|
+
const mod = faker[group];
|
|
66
|
+
const fn = typeof mod === "object" && mod !== null ? mod[method] : void 0;
|
|
67
|
+
if (typeof fn !== "function" || method === "constructor") throw unknown();
|
|
68
|
+
return { found: true, value: fn.call(mod) };
|
|
49
69
|
}
|
|
50
70
|
var steps = (path) => path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
51
71
|
function lookup(scope, path) {
|
|
@@ -60,7 +80,19 @@ function lookup(scope, path) {
|
|
|
60
80
|
}
|
|
61
81
|
return { found: true, value: cur };
|
|
62
82
|
}
|
|
83
|
+
function rejectFakerCall(s) {
|
|
84
|
+
if (!s.includes("faker")) return;
|
|
85
|
+
const left = /\{\{\s*(faker\.[\s\S]*?)(?:\}\}|$)/.exec(s.replace(EMBED, ""));
|
|
86
|
+
if (left) {
|
|
87
|
+
const name = left[1].trim();
|
|
88
|
+
throw new FakerPlaceholderError(
|
|
89
|
+
name,
|
|
90
|
+
`"{{${name}}}" is not a faker placeholder: placeholders take no arguments. Use faker in a request file for those.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
63
94
|
function renderString(s, scope) {
|
|
95
|
+
rejectFakerCall(s);
|
|
64
96
|
const whole = WHOLE.exec(s);
|
|
65
97
|
if (whole) {
|
|
66
98
|
const name = whole[1];
|
|
@@ -776,7 +808,7 @@ async function scanExamples(root, outDir, operations) {
|
|
|
776
808
|
list.sort((a, b) => order(a) - order(b) || a.name.localeCompare(b.name));
|
|
777
809
|
return { examples, assets };
|
|
778
810
|
}
|
|
779
|
-
var order = (e) => e.source === "project" ? 0 : e.name === "default" ? 2 :
|
|
811
|
+
var order = (e) => e.source === "project" ? 0 : e.name === "default" ? 2 : 1;
|
|
780
812
|
|
|
781
813
|
// src/files.ts
|
|
782
814
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -1081,7 +1113,7 @@ async function readBody2(req) {
|
|
|
1081
1113
|
req.on("error", fail);
|
|
1082
1114
|
});
|
|
1083
1115
|
}
|
|
1084
|
-
function fakerLocales(entry) {
|
|
1116
|
+
function fakerLocales(entry, cfg) {
|
|
1085
1117
|
const names = /* @__PURE__ */ new Set(["en", "base"]);
|
|
1086
1118
|
const dir = join5(dirname(entry), "locale");
|
|
1087
1119
|
if (existsSync3(dir))
|
|
@@ -1089,8 +1121,11 @@ function fakerLocales(entry) {
|
|
|
1089
1121
|
const m = /^([A-Za-z0-9_]+)\.js$/.exec(f);
|
|
1090
1122
|
if (m) names.add(m[1]);
|
|
1091
1123
|
}
|
|
1092
|
-
return [...names].sort();
|
|
1124
|
+
if (!cfg) return [...names].sort();
|
|
1125
|
+
const wanted = new Set(fakerLocaleExports(cfg));
|
|
1126
|
+
return [...names].filter((n) => wanted.has(n)).sort();
|
|
1093
1127
|
}
|
|
1128
|
+
var USES_FAKER = /@specbook\/core\/faker|\{\{\s*faker\./;
|
|
1094
1129
|
function slim(manifests) {
|
|
1095
1130
|
return Object.fromEntries(
|
|
1096
1131
|
Object.entries(manifests).map(([k, m]) => [
|
|
@@ -1189,6 +1224,14 @@ function specbook(options) {
|
|
|
1189
1224
|
fakerEntry = createRequire(join5(root, "package.json")).resolve("@faker-js/faker");
|
|
1190
1225
|
} catch {
|
|
1191
1226
|
}
|
|
1227
|
+
if (!fakerEntry) {
|
|
1228
|
+
for (const f of [...files.requests, ...files.sequences]) {
|
|
1229
|
+
if (USES_FAKER.test(await readFile6(join5(root, f), "utf8").catch(() => "")))
|
|
1230
|
+
throw new Error(
|
|
1231
|
+
`${f} uses faker, but @faker-js/faker is not installed in this project. Add it to devDependencies (npm install --save-dev @faker-js/faker).`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1192
1235
|
const operationEntries = Object.values(manifests).flatMap((m) => {
|
|
1193
1236
|
const mm = m;
|
|
1194
1237
|
return mm.operations.map(
|
|
@@ -1560,7 +1603,10 @@ export const loadConfig = ${cfg ? imp(cfg) : '() => Promise.resolve({ default: {
|
|
|
1560
1603
|
const r = await this.resolve("@faker-js/faker", join5(root, "package.json"), {
|
|
1561
1604
|
skipSelf: true
|
|
1562
1605
|
});
|
|
1563
|
-
if (!r)
|
|
1606
|
+
if (!r)
|
|
1607
|
+
throw new Error(
|
|
1608
|
+
"@faker-js/faker could not be resolved from this project. Add it to devDependencies."
|
|
1609
|
+
);
|
|
1564
1610
|
const pkg = JSON.stringify(r.id);
|
|
1565
1611
|
if (id.startsWith(RESOLVED_FAKER_LOCALE))
|
|
1566
1612
|
return `export { ${id.slice(RESOLVED_FAKER_LOCALE.length)} as default } from ${pkg}`;
|
|
@@ -1569,7 +1615,7 @@ export const loadConfig = ${cfg ? imp(cfg) : '() => Promise.resolve({ default: {
|
|
|
1569
1615
|
entry = createRequire(join5(root, "package.json")).resolve("@faker-js/faker");
|
|
1570
1616
|
} catch {
|
|
1571
1617
|
}
|
|
1572
|
-
const names = fakerLocales(entry);
|
|
1618
|
+
const names = fakerLocales(entry, options.faker);
|
|
1573
1619
|
return `export { Faker, base } from ${pkg}
|
|
1574
1620
|
export const locales = {
|
|
1575
1621
|
${names.map((n) => ` ${JSON.stringify(n)}: () => import(${JSON.stringify(`${VIRTUAL_FAKER_LOCALE}${n}`)}).then((m) => m.default)`).join(",\n")}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specbook/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Specbook plugin for Vite: the registry, the docs UI hooks and the dev server credential proxy.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"specbook",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@specbook/codegen": "0.
|
|
38
|
-
"@specbook/core": "0.
|
|
37
|
+
"@specbook/codegen": "0.5.0",
|
|
38
|
+
"@specbook/core": "0.5.0",
|
|
39
39
|
"fast-glob": "3.3.2",
|
|
40
40
|
"marked": "^18.1.1"
|
|
41
41
|
},
|
package/src/examples.ts
CHANGED
|
@@ -74,6 +74,5 @@ export async function scanExamples(
|
|
|
74
74
|
return { examples, assets }
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
const order = (e: ExampleEntry) =>
|
|
79
|
-
e.source === 'project' ? 0 : e.name === 'default' ? 2 : e.name === 'dynamic' ? 3 : 1
|
|
77
|
+
/** Project examples first, then the spec's, then the generated default. */
|
|
78
|
+
const order = (e: ExampleEntry) => (e.source === 'project' ? 0 : e.name === 'default' ? 2 : 1)
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
|
3
3
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
5
5
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path'
|
|
6
|
-
import { environmentNames } from '@specbook/core'
|
|
6
|
+
import { environmentNames, type FakerConfig, fakerLocaleExports } from '@specbook/core'
|
|
7
7
|
import fg from 'fast-glob'
|
|
8
8
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
9
9
|
import { type AddressConfig, addressesFor, NO_ADDRESSES } from './addresses'
|
|
@@ -44,6 +44,11 @@ export interface SpecbookPluginOptions {
|
|
|
44
44
|
* reveals the rest of the API.
|
|
45
45
|
*/
|
|
46
46
|
operations?: Set<string>
|
|
47
|
+
/**
|
|
48
|
+
* The project's `faker` config. A build ships only the locales it can reach (its chains, plus `en` and `base`);
|
|
49
|
+
* without it every locale faker has is available.
|
|
50
|
+
*/
|
|
51
|
+
faker?: FakerConfig
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
const VIRTUAL = 'virtual:specbook/registry'
|
|
@@ -85,7 +90,7 @@ async function readBody(req: IncomingMessage): Promise<string> {
|
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
/** Locale names a faker install ships (`de`, `en_US`, ...), from its `locale/` folder next to the entry. */
|
|
88
|
-
function fakerLocales(entry: string): string[] {
|
|
93
|
+
function fakerLocales(entry: string, cfg?: FakerConfig): string[] {
|
|
89
94
|
const names = new Set<string>(['en', 'base'])
|
|
90
95
|
const dir = join(dirname(entry), 'locale')
|
|
91
96
|
if (existsSync(dir))
|
|
@@ -93,9 +98,14 @@ function fakerLocales(entry: string): string[] {
|
|
|
93
98
|
const m = /^([A-Za-z0-9_]+)\.js$/.exec(f)
|
|
94
99
|
if (m) names.add(m[1] as string)
|
|
95
100
|
}
|
|
96
|
-
return [...names].sort()
|
|
101
|
+
if (!cfg) return [...names].sort()
|
|
102
|
+
const wanted = new Set(fakerLocaleExports(cfg))
|
|
103
|
+
return [...names].filter((n) => wanted.has(n)).sort()
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
/** Whether a request or flow file uses faker: it imports `@specbook/core/faker` or has a `{{faker.` placeholder. */
|
|
107
|
+
const USES_FAKER = /@specbook\/core\/faker|\{\{\s*faker\./
|
|
108
|
+
|
|
99
109
|
/** What the sidebar and palette need; descriptions and example titles load per operation (`operationDocs`). */
|
|
100
110
|
function slim(manifests: Record<string, any>) {
|
|
101
111
|
return Object.fromEntries(
|
|
@@ -223,7 +233,15 @@ export function specbook(options: SpecbookPluginOptions): Plugin {
|
|
|
223
233
|
try {
|
|
224
234
|
fakerEntry = createRequire(join(root, 'package.json')).resolve('@faker-js/faker')
|
|
225
235
|
} catch {
|
|
226
|
-
/*
|
|
236
|
+
/* not installed: fine unless a file uses it, checked below */
|
|
237
|
+
}
|
|
238
|
+
if (!fakerEntry) {
|
|
239
|
+
for (const f of [...files.requests, ...files.sequences]) {
|
|
240
|
+
if (USES_FAKER.test(await readFile(join(root, f), 'utf8').catch(() => '')))
|
|
241
|
+
throw new Error(
|
|
242
|
+
`${f} uses faker, but @faker-js/faker is not installed in this project. Add it to devDependencies (npm install --save-dev @faker-js/faker).`,
|
|
243
|
+
)
|
|
244
|
+
}
|
|
227
245
|
}
|
|
228
246
|
const operationEntries = Object.values(manifests).flatMap((m) => {
|
|
229
247
|
const mm = m as { service: string; operations: { id: string }[] }
|
|
@@ -604,7 +622,10 @@ export const loadConfig = ${cfg ? imp(cfg) : '() => Promise.resolve({ default: {
|
|
|
604
622
|
const r = await this.resolve('@faker-js/faker', join(root, 'package.json'), {
|
|
605
623
|
skipSelf: true,
|
|
606
624
|
})
|
|
607
|
-
if (!r)
|
|
625
|
+
if (!r)
|
|
626
|
+
throw new Error(
|
|
627
|
+
'@faker-js/faker could not be resolved from this project. Add it to devDependencies.',
|
|
628
|
+
)
|
|
608
629
|
const pkg = JSON.stringify(r.id)
|
|
609
630
|
if (id.startsWith(RESOLVED_FAKER_LOCALE))
|
|
610
631
|
return `export { ${id.slice(RESOLVED_FAKER_LOCALE.length)} as default } from ${pkg}`
|
|
@@ -617,7 +638,7 @@ export const loadConfig = ${cfg ? imp(cfg) : '() => Promise.resolve({ default: {
|
|
|
617
638
|
} catch {
|
|
618
639
|
/* fall back to the resolved id */
|
|
619
640
|
}
|
|
620
|
-
const names = fakerLocales(entry)
|
|
641
|
+
const names = fakerLocales(entry, options.faker)
|
|
621
642
|
return `export { Faker, base } from ${pkg}
|
|
622
643
|
export const locales = {
|
|
623
644
|
${names.map((n) => ` ${JSON.stringify(n)}: () => import(${JSON.stringify(`${VIRTUAL_FAKER_LOCALE}${n}`)}).then((m) => m.default)`).join(',\n')}
|