jamdesk 1.1.166 → 1.1.167

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.
Files changed (39) hide show
  1. package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts +2 -0
  2. package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts.map +1 -0
  3. package/dist/__tests__/unit/dev-workspace-symlinks.test.js +112 -0
  4. package/dist/__tests__/unit/dev-workspace-symlinks.test.js.map +1 -0
  5. package/dist/__tests__/unit/language-filter.test.d.ts +2 -0
  6. package/dist/__tests__/unit/language-filter.test.d.ts.map +1 -0
  7. package/dist/__tests__/unit/language-filter.test.js +166 -0
  8. package/dist/__tests__/unit/language-filter.test.js.map +1 -0
  9. package/dist/__tests__/unit/openapi-license-identifier.test.js +14 -4
  10. package/dist/__tests__/unit/openapi-license-identifier.test.js.map +1 -1
  11. package/dist/__tests__/unit/openapi-schema-patch-sync.test.js +24 -7
  12. package/dist/__tests__/unit/openapi-schema-patch-sync.test.js.map +1 -1
  13. package/dist/__tests__/unit/openapi-schema-runtime-patch.test.d.ts +2 -0
  14. package/dist/__tests__/unit/openapi-schema-runtime-patch.test.d.ts.map +1 -0
  15. package/dist/__tests__/unit/openapi-schema-runtime-patch.test.js +120 -0
  16. package/dist/__tests__/unit/openapi-schema-runtime-patch.test.js.map +1 -0
  17. package/dist/__tests__/unit/openapi-server-variable-description.test.js +14 -5
  18. package/dist/__tests__/unit/openapi-server-variable-description.test.js.map +1 -1
  19. package/dist/lib/deps.d.ts +10 -6
  20. package/dist/lib/deps.d.ts.map +1 -1
  21. package/dist/lib/deps.js +20 -8
  22. package/dist/lib/deps.js.map +1 -1
  23. package/dist/lib/language-filter.d.ts +31 -0
  24. package/dist/lib/language-filter.d.ts.map +1 -0
  25. package/dist/lib/language-filter.js +14 -0
  26. package/dist/lib/language-filter.js.map +1 -0
  27. package/dist/lib/openapi/patch-openapi-schemas-runtime.d.ts +24 -0
  28. package/dist/lib/openapi/patch-openapi-schemas-runtime.d.ts.map +1 -0
  29. package/dist/lib/openapi/patch-openapi-schemas-runtime.js +75 -0
  30. package/dist/lib/openapi/patch-openapi-schemas-runtime.js.map +1 -0
  31. package/dist/lib/openapi/validator.d.ts.map +1 -1
  32. package/dist/lib/openapi/validator.js +23 -0
  33. package/dist/lib/openapi/validator.js.map +1 -1
  34. package/package.json +3 -2
  35. package/scripts/patch-openapi-schemas.js +8 -3
  36. package/vendored/lib/openapi/patch-openapi-schemas-runtime.ts +83 -0
  37. package/vendored/lib/openapi/validator.ts +28 -0
  38. package/vendored/next.config.js +13 -1
  39. package/vendored/workspace-package-lock.json +86 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dev-workspace-symlinks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-workspace-symlinks.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @vitest-environment node
