@panomc/client-gen 1.0.0 → 1.1.0-dev.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panomc/client-gen",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-dev.2",
4
4
  "description": "Generates a typed JavaScript client from a Pano OpenAPI document and scaffolds headless sites (pano-client).",
5
5
  "author": "PanoMC",
6
6
  "license": "MIT",
package/src/generate.js CHANGED
@@ -113,7 +113,7 @@ export function generateClient(input) {
113
113
  const t = /** @type {{ response: string, request: string | null }} */ (opTypes.get(op))
114
114
  const { type: paramsType, required } = paramsOf(op, t.request, importCtx)
115
115
  const lines = ['/**']
116
- const doc1 = oneLine(op.summary) || oneLine(op.description) || `${op.method} ${op.path}`
116
+ const doc1 = oneLine(op.summary) || oneLine(op.description) || oneLine(`${op.method} ${op.path}`)
117
117
  lines.push(` * ${doc1}`)
118
118
  if (op.summary && op.description && oneLine(op.description) !== oneLine(op.summary)) lines.push(` * ${oneLine(op.description)}`)
119
119
  if (op.deprecated) lines.push(' * @deprecated')
@@ -103,6 +103,39 @@ describe('generate: input errors', () => {
103
103
  expect(gen.files['operations.js']).toContain('deleteOperation:')
104
104
  expect(gen.warnings.some((w) => w.includes('unknown schema "Nope"'))).toBe(true)
105
105
  })
106
+
107
+ test('values from the document cannot close a JSDoc comment and add code', () => {
108
+ const evil = '*/ globalThis.PWNED=1; /*'
109
+ const gen = generateClient({
110
+ openapi: '3.1.0',
111
+ paths: {
112
+ [`/x/{id}/${evil}`]: {
113
+ get: {
114
+ operationId: 'getX',
115
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', enum: [evil] } }],
116
+ responses: {
117
+ 200: {
118
+ content: {
119
+ 'application/json': {
120
+ schema: { type: 'object', properties: { [evil]: { const: evil } }, required: [evil] }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ })
129
+ for (const [name, text] of Object.entries(gen.files)) {
130
+ if (!name.endsWith('.js')) continue
131
+ expect(text).not.toContain('*/ globalThis')
132
+ // every comment line between /** and */ starts with " *"
133
+ for (const block of text.matchAll(/\/\*\*\n([\s\S]*?)\n\s*\*\//g)) {
134
+ for (const line of block[1].split('\n')) expect(line.trimStart().startsWith('*')).toBe(true)
135
+ }
136
+ }
137
+ expect(gen.files['operations.js']).toContain("'/x/{id}/*\\/ globalThis.PWNED=1; /*'")
138
+ })
106
139
  })
107
140
 
108
141
  describe('generate: the output type checks', () => {
package/src/schema.js CHANGED
@@ -8,9 +8,21 @@ export const isIdent = (s) => IDENT.test(s)
8
8
  /** Property key as it appears in an object type literal. @param {string} s */
9
9
  export const propKey = (s) => (isIdent(s) ? s : quote(s))
10
10
 
11
- /** @param {string} s */
11
+ /**
12
+ * A single-quoted JS string literal. It also lands inside JSDoc comments, so a comment terminator is written
13
+ * as `*\/` (the same string value) and every kind of line break is escaped.
14
+ * @param {string} s
15
+ */
12
16
  export function quote(s) {
13
- return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\r?\n/g, '\\n')}'`
17
+ const body = s
18
+ .replace(/\\/g, '\\\\')
19
+ .replace(/'/g, "\\'")
20
+ .replace(/\r?\n/g, '\\n')
21
+ .replace(/\r/g, '\\r')
22
+ .replaceAll(String.fromCharCode(0x2028), '\\u2028')
23
+ .replaceAll(String.fromCharCode(0x2029), '\\u2029')
24
+ .replace(/\*\//g, '*\\/')
25
+ return `'${body}'`
14
26
  }
15
27
 
16
28
  /**