api-spec-cli 0.2.4 → 0.2.5

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.
@@ -1,67 +1,67 @@
1
- import { out } from "../output.js";
2
- import { parseArgs } from "../args.js";
3
- import { getRegistry, getEntry, getCachedSpec, saveCachedSpec, allEntries } from "../registry.js";
4
- import { fetchSpec } from "./fetch.js";
5
- import { matchGlob } from "../glob.js";
6
-
7
- export async function grepCmd(args) {
8
- const { flags, positional } = parseArgs(args);
9
- const pattern = positional[0];
10
- if (!pattern)
11
- throw new Error(
12
- "Usage: spec grep <pattern> [--spec <name>]\n" +
13
- " Glob patterns: * matches anything, ? matches one char\n" +
14
- " Plain text: substring match across name and description"
15
- );
16
-
17
- const entries = flags.spec
18
- ? [getEntry(flags.spec)]
19
- : allEntries(getRegistry()).filter((e) => e.enabled);
20
-
21
- if (entries.length === 0) throw new Error("No registered specs. Run 'spec add' first.");
22
-
23
- const results = [];
24
-
25
- for (const entry of entries) {
26
- let spec = getCachedSpec(entry.name);
27
- if (!spec) {
28
- spec = await fetchSpec(entry);
29
- saveCachedSpec(entry.name, spec);
30
- }
31
-
32
- const matches = [];
33
-
34
- if (spec.type === "mcp") {
35
- for (const tool of spec.tools) {
36
- const nameMatch = matchGlob(pattern, tool.name);
37
- const descMatch = tool.description && matchGlob(pattern, tool.description);
38
- if (nameMatch || descMatch) {
39
- matches.push({ id: tool.name, description: tool.description });
40
- }
41
- }
42
- } else if (spec.type === "openapi") {
43
- for (const op of spec.operations) {
44
- if (
45
- matchGlob(pattern, op.id) ||
46
- matchGlob(pattern, op.path) ||
47
- (op.summary && matchGlob(pattern, op.summary))
48
- ) {
49
- matches.push({ id: op.id, method: op.method, path: op.path });
50
- }
51
- }
52
- } else if (spec.type === "graphql") {
53
- for (const op of spec.operations) {
54
- if (matchGlob(pattern, op.name) || (op.description && matchGlob(pattern, op.description))) {
55
- matches.push({ id: op.name, kind: op.kind });
56
- }
57
- }
58
- }
59
-
60
- if (matches.length > 0) {
61
- results.push({ spec: entry.name, type: spec.type, matches });
62
- }
63
- }
64
-
65
- const total = results.reduce((s, r) => s + r.matches.length, 0);
66
- out({ pattern, total, results });
67
- }
1
+ import { out } from "../output.js";
2
+ import { parseArgs } from "../args.js";
3
+ import { getRegistry, getEntry, getCachedSpec, saveCachedSpec, allEntries } from "../registry.js";
4
+ import { fetchSpec } from "./fetch.js";
5
+ import { matchGlob } from "../glob.js";
6
+
7
+ export async function grepCmd(args) {
8
+ const { flags, positional } = parseArgs(args);
9
+ const pattern = positional[0];
10
+ if (!pattern)
11
+ throw new Error(
12
+ "Usage: spec grep <pattern> [--spec <name>]\n" +
13
+ " Glob patterns: * matches anything, ? matches one char\n" +
14
+ " Plain text: substring match across name and description"
15
+ );
16
+
17
+ const entries = flags.spec
18
+ ? [getEntry(flags.spec)]
19
+ : allEntries(getRegistry()).filter((e) => e.enabled);
20
+
21
+ if (entries.length === 0) throw new Error("No registered specs. Run 'spec add' first.");
22
+
23
+ const results = [];
24
+
25
+ for (const entry of entries) {
26
+ let spec = getCachedSpec(entry.name);
27
+ if (!spec) {
28
+ spec = await fetchSpec(entry);
29
+ saveCachedSpec(entry.name, spec);
30
+ }
31
+
32
+ const matches = [];
33
+
34
+ if (spec.type === "mcp") {
35
+ for (const tool of spec.tools) {
36
+ const nameMatch = matchGlob(pattern, tool.name);
37
+ const descMatch = tool.description && matchGlob(pattern, tool.description);
38
+ if (nameMatch || descMatch) {
39
+ matches.push({ id: tool.name, description: tool.description });
40
+ }
41
+ }
42
+ } else if (spec.type === "openapi") {
43
+ for (const op of spec.operations) {
44
+ if (
45
+ matchGlob(pattern, op.id) ||
46
+ matchGlob(pattern, op.path) ||
47
+ (op.summary && matchGlob(pattern, op.summary))
48
+ ) {
49
+ matches.push({ id: op.id, method: op.method, path: op.path });
50
+ }
51
+ }
52
+ } else if (spec.type === "graphql") {
53
+ for (const op of spec.operations) {
54
+ if (matchGlob(pattern, op.name) || (op.description && matchGlob(pattern, op.description))) {
55
+ matches.push({ id: op.name, kind: op.kind });
56
+ }
57
+ }
58
+ }
59
+
60
+ if (matches.length > 0) {
61
+ results.push({ spec: entry.name, type: spec.type, matches });
62
+ }
63
+ }
64
+
65
+ const total = results.reduce((s, r) => s + r.matches.length, 0);
66
+ out({ pattern, total, results });
67
+ }
@@ -1,6 +1,7 @@
1
1
  import { out } from "../output.js";