3
+ *
4
+ * Tests prepareProjectWorkspaceLinks — replaces the single
5
+ * <workspace>/projects/<name> -> <projectDir> symlink with per-entry
6
+ * symlinks that skip non-active language directories. This is what
7
+ * actually reduces Turbopack's filesystem scan from 403 MDX files to
8
+ * 135 on jamdesk-docs.
9
+ */
10
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
11
+ import fs from 'fs-extra';
12
+ import path from 'path';
13
+ import { tmpdir } from 'os';
14
+ import { prepareProjectWorkspaceLinks } from '../../commands/dev.js';
15
+ import { output } from '../../lib/output.js';
16
+ describe('prepareProjectWorkspaceLinks', () => {
17
+ let tmpRoot;
18
+ let projectDir;
19
+ let workspaceProjectDir;
20
+ beforeEach(() => {
21
+ tmpRoot = fs.mkdtempSync(path.join(tmpdir(), 'jam-ws-'));
22
+ projectDir = path.join(tmpRoot, 'project');
23
+ workspaceProjectDir = path.join(tmpRoot, 'ws', 'projects', 'project');
24
+ // Set up a project layout that mirrors a multi-language docs project:
25
+ // project/
26
+ // ai/intro.mdx (en at root)
27
+ // development/foo.mdx (en at root)
28
+ // es/ai/intro.mdx (spanish)
29
+ // fr/ai/intro.mdx (french)
30
+ // docs.json
31
+ // images/logo.png
32
+ fs.mkdirpSync(path.join(projectDir, 'ai'));
33
+ fs.writeFileSync(path.join(projectDir, 'ai', 'intro.mdx'), '# en');
34
+ fs.mkdirpSync(path.join(projectDir, 'development'));
35
+ fs.writeFileSync(path.join(projectDir, 'development', 'foo.mdx'), '# en');
36
+ fs.mkdirpSync(path.join(projectDir, 'es', 'ai'));
37
+ fs.writeFileSync(path.join(projectDir, 'es', 'ai', 'intro.mdx'), '# es');
38
+ fs.mkdirpSync(path.join(projectDir, 'fr', 'ai'));
39
+ fs.writeFileSync(path.join(projectDir, 'fr', 'ai', 'intro.mdx'), '# fr');
40
+ fs.writeFileSync(path.join(projectDir, 'docs.json'), '{}');
41
+ fs.mkdirpSync(path.join(projectDir, 'images'));
42
+ fs.writeFileSync(path.join(projectDir, 'images', 'logo.png'), '');
43
+ });
44
+ afterEach(() => {
45
+ fs.removeSync(tmpRoot);
46
+ });
47
+ it('symlinks every top-level entry when skip set is empty', async () => {
48
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
49
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
50
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es', 'ai', 'intro.mdx'))).toBe(true);
51
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'fr', 'ai', 'intro.mdx'))).toBe(true);
52
+ });
53
+ it('does not symlink docs.json (caller writes a filtered copy)', async () => {
54
+ // docs.json must not be symlinked — the caller writes a per-language
55
+ // filtered copy, and fs.writeFile through a symlink would clobber the
56
+ // user's source docs.json.
57
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
58
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'docs.json'))).toBe(false);
59
+ });
60
+ it('skips entries whose names are in the skip set', async () => {
61
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es', 'fr']));
62
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
63
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'development', 'foo.mdx'))).toBe(true);
64
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'images', 'logo.png'))).toBe(true);
65
+ // Skipped:
66
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
67
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'fr'))).toBe(false);
68
+ // docs.json not symlinked — caller writes a filtered copy.
69
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'docs.json'))).toBe(false);
70
+ });
71
+ it('rebuilds the workspace links from scratch on subsequent calls', async () => {
72
+ // First call: no skip
73
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
74
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(true);
75
+ // Second call: skip es
76
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es']));
77
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
78
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'fr'))).toBe(true);
79
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
80
+ });
81
+ it('handles a pre-existing single symlink at workspaceProjectDir (legacy layout)', async () => {
82
+ // Pre-create the legacy single-symlink layout
83
+ fs.mkdirpSync(path.dirname(workspaceProjectDir));
84
+ fs.symlinkSync(projectDir, workspaceProjectDir, 'junction');
85
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es', 'fr']));
86
+ const lstat = fs.lstatSync(workspaceProjectDir);
87
+ expect(lstat.isDirectory()).toBe(true);
88
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
89
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
90
+ });
91
+ it('surfaces friendly error (not raw stack trace) when fs.rm throws ENOTEMPTY', async () => {
92
+ // Regression: before safeRemoveCache, fs.remove raised an unfriendly stack
93
+ // trace when Turbopack still held open files. Now safeRemoveCache detects
94
+ // the race and calls process.exit(1) with a human-readable message.
95
+ const rmSpy = vi.spyOn(fs, 'rm');
96
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
97
+ throw new Error(`process.exit:${code}`);
98
+ }));
99
+ const errorSpy = vi.spyOn(output, 'error').mockImplementation(() => undefined);
100
+ const enotempty = Object.assign(new Error('ENOTEMPTY'), { code: 'ENOTEMPTY' });
101
+ // fs.rm will be called by safeRemoveCache; make it fail with ENOTEMPTY
102
+ // even after the internal maxRetries — simulate persistent race condition.
103
+ rmSpy.mockRejectedValue(enotempty);
104
+ await expect(prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set())).rejects.toThrow('process.exit:1');
105
+ const msg = errorSpy.mock.calls[0]?.[0] ?? '';
106
+ expect(msg).toContain('Another `jamdesk dev` instance');
107
+ expect(msg).toContain('pkill -f');
108
+ expect(exitSpy).toHaveBeenCalledWith(1);
109
+ vi.restoreAllMocks();
110
+ });
111
+ });
112
+ //# sourceMappingURL=dev-workspace-symlinks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-workspace-symlinks.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=language-filter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-filter.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @vitest-environment node
3
+ *
4
+ * Tests for getActiveLanguageFilter — pure helper that decides which
5
+ * top-level language directories to skip when symlinking project content
6
+ * into the dev workspace. Multi-language sites' non-default languages
7
+ * inflate Turbopack's filesystem scan and balloon cold compile time
8
+ * (jamdesk-docs: 67s with 3 langs vs. 12.5s with 1 lang).
9
+ */
10
+ import { describe, it, expect } from 'vitest';
11
+ import { getActiveLanguageFilter, isPageInSkippedLanguage, filterConfigByActiveLanguage, } from '../../lib/language-filter.js';
12
+ const config = (langs) => ({
13
+ navigation: { languages: langs.map(l => ({ language: l.language, default: l.default })) },
14
+ });
15
+ describe('getActiveLanguageFilter', () => {
16
+ it('returns null filter when project has no languages array', () => {
17
+ const result = getActiveLanguageFilter({ navigation: {} }, undefined, false);
18
+ expect(result).toEqual({ active: null, skip: new Set() });
19
+ });
20
+ it('returns null filter when project has only one language', () => {
21
+ const result = getActiveLanguageFilter(config([{ language: 'en', default: true }]), undefined, false);
22
+ expect(result).toEqual({ active: 'en', skip: new Set() });
23
+ });
24
+ it('skips non-default languages when active is the default', () => {
25
+ const result = getActiveLanguageFilter(config([
26
+ { language: 'en', default: true },
27
+ { language: 'es' },
28
+ { language: 'fr' },
29
+ ]), undefined, false);
30
+ expect(result.active).toBe('en');
31
+ expect(result.skip).toEqual(new Set(['es', 'fr']));
32
+ });
33
+ it('falls back to first language when none is marked default', () => {
34
+ const result = getActiveLanguageFilter(config([{ language: 'en' }, { language: 'es' }, { language: 'fr' }]), undefined, false);
35
+ expect(result.active).toBe('en');
36
+ expect(result.skip).toEqual(new Set(['es', 'fr']));
37
+ });
38
+ it('accepts --lang matching the default language (no-op equivalence)', () => {
39
+ const result = getActiveLanguageFilter(config([
40
+ { language: 'en', default: true },
41
+ { language: 'es' },
42
+ ]), 'en', false);
43
+ expect(result.active).toBe('en');
44
+ expect(result.skip).toEqual(new Set(['es']));
45
+ });
46
+ it('throws on --lang for a non-default language (initial release scope)', () => {
47
+ // Non-default-language layouts mix root-level default-lang content with
48
+ // <lang>/ subdirs for translations; correctly stripping the default
49
+ // content while keeping <lang>/ takes more work and is deferred to a
50
+ // follow-up. For now, surface the workaround clearly.
51
+ expect(() => getActiveLanguageFilter(config([
52
+ { language: 'en', default: true },
53
+ { language: 'es' },
54
+ { language: 'fr' },
55
+ ]), 'es', false)).toThrow(/--lang es: previewing non-default languages.*--all-langs/i);
56
+ });
57
+ it('returns empty skip set when --all-langs is set', () => {
58
+ const result = getActiveLanguageFilter(config([
59
+ { language: 'en', default: true },
60
+ { language: 'es' },
61
+ ]), undefined, true);
62
+ expect(result.active).toBe('en');
63
+ expect(result.skip).toEqual(new Set());
64
+ });
65
+ it('throws on --lang code that does not exist in docs.json', () => {
66
+ expect(() => getActiveLanguageFilter(config([{ language: 'en', default: true }, { language: 'es' }]), 'de', false)).toThrow(/--lang de.*not.*docs\.json.*Available: en, es/);
67
+ });
68
+ it('handles malformed languages array (entries missing language field) by ignoring them', () => {
69
+ const malformed = {
70
+ navigation: {
71
+ languages: [
72
+ { language: 'en', default: true },
73
+ { default: false }, // missing language: ignored
74
+ { language: 'es' },
75
+ { language: 42 }, // wrong type: ignored
76
+ ],
77
+ },
78
+ };
79
+ const result = getActiveLanguageFilter(malformed, undefined, false);
80
+ expect(result.active).toBe('en');
81
+ expect(result.skip).toEqual(new Set(['es']));
82
+ });
83
+ it('returns null active when no valid language entries exist', () => {
84
+ const result = getActiveLanguageFilter({ navigation: { languages: [{ default: true }] } }, undefined, false);
85
+ expect(result).toEqual({ active: null, skip: new Set() });
86
+ });
87
+ it('rejects empty-string --lang as an invalid code (commander passes "" through)', () => {
88
+ // commander.js treats `--lang ""` as a value, not as missing — so
89
+ // langOption is "" (defined, but empty). Empty string is never a valid
90
+ // language code; surface it as a clear error rather than silently
91
+ // falling back to the default.
92
+ expect(() => getActiveLanguageFilter(config([{ language: 'en', default: true }, { language: 'es' }]), '', false)).toThrow(/--lang.*not.*docs\.json/);
93
+ });
94
+ });
95
+ describe('isPageInSkippedLanguage', () => {
96
+ it('returns false when skip set is empty', () => {
97
+ expect(isPageInSkippedLanguage('fr/introduction', new Set())).toBe(false);
98
+ });
99
+ it('returns true when first path segment is a skipped language', () => {
100
+ expect(isPageInSkippedLanguage('fr/introduction', new Set(['fr']))).toBe(true);
101
+ });
102
+ it('returns true for nested paths inside a skipped language', () => {
103
+ expect(isPageInSkippedLanguage('fr/setup/connecting-github', new Set(['fr', 'de']))).toBe(true);
104
+ });
105
+ it('returns false when first segment is the active language', () => {
106
+ expect(isPageInSkippedLanguage('en/introduction', new Set(['fr']))).toBe(false);
107
+ });
108
+ it('returns false for unprefixed root pages', () => {
109
+ expect(isPageInSkippedLanguage('introduction', new Set(['fr']))).toBe(false);
110
+ });
111
+ it('does not match when the skip code is a prefix-substring of a different segment', () => {
112
+ // Guard against startsWith('fr/') matching e.g. 'fr-something/foo'
113
+ expect(isPageInSkippedLanguage('fr-something/foo', new Set(['fr']))).toBe(false);
114
+ });
115
+ it('returns true for a path with a fragment anchor in a skipped language', () => {
116
+ // Broken-anchor warnings from validate-links.cjs surface as link values
117
+ // like `fr/introduction#missing-section` — the fragment lives past the
118
+ // first segment so the language check still works.
119
+ expect(isPageInSkippedLanguage('fr/introduction#setup', new Set(['fr']))).toBe(true);
120
+ });
121
+ });
122
+ describe('filterConfigByActiveLanguage', () => {
123
+ it('returns the input unchanged (same reference) when skip is empty', () => {
124
+ const config = { name: 'foo', navigation: { languages: [{ language: 'en' }] } };
125
+ const result = filterConfigByActiveLanguage(config, { active: 'en', skip: new Set() });
126
+ expect(result).toBe(config);
127
+ });
128
+ it('drops skipped languages from navigation.languages', () => {
129
+ const config = {
130
+ name: 'jamdesk-docs',
131
+ navigation: {
132
+ languages: [
133
+ { language: 'en', default: true, tabs: [{ tab: 'Guide' }] },
134
+ { language: 'fr', tabs: [{ tab: 'Guide' }] },
135
+ { language: 'de', tabs: [{ tab: 'Guide' }] },
136
+ ],
137
+ },
138
+ };
139
+ const result = filterConfigByActiveLanguage(config, {
140
+ active: 'en',
141
+ skip: new Set(['fr', 'de']),
142
+ });
143
+ expect(result.navigation.languages).toEqual([
144
+ { language: 'en', default: true, tabs: [{ tab: 'Guide' }] },
145
+ ]);
146
+ // Top-level fields preserved.
147
+ expect(result.name).toBe('jamdesk-docs');
148
+ // Original config untouched.
149
+ expect(config.navigation.languages).toHaveLength(3);
150
+ });
151
+ it('preserves other navigation fields (tabs, global, anchors, etc.)', () => {
152
+ const config = {
153
+ navigation: {
154
+ languages: [{ language: 'en' }, { language: 'fr' }],
155
+ global: { anchors: [{ anchor: 'Support' }] },
156
+ },
157
+ };
158
+ const result = filterConfigByActiveLanguage(config, {
159
+ active: 'en',
160
+ skip: new Set(['fr']),
161
+ });
162
+ expect(result.navigation.global).toEqual({ anchors: [{ anchor: 'Support' }] });
163
+ expect(result.navigation.languages).toHaveLength(1);
164
+ });
165
+ });
166
+ //# sourceMappingURL=language-filter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-filter.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":"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"}
@@ -10,12 +10,22 @@
10
10
  * valid — Mintlify accepts it, and `jamdesk migrate`'s auto-discovered
