@specbook/vite 0.3.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 +69 -21
- 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,10 +18,21 @@ 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
|
-
var
|
|
24
|
-
var
|
|
33
|
+
var NAME = String.raw`[\w.$\[\]-]+`;
|
|
34
|
+
var WHOLE = new RegExp(String.raw`^\s*\{\{\s*(${NAME})\s*\}\}\s*$`);
|
|
35
|
+
var EMBED = new RegExp(String.raw`\{\{\s*(${NAME})\s*\}\}`, "g");
|
|
25
36
|
var DEFERRED_NAMESPACES = ["secrets", "session"];
|
|
26
37
|
var TemplateError = class extends Error {
|
|
27
38
|
constructor(variable) {
|
|
@@ -36,21 +47,32 @@ function isDeferred(name) {
|
|
|
36
47
|
return DEFERRED_NAMESPACES.includes(ns) && name.includes(".");
|
|
37
48
|
}
|
|
38
49
|
var isFakerName = (name) => name.startsWith("faker.");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
cur = cur?.[part];
|
|
45
|
-
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";
|
|
46
55
|
}
|
|
47
|
-
|
|
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) };
|
|
48
69
|
}
|
|
70
|
+
var steps = (path) => path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
49
71
|
function lookup(scope, path) {
|
|
50
72
|
if (isFakerName(path)) return fromFaker(path);
|
|
51
73
|
let cur = scope;
|
|
52
|
-
for (const part of path
|
|
53
|
-
if (cur !== null && typeof cur === "object" && part
|
|
74
|
+
for (const part of steps(path)) {
|
|
75
|
+
if (cur !== null && typeof cur === "object" && Object.hasOwn(cur, part)) {
|
|
54
76
|
cur = cur[part];
|
|
55
77
|
} else {
|
|
56
78
|
return { found: false };
|
|
@@ -58,7 +80,19 @@ function lookup(scope, path) {
|
|
|
58
80
|
}
|
|
59
81
|
return { found: true, value: cur };
|
|
60
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
|
+
}
|
|
61
94
|
function renderString(s, scope) {
|
|
95
|
+
rejectFakerCall(s);
|
|
62
96
|
const whole = WHOLE.exec(s);
|
|
63
97
|
if (whole) {
|
|
64
98
|
const name = whole[1];
|
|
@@ -727,7 +761,7 @@ async function writeEnvKey(file, key, value) {
|
|
|
727
761
|
import { relative as relative2, sep as sep2 } from "path";
|
|
728
762
|
import fg2 from "fast-glob";
|
|
729
763
|
var IGNORE = ["**/node_modules/**", "**/.generated/**", "**/dist/**", "**/.git/**"];
|
|
730
|
-
var
|
|
764
|
+
var NAME2 = /\.example\.json$/;
|
|
731
765
|
async function scanExamples(root, outDir, operations) {
|
|
732
766
|
const examples = {};
|
|
733
767
|
const assets = {};
|
|
@@ -745,7 +779,7 @@ async function scanExamples(root, outDir, operations) {
|
|
|
745
779
|
const [svc, , id, file] = relative2(gen, abs).split(sep2);
|
|
746
780
|
if (!operations.has(`${svc}/${id}`)) continue;
|
|
747
781
|
add(`${svc}/${id}`, {
|
|
748
|
-
name: file.replace(
|
|
782
|
+
name: file.replace(NAME2, ""),
|
|
749
783
|
abs,
|
|
750
784
|
source: "generated",
|
|
751
785
|
dir: `${svc}/${id}@generated`
|
|
@@ -759,7 +793,7 @@ async function scanExamples(root, outDir, operations) {
|
|
|
759
793
|
const key = `${svc}/${id}`;
|
|
760
794
|
if (!svc || !id || !operations.has(key)) continue;
|
|
761
795
|
add(key, {
|
|
762
|
-
name: file.replace(
|
|
796
|
+
name: file.replace(NAME2, ""),
|
|
763
797
|
abs,
|
|
764
798
|
source: "project",
|
|
765
799
|
dir: `${svc}/${id}@project`
|
|
@@ -774,7 +808,7 @@ async function scanExamples(root, outDir, operations) {
|
|
|
774
808
|
list.sort((a, b) => order(a) - order(b) || a.name.localeCompare(b.name));
|
|
775
809
|
return { examples, assets };
|
|
776
810
|
}
|
|
777
|
-
var order = (e) => e.source === "project" ? 0 : e.name === "default" ? 2 :
|
|
811
|
+
var order = (e) => e.source === "project" ? 0 : e.name === "default" ? 2 : 1;
|
|
778
812
|
|
|
779
813
|
// src/files.ts
|
|
780
814
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -1079,7 +1113,7 @@ async function readBody2(req) {
|
|
|
1079
1113
|
req.on("error", fail);
|
|
1080
1114
|
});
|
|
1081
1115
|
}
|
|
1082
|
-
function fakerLocales(entry) {
|
|
1116
|
+
function fakerLocales(entry, cfg) {
|
|
1083
1117
|
const names = /* @__PURE__ */ new Set(["en", "base"]);
|
|
1084
1118
|
const dir = join5(dirname(entry), "locale");
|
|
1085
1119
|
if (existsSync3(dir))
|
|
@@ -1087,8 +1121,11 @@ function fakerLocales(entry) {
|
|
|
1087
1121
|
const m = /^([A-Za-z0-9_]+)\.js$/.exec(f);
|
|
1088
1122
|
if (m) names.add(m[1]);
|
|
1089
1123
|
}
|
|
1090
|
-
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();
|
|
1091
1127
|
}
|
|
1128
|
+
var USES_FAKER = /@specbook\/core\/faker|\{\{\s*faker\./;
|
|
1092
1129
|
function slim(manifests) {
|
|
1093
1130
|
return Object.fromEntries(
|
|
1094
1131
|
Object.entries(manifests).map(([k, m]) => [
|
|
@@ -1187,6 +1224,14 @@ function specbook(options) {
|
|
|
1187
1224
|
fakerEntry = createRequire(join5(root, "package.json")).resolve("@faker-js/faker");
|
|
1188
1225
|
} catch {
|
|
1189
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
|
+
}
|
|
1190
1235
|
const operationEntries = Object.values(manifests).flatMap((m) => {
|
|
1191
1236
|
const mm = m;
|
|
1192
1237
|
return mm.operations.map(
|
|
@@ -1558,7 +1603,10 @@ export const loadConfig = ${cfg ? imp(cfg) : '() => Promise.resolve({ default: {
|
|
|
1558
1603
|
const r = await this.resolve("@faker-js/faker", join5(root, "package.json"), {
|
|
1559
1604
|
skipSelf: true
|
|
1560
1605
|
});
|
|
1561
|
-
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
|
+
);
|
|
1562
1610
|
const pkg = JSON.stringify(r.id);
|
|
1563
1611
|
if (id.startsWith(RESOLVED_FAKER_LOCALE))
|
|
1564
1612
|
return `export { ${id.slice(RESOLVED_FAKER_LOCALE.length)} as default } from ${pkg}`;
|
|
@@ -1567,7 +1615,7 @@ export const loadConfig = ${cfg ? imp(cfg) : '() => Promise.resolve({ default: {
|
|
|
1567
1615
|
entry = createRequire(join5(root, "package.json")).resolve("@faker-js/faker");
|
|
1568
1616
|
} catch {
|
|
1569
1617
|
}
|
|
1570
|
-
const names = fakerLocales(entry);
|
|
1618
|
+
const names = fakerLocales(entry, options.faker);
|
|
1571
1619
|
return `export { Faker, base } from ${pkg}
|
|
1572
1620
|
export const locales = {
|
|
1573
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')}
|