2
2
  import { parseArgs } from "../args.js";
3
3
  import { resolveSpec } from "../resolve.js";
4
+ import { getUsage } from "../usage.js";
4
5
 
5
6
  export async function listOperations(args) {
6
7
  const opts = parseArgs(args);
@@ -13,6 +14,7 @@ export async function listOperations(args) {
13
14
  const limit = parseInt(flags.limit) || 0;
14
15
  const offset = parseInt(flags.offset) || 0;
15
16
  const tag = flags.tag?.toLowerCase();
17
+ const top = parseInt(flags.top) || 0;
16
18
 
17
19
  let operations;
18
20
 
@@ -65,8 +67,17 @@ export async function listOperations(args) {
65
67
 
66
68
  const total = operations.length;
67
69
 
68
- if (offset > 0) operations = operations.slice(offset);
69
- if (limit > 0) operations = operations.slice(0, limit);
70
+ if (top > 0) {
71
+ const usageMap = flags.spec ? getUsage(flags.spec) : {};
72
+ operations = operations
73
+ .map((op) => ({ op, count: usageMap[op.id]?.count ?? 0 }))
74
+ .sort((a, b) => b.count - a.count)
75
+ .map(({ op }) => op)
76
+ .slice(0, top);
77
+ } else {
78
+ if (offset > 0) operations = operations.slice(offset);
79
+ if (limit > 0) operations = operations.slice(0, limit);
80
+ }
70
81
 
71
82
  out({
72
83
  type: spec.type,
@@ -1,224 +1,224 @@
1
- import { out } from "../output.js";
2
- import { parseArgs } from "../args.js";
3
- import { resolveSpec } from "../resolve.js";
4
-
5
- export async function showOperation(args) {
6
- const { flags, positional } = parseArgs(args);
7
- const target = positional[0];
8
- if (!target)
9
- throw new Error(
10
- "Usage: spec show <operationId-or-path> [--spec <name> | --openapi <url> | ...]"
11
- );
12
-
13
- const { spec } = await resolveSpec(flags);
14
-
15
- if (spec.type === "openapi") {
16
- showOpenAPI(spec, target);
17
- } else if (spec.type === "mcp") {
18
- showMCP(spec, target);
19
- } else {
20
- showGraphQL(spec, target);
21
- }
22
- }
23
-
24
- function showOpenAPI(spec, target) {
25
- const lower = target.toLowerCase();
26
-
27
- const op = spec.operations.find(
28
- (o) =>
29
- o.id.toLowerCase() === lower ||
30
- o.path.toLowerCase() === lower ||
31
- `${o.method.toLowerCase()} ${o.path.toLowerCase()}` === lower
32
- );
33
-
34
- if (!op) {
35
- throw new Error(`Operation not found: ${target}. Run 'spec list' to see available operations.`);
36
- }
37
-
38
- const root = spec.raw || spec.components;
39
-
40
- out({
41
- id: op.id,
42
- method: op.method,
43
- path: op.path,
44
- summary: op.summary,
45
- description: op.description,
46
- tags: op.tags,
47
- deprecated: op.deprecated,
48
- parameters: op.parameters.map((p) => {
49
- const resolved = resolveRef(p, root);
50
- return {
51
- name: resolved.name,
52
- in: resolved.in,
53
- required: resolved.required || false,
54
- type: resolved.schema?.type || null,
55
- format: resolved.schema?.format || undefined,
56
- description: resolved.description || undefined,
57
- enum: resolved.schema?.enum || undefined,
58
- };
59
- }),
60
- requestBody: op.requestBody ? resolveRequestBody(op.requestBody, root) : null,
61
- responses: resolveResponsesCompact(op.responses, root),
62
- });
63
- }
64
-
65
- function showMCP(spec, target) {
66
- const tool = spec.tools.find((t) => t.name.toLowerCase() === target.toLowerCase());
67
- if (!tool) {
68
- throw new Error(`Tool not found: ${target}. Run 'spec list' to see available tools.`);
69
- }
70
- out({
71
- name: tool.name,
72
- description: tool.description,
73
- inputSchema: tool.inputSchema,
74
- });
75
- }
76
-
77
- function showGraphQL(spec, target) {
78
- const lower = target.toLowerCase();
79
-
80
- const op = spec.operations.find((o) => o.name.toLowerCase() === lower);
81
-
82
- if (!op) {
83
- throw new Error(`Operation not found: ${target}. Run 'spec list' to see available operations.`);
84
- }
85
-
86
- const relatedTypes = findRelatedTypes(op, spec.types);
87
-
88
- out({
89
- name: op.name,
90
- kind: op.kind,
91
- description: op.description,
92
- returnType: op.returnType,
93
- isDeprecated: op.isDeprecated,
94
- args: op.args?.map((a) => ({
95
- name: a.name,
96
- type: flattenType(a.type),
97
- required: a.type?.kind === "NON_NULL",
98
- description: a.description || undefined,
99
- defaultValue: a.defaultValue || undefined,
100
- })),
101
- relatedTypes,
102
- });
103
- }
104
-
105
- // --- Helpers ---
106
-
107
- function resolveRef(obj, root) {
108
- if (!obj || typeof obj !== "object") return obj;
109
- if (obj.$ref) {
110
- const path = obj.$ref.replace("#/", "").split("/");
111
- let resolved = root;
112
- for (const p of path) resolved = resolved?.[p];
113
- return resolved || obj;
114
- }
115
- return obj;
116
- }
117
-
118
- function resolveRequestBody(body, root) {
119
- if (!body) return null;
120
- const resolved = resolveRef(body, root);
121
- if (resolved?.content) {
122
- const jsonContent = resolved.content["application/json"];
123
- if (jsonContent) {
124
- return {
125
- description: resolved.description || undefined,
126
- required: resolved.required || undefined,
127
- schema: resolveSchema(jsonContent.schema, root),
128
- };
129
- }
130
- const [mediaType, value] = Object.entries(resolved.content)[0];
131
- return {
132
- description: resolved.description || undefined,
133
- required: resolved.required || undefined,
134
- mediaType,
135
- schema: resolveSchema(value.schema, root),
136
- };
137
- }
138
- return resolved;
139
- }
140
-
141
- function resolveResponsesCompact(responses, root) {
142
- if (!responses) return null;
143
- const result = {};
144
- for (const [code, resp] of Object.entries(responses)) {
145
- const resolved = resolveRef(resp, root);
146
- if (resolved?.content) {
147
- const jsonContent = resolved.content["application/json"];
148
- result[code] = jsonContent
149
- ? { description: resolved.description, schema: resolveSchema(jsonContent.schema, root) }
150
- : { description: resolved.description };
151
- } else {
152
- result[code] = { description: resolved.description };
153
- }
154
- }
155
- return result;
156
- }
157
-
158
- function resolveSchema(schema, root, depth = 0) {
159
- if (!schema || depth > 3) return schema;
160
- if (schema.$ref) return resolveSchema(resolveRef(schema, root), root, depth + 1);
161
- if (schema.properties) {
162
- const result = { type: schema.type, required: schema.required, properties: {} };
163
- for (const [key, val] of Object.entries(schema.properties)) {
164
- if (val.$ref) {
165
- result.properties[key] = { $ref: val.$ref.split("/").pop() };
166
- } else if (val.type === "array" && val.items?.$ref) {
167
- result.properties[key] = { type: "array", items: val.items.$ref.split("/").pop() };
168
- } else {
169
- const prop = { type: val.type };
170
- if (val.format) prop.format = val.format;
171
- if (val.enum) prop.enum = val.enum;
172
- if (val.description) prop.description = val.description;
173
- result.properties[key] = prop;
174
- }
175
- }
176
- return result;
177
- }
178
- if (schema.items) {
179
- if (schema.items.$ref) return { type: "array", items: schema.items.$ref.split("/").pop() };
180
- return { type: "array", items: resolveSchema(schema.items, root, depth + 1) };
181
- }
182
- return schema;
183
- }
184
-
185
- function findRelatedTypes(op, types) {
186
- const names = new Set();
187
-
188
- function extractTypeNames(typeStr) {
189
- if (!typeStr) return;
190
- const cleaned = typeStr.replace(/[[\]!]/g, "");
191
- if (cleaned) names.add(cleaned);
192
- }
193
-
194
- extractTypeNames(op.returnType);
195
- for (const arg of op.args || []) extractTypeNames(flattenType(arg.type));
196
-
197
- const scalars = new Set(["String", "Int", "Float", "Boolean", "ID"]);
198
- return types
199
- .filter((t) => names.has(t.name) && !scalars.has(t.name))
200
- .map((t) => {
201
- const result = { name: t.name, kind: t.kind };
202
- if (t.fields)
203
- result.fields = t.fields.map((f) => ({ name: f.name, type: flattenType(f.type) }));
204
- if (t.inputFields)
205
- result.inputFields = t.inputFields.map((f) => ({
206
- name: f.name,
207
- type: flattenType(f.type),
208
- }));
209
- if (t.enumValues) result.enumValues = t.enumValues.map((e) => e.name);
210
- return result;
211
- });
212
- }
213
-
214
- function flattenType(t) {
215
- if (!t) return null;
216
- if (t.name) return t.kind === "NON_NULL" ? `${t.name}!` : t.name;
217
- if (t.ofType) {
218
- const inner = flattenType(t.ofType);
219
- if (t.kind === "LIST") return `[${inner}]`;
220
- if (t.kind === "NON_NULL") return `${inner}!`;
221
- return inner;
222
- }
223
- return t.kind;
224
- }
1
+ import { out } from "../output.js";
2
+ import { parseArgs } from "../args.js";
3
+ import { resolveSpec } from "../resolve.js";
4
+
5
+ export async function showOperation(args) {
6
+ const { flags, positional } = parseArgs(args);
7
+ const target = positional[0];
8
+ if (!target)
9
+ throw new Error(
10
+ "Usage: spec show <operationId-or-path> [--spec <name> | --openapi <url> | ...]"
11
+ );
12
+
13
+ const { spec } = await resolveSpec(flags);
14
+
15
+ if (spec.type === "openapi") {
16
+ showOpenAPI(spec, target);
17
+ } else if (spec.type === "mcp") {
18
+ showMCP(spec, target);
19
+ } else {
20
+ showGraphQL(spec, target);
21
+ }
22
+ }
23
+
24
+ function showOpenAPI(spec, target) {
25
+ const lower = target.toLowerCase();
26
+
27
+ const op = spec.operations.find(
28
+ (o) =>
29
+ o.id.toLowerCase() === lower ||
30
+ o.path.toLowerCase() === lower ||
31
+ `${o.method.toLowerCase()} ${o.path.toLowerCase()}` === lower
32
+ );
33
+
34
+ if (!op) {
35
+ throw new Error(`Operation not found: ${target}. Run 'spec list' to see available operations.`);
36
+ }
37
+
38
+ const root = spec.raw || spec.components;
39
+
40
+ out({
41
+ id: op.id,
42
+ method: op.method,
43
+ path: op.path,
44
+ summary: op.summary,
45
+ description: op.description,
46
+ tags: op.tags,
47
+ deprecated: op.deprecated,
48
+ parameters: op.parameters.map((p) => {
49
+ const resolved = resolveRef(p, root);
50
+ return {
51
+ name: resolved.name,
52
+ in: resolved.in,
53
+ required: resolved.required || false,
54
+ type: resolved.schema?.type || null,
55
+ format: resolved.schema?.format || undefined,
56
+ description: resolved.description || undefined,
57
+ enum: resolved.schema?.enum || undefined,
58
+ };
59
+ }),
60
+ requestBody: op.requestBody ? resolveRequestBody(op.requestBody, root) : null,
61
+ responses: resolveResponsesCompact(op.responses, root),
62
+ });
63
+ }
64
+
65
+ function showMCP(spec, target) {
66
+ const tool = spec.tools.find((t) => t.name.toLowerCase() === target.toLowerCase());
67
+ if (!tool) {
68
+ throw new Error(`Tool not found: ${target}. Run 'spec list' to see available tools.`);
69
+ }
70
+ out({
71
+ name: tool.name,
72
+ description: tool.description,
73
+ inputSchema: tool.inputSchema,
74
+ });
75
+ }
76
+
77
+ function showGraphQL(spec, target) {
78
+ const lower = target.toLowerCase();
79
+
80
+ const op = spec.operations.find((o) => o.name.toLowerCase() === lower);
81
+
82
+ if (!op) {
83
+ throw new Error(`Operation not found: ${target}. Run 'spec list' to see available operations.`);
84
+ }
85
+
86
+ const relatedTypes = findRelatedTypes(op, spec.types);
87
+
88
+ out({
89
+ name: op.name,
90
+ kind: op.kind,
91
+ description: op.description,
92
+ returnType: op.returnType,
93
+ isDeprecated: op.isDeprecated,
94
+ args: op.args?.map((a) => ({
95
+ name: a.name,
96
+ type: flattenType(a.type),
97
+ required: a.type?.kind === "NON_NULL",
98
+ description: a.description || undefined,
99
+ defaultValue: a.defaultValue || undefined,
100
+ })),
101
+ relatedTypes,
102
+ });
103
+ }
104
+
105
+ // --- Helpers ---
106
+
107
+ function resolveRef(obj, root) {
108
+ if (!obj || typeof obj !== "object") return obj;
109
+ if (obj.$ref) {
110
+ const path = obj.$ref.replace("#/", "").split("/");
111
+ let resolved = root;
112
+ for (const p of path) resolved = resolved?.[p];
113
+ return resolved || obj;
114
+ }
115
+ return obj;
116
+ }
117
+
118
+ function resolveRequestBody(body, root) {
119
+ if (!body) return null;
120
+ const resolved = resolveRef(body, root);
121
+ if (resolved?.content) {
122
+ const jsonContent = resolved.content["application/json"];
123
+ if (jsonContent) {
124
+ return {
125
+ description: resolved.description || undefined,
126
+ required: resolved.required || undefined,
127
+ schema: resolveSchema(jsonContent.schema, root),
128
+ };
129
+ }
130
+ const [mediaType, value] = Object.entries(resolved.content)[0];
131
+ return {
132
+ description: resolved.description || undefined,
133
+ required: resolved.required || undefined,
134
+ mediaType,
135
+ schema: resolveSchema(value.schema, root),
136
+ };
137
+ }
138
+ return resolved;
139
+ }
140
+
141
+ function resolveResponsesCompact(responses, root) {
142
+ if (!responses) return null;
143
+ const result = {};
144
+ for (const [code, resp] of Object.entries(responses)) {
145
+ const resolved = resolveRef(resp, root);
146
+ if (resolved?.content) {
147
+ const jsonContent = resolved.content["application/json"];
148
+ result[code] = jsonContent
149
+ ? { description: resolved.description, schema: resolveSchema(jsonContent.schema, root) }
150
+ : { description: resolved.description };
151
+ } else {
152
+ result[code] = { description: resolved.description };
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+
158
+ function resolveSchema(schema, root, depth = 0) {
159
+ if (!schema || depth > 3) return schema;
160
+ if (schema.$ref) return resolveSchema(resolveRef(schema, root), root, depth + 1);
161
+ if (schema.properties) {
162
+ const result = { type: schema.type, required: schema.required, properties: {} };
163
+ for (const [key, val] of Object.entries(schema.properties)) {
164
+ if (val.$ref) {
165
+ result.properties[key] = { $ref: val.$ref.split("/").pop() };
166
+ } else if (val.type === "array" && val.items?.$ref) {
167
+ result.properties[key] = { type: "array", items: val.items.$ref.split("/").pop() };
168
+ } else {
169
+ const prop = { type: val.type };
170
+ if (val.format) prop.format = val.format;
171
+ if (val.enum) prop.enum = val.enum;
172
+ if (val.description) prop.description = val.description;
173
+ result.properties[key] = prop;
174
+ }
175
+ }
176
+ return result;
177
+ }
178
+ if (schema.items) {
179
+ if (schema.items.$ref) return { type: "array", items: schema.items.$ref.split("/").pop() };
180
+ return { type: "array", items: resolveSchema(schema.items, root, depth + 1) };
181
+ }
182
+ return schema;
183
+ }
184
+
185
+ function findRelatedTypes(op, types) {
186
+ const names = new Set();
187
+
188
+ function extractTypeNames(typeStr) {
189
+ if (!typeStr) return;
190
+ const cleaned = typeStr.replace(/[[\]!]/g, "");
191
+ if (cleaned) names.add(cleaned);
192
+ }
193
+
194
+ extractTypeNames(op.returnType);
195
+ for (const arg of op.args || []) extractTypeNames(flattenType(arg.type));
196
+
197
+ const scalars = new Set(["String", "Int", "Float", "Boolean", "ID"]);
198
+ return types
199
+ .filter((t) => names.has(t.name) && !scalars.has(t.name))
200
+ .map((t) => {
201
+ const result = { name: t.name, kind: t.kind };
202
+ if (t.fields)
203
+ result.fields = t.fields.map((f) => ({ name: f.name, type: flattenType(f.type) }));
204
+ if (t.inputFields)
205
+ result.inputFields = t.inputFields.map((f) => ({
206
+ name: f.name,
207
+ type: flattenType(f.type),
208
+ }));
209
+ if (t.enumValues) result.enumValues = t.enumValues.map((e) => e.name);
210
+ return result;
211
+ });
212
+ }
213
+
214
+ function flattenType(t) {
215
+ if (!t) return null;
216
+ if (t.name) return t.kind === "NON_NULL" ? `${t.name}!` : t.name;
217
+ if (t.ofType) {
218
+ const inner = flattenType(t.ofType);
219
+ if (t.kind === "LIST") return `[${inner}]`;
220
+ if (t.kind === "NON_NULL") return `${inner}!`;
221
+ return inner;
222
+ }
223
+ return t.kind;
224
+ }
@@ -0,0 +1,40 @@
1
+ import { homedir } from "os";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { parseArgs } from "../args.js";
6
+ import { out } from "../output.js";
7
+
8
+ const SKILL_NAME = "api-spec-cli";
9
+ const BUNDLED = join(dirname(fileURLToPath(import.meta.url)), "..", "skill", "SKILL.md");
10
+
11
+ function installDir() {
12
+ return join(homedir(), ".claude", "skills", SKILL_NAME);
13
+ }
14
+
15
+ export async function skillCmd(args) {
16
+ const { flags, positional } = parseArgs(args);
17
+
18
+ if (positional[0] === "path" || "path" in flags) {
19
+ out({ skill: SKILL_NAME, path: BUNDLED });
20
+ return;
21
+ }
22
+
23
+ if (positional[0] === "install" || "install" in flags) {
24
+ if (!existsSync(BUNDLED)) throw new Error(`Bundled skill not found at ${BUNDLED}`);
25
+ const dest = join(installDir(), "SKILL.md");
26
+ mkdirSync(installDir(), { recursive: true });
27
+ writeFileSync(dest, readFileSync(BUNDLED, "utf-8"));
28
+ out({ installed: true, skill: SKILL_NAME, path: dest });
29
+ return;
30
+ }
31
+
32
+ out({
33
+ skill: SKILL_NAME,
34
+ usage: {
35
+ install: "spec skill install Copy the skill into ~/.claude/skills/",
36
+ path: "spec skill path Print the bundled SKILL.md location",
37
+ },
38
+ bundled: BUNDLED,
39
+ });
40
+ }