11
11
  * Mintlify starter spec uses exactly `{ "name": "MIT" }`.
12
12
  *
13
- * The patcher (scripts/patch-openapi-schemas.js) must have replaced the bad
14
- * oneOf with `not: { required: [identifier, url] }` by the time this runs.
15
- * Uses the REAL SwaggerParser so it exercises the bundled schema.
13
+ * The bad oneOf must be replaced with `not: { required: [identifier, url] }`
14
+ * by the time these specs are validated. Uses the REAL SwaggerParser so it
15
+ * exercises the bundled schema, and heals the in-memory singleton first
16
+ * because the on-disk postinstall patch (scripts/patch-openapi-schemas.js)
17
+ * CANNOT be assumed to have run: npm 12 blocks lifecycle scripts by default,
18
+ * so a fresh `npm ci` leaves node_modules unpatched. Relying on the
19
+ * postinstall made this test fail on correct code after any clean install —
20
+ * the same heal production code performs at the top of validateOpenApiSpec()
21
+ * is what it must exercise.
16
22
  */
17
- import { describe, it, expect } from 'vitest';
23
+ import { describe, it, expect, beforeAll } from 'vitest';
18
24
  import SwaggerParser from '@apidevtools/swagger-parser';
25
+ import { patchOpenApiSchemasInMemory } from '../../lib/openapi/patch-openapi-schemas-runtime.js';
26
+ beforeAll(() => {
27
+ patchOpenApiSchemasInMemory();
28
+ });
19
29
  const base = (license) => ({
20
30
  openapi: '3.1.0',
21
31
  info: { title: 'License Regression', version: '1.0.0', license },
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-license-identifier.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-license-identifier.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,aAAa,MAAM,6BAA6B,CAAC;AAExD,MAAM,IAAI,GAAG,CAAC,OAAgC,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;IAChE,KAAK,EAAE,EAAE;CACV,CAAC,CAAC;AAEH,QAAQ,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC/D,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAU,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,aAAa,CAAC,QAAQ,CAC1B,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,CAAU,CAChE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QAClC,MAAM,aAAa,CAAC,QAAQ,CAC1B,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,6BAA6B,EAAE,CAAU,CACtE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,MAAM,CACV,aAAa,CAAC,QAAQ,CACpB,IAAI,CAAC;YACH,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,KAAK;YACjB,GAAG,EAAE,6BAA6B;SACnC,CAAU,CACZ,CACF,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"openapi-license-identifier.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-license-identifier.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,oDAAoD,CAAC;AAEjG,SAAS,CAAC,GAAG,EAAE;IACb,2BAA2B,EAAE,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,MAAM,IAAI,GAAG,CAAC,OAAgC,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;IAChE,KAAK,EAAE,EAAE;CACV,CAAC,CAAC;AAEH,QAAQ,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC/D,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAU,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,aAAa,CAAC,QAAQ,CAC1B,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,CAAU,CAChE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QAClC,MAAM,aAAa,CAAC,QAAQ,CAC1B,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,6BAA6B,EAAE,CAAU,CACtE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,MAAM,CACV,aAAa,CAAC,QAAQ,CACpB,IAAI,CAAC;YACH,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,KAAK;YACjB,GAAG,EAAE,6BAA6B;SACnC,CAAU,CACZ,CACF,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,17 +1,31 @@
1
1
  /**
2
- * Drift guard for the 3-place OpenAPI-schema patch sync chain.
2
+ * Drift guard for the 6-place OpenAPI-schema patch sync chain.
3
3
  *
4
4
  * The @apidevtools/openapi-schemas@2.1.0 v3.1 fixes (server-variable
5
5
  * `descriptions`→`description` typo; `license` oneOf over-constraint) are
6
- * applied in THREE places that cannot share a module — an ESM postinstall,
7
- * a CJS postinstall in a separate npm package, and a runtime TS patch of
8
- * the ~/.jamdesk workspace install:
6
+ * applied in SIX places that cannot share a module — an ESM postinstall,
7
+ * a CJS postinstall in a separate npm package, a runtime TS patch of the
8
+ * ~/.jamdesk workspace install, two in-memory runtime self-heal helpers,
9
+ * and the vendored copy of one of those helpers that ships in the npm
10
+ * tarball:
9
11
  *
10
12
  * - builder/cli/scripts/patch-openapi-schemas.js (ESM postinstall)
11
13
  * - builder/build-service/scripts/patch-openapi-schemas.js (CJS postinstall)
12
- * - patchWorkspaceOpenApiSchemas() in builder/cli/src/lib/deps.ts (runtime)
14
+ * - patchWorkspaceOpenApiSchemas() in builder/cli/src/lib/deps.ts (runtime, disk)
15
+ * - builder/cli/src/lib/openapi/patch-openapi-schemas-runtime.ts (in-memory)
16
+ * - builder/build-service/lib/openapi/patch-openapi-schemas-runtime.ts (in-memory)
17
+ * - builder/cli/vendored/lib/openapi/patch-openapi-schemas-runtime.ts
18
+ * (in-memory, vendored copy — ships in the npm tarball; nothing but a
19
+ * manual `npm run vendor` re-syncs it from the build-service source above)
13
20
  *
14
- * Comments say "if you change one, change all three"; this test enforces
21
+ * The two in-memory helpers exist because npm 12 blocks postinstall
22
+ * lifecycle scripts not covered by `allowScripts` (silent `warn
23
+ * install-scripts`, exit 0) — so the disk-patching postinstalls above are
24
+ * best-effort only. Patching the required `@apidevtools/openapi-schemas`
25
+ * module singleton in place, at swagger-parser call time, is the actual
26
+ * guarantee regardless of installer or script-blocking behavior.
27
+ *
28
+ * Comments say "if you change one, change all six"; this test enforces
15
29
  * it mechanically (cf. the other documented sync chains in
16
30
  * builder/CLAUDE.md). Whitespace is normalized so the assertion pins the
17
31
  * patch *logic*, not formatting.
@@ -25,6 +39,9 @@ const SITES = {
25
39
  'cli postinstall (ESM)': path.join(here, '../../../scripts/patch-openapi-schemas.js'),
26
40
  'build-service postinstall (CJS)': path.join(here, '../../../../build-service/scripts/patch-openapi-schemas.js'),
27
41
  'deps.ts runtime patch': path.join(here, '../../lib/deps.ts'),
42
+ 'cli in-memory runtime patch': path.join(here, '../../lib/openapi/patch-openapi-schemas-runtime.ts'),
43
+ 'build-service in-memory runtime patch': path.join(here, '../../../../build-service/lib/openapi/patch-openapi-schemas-runtime.ts'),
44
+ 'cli vendored in-memory runtime patch (ships in the npm tarball)': path.join(here, '../../../vendored/lib/openapi/patch-openapi-schemas-runtime.ts'),
28
45
  };
29
46
  // The patch logic, whitespace-collapsed. Every site must contain all of these.
30
47
  const REQUIRED_SNIPPETS = [
@@ -37,7 +54,7 @@ const REQUIRED_SNIPPETS = [
37
54
  // keeps their output byte-identical to deps.ts's fs-extra writeJson.
38
55
  const SCRIPT_NEWLINE_SNIPPET = "JSON.stringify(schema, null, 2) + '\\n'";
39
56
  const collapse = (s) => s.replace(/\s+/g, ' ');
40
- describe('OpenAPI schema patch sync chain (3-place drift guard)', () => {
57
+ describe('OpenAPI schema patch sync chain (6-place drift guard)', () => {
41
58
  for (const [name, file] of Object.entries(SITES)) {
42
59
  it(`${name} applies both fixes (server-variable + license)`, async () => {
43
60
  const src = collapse(await fs.readFile(file, 'utf-8'));
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-schema-patch-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-schema-patch-sync.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,KAAK,GAAG;IACZ,uBAAuB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2CAA2C,CAAC;IACrF,iCAAiC,EAAE,IAAI,CAAC,IAAI,CAC1C,IAAI,EACJ,4DAA4D,CAC7D;IACD,uBAAuB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC;CAC9D,CAAC;AAEF,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG;IACxB,yCAAyC;IACzC,4BAA4B;IAC5B,uBAAuB;IACvB,oDAAoD;CACrD,CAAC;AAEF,6EAA6E;AAC7E,qEAAqE;AACrE,MAAM,sBAAsB,GAAG,yCAAyC,CAAC;AAEzE,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEvD,QAAQ,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACrE,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,EAAE,CAAC,GAAG,IAAI,iDAAiD,EAAE,KAAK,IAAI,EAAE;YACtE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YACvD,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,gBAAgB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,EAAE,CAAC,kFAAkF,EAAE,KAAK,IAAI,EAAE;QAChG,KAAK,MAAM,IAAI,IAAI,CAAC,uBAAuB,EAAE,iCAAiC,CAAU,EAAE,CAAC;YACzF,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YAC9D,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,kCAAkC,CAAC,CAAC,SAAS,CAC9D,QAAQ,CAAC,sBAAsB,CAAC,CACjC,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"openapi-schema-patch-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-schema-patch-sync.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,KAAK,GAAG;IACZ,uBAAuB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2CAA2C,CAAC;IACrF,iCAAiC,EAAE,IAAI,CAAC,IAAI,CAC1C,IAAI,EACJ,4DAA4D,CAC7D;IACD,uBAAuB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAC7D,6BAA6B,EAAE,IAAI,CAAC,IAAI,CACtC,IAAI,EACJ,oDAAoD,CACrD;IACD,uCAAuC,EAAE,IAAI,CAAC,IAAI,CAChD,IAAI,EACJ,wEAAwE,CACzE;IACD,iEAAiE,EAAE,IAAI,CAAC,IAAI,CAC1E,IAAI,EACJ,gEAAgE,CACjE;CACF,CAAC;AAEF,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG;IACxB,yCAAyC;IACzC,4BAA4B;IAC5B,uBAAuB;IACvB,oDAAoD;CACrD,CAAC;AAEF,6EAA6E;AAC7E,qEAAqE;AACrE,MAAM,sBAAsB,GAAG,yCAAyC,CAAC;AAEzE,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEvD,QAAQ,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACrE,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,EAAE,CAAC,GAAG,IAAI,iDAAiD,EAAE,KAAK,IAAI,EAAE;YACtE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YACvD,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,gBAAgB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,EAAE,CAAC,kFAAkF,EAAE,KAAK,IAAI,EAAE;QAChG,KAAK,MAAM,IAAI,IAAI,CAAC,uBAAuB,EAAE,iCAAiC,CAAU,EAAE,CAAC;YACzF,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YAC9D,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,kCAAkC,CAAC,CAAC,SAAS,CAC9D,QAAQ,CAAC,sBAAsB,CAAC,CACjC,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=openapi-schema-runtime-patch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-schema-runtime-patch.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-schema-runtime-patch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @vitest-environment node
3
+ *
4
+ * Integration test for the runtime (in-memory) self-heal of
5
+ * @apidevtools/openapi-schemas@2.1.0's OAS 3.1 meta-schema defects.
6
+ *
7
+ * npm 12 blocks postinstall scripts by default, so the on-disk schema may be
8
+ * unpatched on user machines. These tests REINTRODUCE both defects on the
9
+ * in-memory module singleton (exactly what an unpatched install presents)
10
+ * and prove validateOpenApiSpec() heals and validates anyway.
11
+ *
12
+ * Forced to `node` (not the monorepo-root jsdom default from
13
+ * builder/vitest.config.ts, which the pre-commit lint-staged hook runs
14
+ * under): under jsdom, SwaggerParser resolves an absolute tmpdir path as a
15
+ * relative URL against `window.location` (`http://localhost:3000/...`)
16
+ * instead of reading it from disk. Same fix as the sibling
17
+ * openapi.test.ts.
18
+ */
19
+ import { describe, it, expect, afterEach, beforeAll } from 'vitest';
20
+ import SwaggerParser from '@apidevtools/swagger-parser';
21
+ import { openapi } from '@apidevtools/openapi-schemas';
22
+ import fs from 'fs-extra';
23
+ import os from 'os';
24
+ import path from 'path';
25
+ import { patchOpenApiSchemasInMemory } from '../../lib/openapi/patch-openapi-schemas-runtime.js';
26
+ import { validateOpenApiSpec } from '../../lib/openapi/validator.js';
27
+ // A spec that is 100% valid OpenAPI 3.1 but trips BOTH upstream defects:
28
+ // - server variable with a (legal) `description`
29
+ // - name-only license (legal per §4.8.4)
30
+ const FIXTURE = `openapi: 3.1.0
31
+ info:
32
+ title: Runtime patch fixture
33
+ version: "1.0.0"
34
+ license:
35
+ name: MIT
36
+ servers:
37
+ - url: https://{host}/api
38
+ variables:
39
+ host:
40
+ default: example.com
41
+ description: API host
42
+ paths:
43
+ /ping:
44
+ get:
45
+ operationId: ping
46
+ summary: Ping
47
+ responses:
48
+ "200":
49
+ description: OK
50
+ `;
51
+ const v31 = openapi.v31;
52
+ /** Reintroduce both upstream defects on the in-memory singleton, as an
53
+ * npm-12 user's unpatched install would present them. Works regardless of
54
+ * whether the on-disk copy was patched. */
55
+ function breakSchema() {
56
+ const props = v31.$defs['server-variable'].properties;
57
+ if (props.description) {
58
+ props.descriptions = props.description;
59
+ delete props.description;
60
+ }
61
+ const license = v31.$defs.license;
62
+ delete license.not;
63
+ license.oneOf = [{ required: ['identifier'] }, { required: ['url'] }];
64
+ }
65
+ let projectDir;
66
+ let specRelPath;
67
+ beforeAll(async () => {
68
+ projectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'jd-runtime-patch-'));
69
+ specRelPath = 'openapi/fixture.yaml';
70
+ await fs.outputFile(path.join(projectDir, specRelPath), FIXTURE);
71
+ });
72
+ afterEach(() => {
73
+ // Never leak a broken singleton to other tests in this worker.
74
+ patchOpenApiSchemasInMemory();
75
+ });
76
+ describe('patchOpenApiSchemasInMemory', () => {
77
+ it('sanity: the reintroduced defects actually break raw SwaggerParser.validate', async () => {
78
+ breakSchema();
79
+ await expect(SwaggerParser.validate(path.join(projectDir, specRelPath))).rejects.toThrow(/unevaluated properties/);
80
+ });
81
+ it('heals both defects on the singleton and reports healthy', () => {
82
+ breakSchema();
83
+ expect(patchOpenApiSchemasInMemory()).toBe(true);
84
+ const props = v31.$defs['server-variable'].properties;
85
+ expect(props.description).toBeDefined();
86
+ expect(props.descriptions).toBeUndefined();
87
+ const license = v31.$defs.license;
88
+ expect(license.oneOf).toBeUndefined();
89
+ expect(license.not).toEqual({ required: ['identifier', 'url'] });
90
+ });
91
+ it('is idempotent (server-variable AND license)', () => {
92
+ breakSchema();
93
+ patchOpenApiSchemasInMemory();
94
+ const snapshot = JSON.stringify([v31.$defs['server-variable'], v31.$defs.license]);
95
+ patchOpenApiSchemasInMemory();
96
+ expect(JSON.stringify([v31.$defs['server-variable'], v31.$defs.license])).toBe(snapshot);
97
+ });
98
+ it('returns false without throwing on an unexpected upstream schema shape', () => {
99
+ const container = openapi;
100
+ const original = container.v31;
101
+ try {
102
+ container.v31 = undefined;
103
+ expect(patchOpenApiSchemasInMemory()).toBe(false);
104
+ container.v31 = { noDefsHere: true };
105
+ expect(patchOpenApiSchemasInMemory()).toBe(false);
106
+ }
107
+ finally {
108
+ container.v31 = original; // Always restore the real singleton.
109
+ }
110
+ });
111
+ it('validateOpenApiSpec self-heals: valid 3.1 spec passes with a broken singleton', async () => {
112
+ breakSchema();
113
+ const result = await validateOpenApiSpec(specRelPath, projectDir);
114
+ // ValidationResult (src/lib/openapi/types.ts:194) = { valid, api?, version?, error? }
115
+ expect(result.error).toBeUndefined();
116
+ expect(result.valid).toBe(true);
117
+ expect(result.version).toBe('3.1');
118
+ });
119
+ });
120
+ //# sourceMappingURL=openapi-schema-runtime-patch.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-schema-runtime-patch.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-schema-runtime-patch.test.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,24 @@
1
1
  /**
2
2
  * Regression guard for the @apidevtools/openapi-schemas@2.1.0 typo in
3
3
  * the OpenAPI 3.1 server-variable schema (`descriptions` instead of
4
- * `description`). The patcher at scripts/patch-openapi-schemas.js must
5
- * have fixed the installed schema by the time this test runs.
4
+ * `description`).
6
5
  *
7
6
  * This test uses the REAL SwaggerParser (not a mock) so it actually
8
- * exercises the bundled schema. If the patcher didn't run, or a future
9
- * upstream release reintroduces the typo, this test fails.
7
+ * exercises the bundled schema. It heals the in-memory singleton first
8
+ * because the on-disk postinstall patch (scripts/patch-openapi-schemas.js)
9
+ * CANNOT be assumed to have run: npm 12 blocks lifecycle scripts by default,
10
+ * so a fresh `npm ci` leaves node_modules unpatched. Relying on the
11
+ * postinstall made this test fail on correct code after any clean install —
12
+ * the same heal production code performs at the top of validateOpenApiSpec()
13
+ * is what it must exercise. If a future upstream release reshapes the schema
14
+ * so the heal can't apply, this test fails.
10
15
  */
11
- import { describe, it } from 'vitest';
16
+ import { describe, it, beforeAll } from 'vitest';
12
17
  import SwaggerParser from '@apidevtools/swagger-parser';
18
+ import { patchOpenApiSchemasInMemory } from '../../lib/openapi/patch-openapi-schemas-runtime.js';
19
+ beforeAll(() => {
20
+ patchOpenApiSchemasInMemory();
21
+ });
13
22
  describe('OpenAPI 3.1 server variable description (regression)', () => {
14
23
  it('accepts `description` on a server variable (spec §4.7.10.1)', async () => {
15
24
  const spec = {
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-server-variable-description.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-server-variable-description.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,aAAa,MAAM,6BAA6B,CAAC;AAExD,QAAQ,CAAC,sDAAsD,EAAE,GAAG,EAAE;IACpE,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,IAAI,GAAG;YACX,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,OAAO,EAAE;gBACP;oBACE,GAAG,EAAE,+BAA+B;oBACpC,WAAW,EAAE,wBAAwB;oBACrC,SAAS,EAAE;wBACT,OAAO,EAAE;4BACP,OAAO,EAAE,cAAc;4BACvB,WAAW,EAAE,2BAA2B;yBACzC;qBACF;iBACF;aACF;YACD,KAAK,EAAE,EAAE;SACV,CAAC;QACF,iFAAiF;QACjF,oDAAoD;QACpD,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAa,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,IAAI,GAAG;YACX,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,OAAO,EAAE;gBACP;oBACE,GAAG,EAAE,+BAA+B;oBACpC,SAAS,EAAE;wBACT,OAAO,EAAE;4BACP,OAAO,EAAE,cAAc;yBACxB;qBACF;iBACF;aACF;YACD,KAAK,EAAE,EAAE;SACV,CAAC;QACF,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAa,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"openapi-server-variable-description.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/openapi-server-variable-description.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,oDAAoD,CAAC;AAEjG,SAAS,CAAC,GAAG,EAAE;IACb,2BAA2B,EAAE,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sDAAsD,EAAE,GAAG,EAAE;IACpE,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,IAAI,GAAG;YACX,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,OAAO,EAAE;gBACP;oBACE,GAAG,EAAE,+BAA+B;oBACpC,WAAW,EAAE,wBAAwB;oBACrC,SAAS,EAAE;wBACT,OAAO,EAAE;4BACP,OAAO,EAAE,cAAc;4BACvB,WAAW,EAAE,2BAA2B;yBACzC;qBACF;iBACF;aACF;YACD,KAAK,EAAE,EAAE;SACV,CAAC;QACF,iFAAiF;QACjF,oDAAoD;QACpD,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAa,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,IAAI,GAAG;YACX,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,OAAO,EAAE;gBACP;oBACE,GAAG,EAAE,+BAA+B;oBACpC,SAAS,EAAE;wBACT,OAAO,EAAE;4BACP,OAAO,EAAE,cAAc;yBACxB;qBACF;iBACF;aACF;YACD,KAAK,EAAE,EAAE;SACV,CAAC;QACF,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAa,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}