@sdk-it/core 0.21.0 → 0.22.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["\nexport * from './lib/deriver.js';\nexport * from './lib/paths.js';\nexport * from './lib/program.js';\nexport * from './lib/ref.js';\nexport * from './lib/utils.js';\n"],
5
- "mappings": "AACA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
4
+ "sourcesContent": ["export * from './lib/deriver.js';\nexport * from './lib/paths.js';\nexport * from './lib/program.js';\nexport * from './lib/ref.js';\nexport * from './lib/utils.js';\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
6
6
  "names": []
7
7
  }
@@ -9,6 +9,7 @@ export type ReadFolderFn = (folder: string) => Promise<{
9
9
  filePath: string;
10
10
  fileName: string;
11
11
  isFolder: boolean;
12
+ content?: string;
12
13
  }[]>;
13
14
  export type Writer = (dir: string, contents: WriteContent) => Promise<void>;
14
15
  export declare function writeFiles(dir: string, contents: WriteContent): Promise<void>;
@@ -33,4 +34,10 @@ export declare function getFolderExportsV2(folder: string, readFolder: ReadFolde
33
34
  export declare const getExt: (fileName?: string) => string;
34
35
  export declare function addLeadingSlash(path: string): string;
35
36
  export declare function removeTrialingSlashes(path: string, keepLastOne?: boolean): string;
37
+ export declare function isNullOrUndefined(value: any): value is undefined | null;
38
+ export declare function notNullOrUndefined<T>(value: T): value is Exclude<T, null | undefined>;
39
+ export declare function createWriterProxy(writer: Writer, output: string): {
40
+ writer: Writer;
41
+ files: Set<string>;
42
+ };
36
43
  //# sourceMappingURL=file-system.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-system.d.ts","sourceRoot":"","sources":["../../src/lib/file-system.ts"],"names":[],"mappings":"AAGA,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,0BAK7C;AAED,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAI1D;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,UAAQ,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAC/B,MAAM,EACN,IAAI,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,CAC9D,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,CAAC;AAC1E,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,iBAqBnE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,YAAY,EACxB,gBAAgB,UAAO,EACvB,UAAU,WAAS,EACnB,MAAM,GAAE,CAAC,MAAM,EAAE;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,KAAK,OAAqB,mBAwB5B;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,YAAY,EACxB,OAAO,GAAE;IACP,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC;KACnB,KAAK,OAAO,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CAMtB,mBA8BF;AAED,eAAO,MAAM,MAAM,GAAI,WAAW,MAAM,WAkBvC,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,UAE3C;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,UAAQ,UAKtE"}
1
+ {"version":3,"file":"file-system.d.ts","sourceRoot":"","sources":["../../src/lib/file-system.ts"],"names":[],"mappings":"AAUA,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,0BAK7C;AAED,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAI1D;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,UAAQ,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAC/B,MAAM,EACN,IAAI,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,CAC9D,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CACpD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,EAAE,CACJ,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,iBAqBnE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,YAAY,EACxB,gBAAgB,UAAO,EACvB,UAAU,WAAS,EACnB,MAAM,GAAE,CAAC,MAAM,EAAE;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,KAAK,OAAqB,mBA2B5B;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,YAAY,EACxB,OAAO,GAAE;IACP,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC;KACnB,KAAK,OAAO,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CAMtB,mBAkCF;AAED,eAAO,MAAM,MAAM,GAAI,WAAW,MAAM,WAkBvC,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,UAE3C;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,UAAQ,UAKtE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,SAAS,GAAG,IAAI,CAEvE;AACD,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,CAEvC;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAaxC"}
@@ -1,5 +1,12 @@
1
1
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
- import { dirname, extname, isAbsolute, join, normalize } from "node:path";
2
+ import {
3
+ dirname,
4
+ extname,
5
+ isAbsolute,
6
+ join,
7
+ normalize,
8
+ relative
9
+ } from "node:path";
3
10
  async function getFile(filePath) {
4
11
  if (await exist(filePath)) {
5
12
  return readFile(filePath, "utf-8");
@@ -59,7 +66,7 @@ async function getFolderExports(folder, readFolder2, includeExtension = true, ex
59
66
  continue;
60
67
  }
61
68
  if (file.isFolder) {
62
- if (await exist(`${file.filePath}/index.ts`)) {
69
+ if (await exist(`${file.filePath}/index.ts`) && await readFile(`${file.filePath}/index.ts`, "utf-8") !== "") {
63
70
  exports.push(
64
71
  `export * from './${file.fileName}/index${includeExtension ? ".ts" : ""}';`
65
72
  );
@@ -95,9 +102,11 @@ async function getFolderExportsV2(folder, readFolder2, options = {
95
102
  );
96
103
  }
97
104
  } else if (file.fileName !== `index.${options.extensions}` && options.extensions.includes(getExt(file.fileName))) {
98
- exports.push(
99
- `${options.exportSyntax} './${options.includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), "")}';`
100
- );
105
+ let name = options.includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), "");
106
+ if (name.startsWith("$")) {
107
+ name = `\\${name}`;
108
+ }
109
+ exports.push(`${options.exportSyntax} './${name}';`);
101
110
  }
102
111
  }
103
112
  return exports.join("\n");
@@ -125,13 +134,36 @@ function removeTrialingSlashes(path, keepLastOne = false) {
125
134
  }
126
135
  return path + (keepLastOne ? "/" : "");
127
136
  }
137
+ function isNullOrUndefined(value) {
138
+ return value === void 0 || value === null;
139
+ }
140
+ function notNullOrUndefined(value) {
141
+ return !isNullOrUndefined(value);
142
+ }
143
+ function createWriterProxy(writer, output) {
144
+ const writtenFiles = /* @__PURE__ */ new Set();
145
+ return {
146
+ files: writtenFiles,
147
+ writer: async (dir, contents) => {
148
+ await writer(dir, contents);
149
+ for (const file of Object.keys(contents)) {
150
+ if (contents[file] !== null) {
151
+ writtenFiles.add(addLeadingSlash(`${relative(output, dir)}/${file}`));
152
+ }
153
+ }
154
+ }
155
+ };
156
+ }
128
157
  export {
129
158
  addLeadingSlash,
159
+ createWriterProxy,
130
160
  exist,
131
161
  getExt,
132
162
  getFile,
133
163
  getFolderExports,
134
164
  getFolderExportsV2,
165
+ isNullOrUndefined,
166
+ notNullOrUndefined,
135
167
  readFolder,
136
168
  removeTrialingSlashes,
137
169
  writeFiles
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/file-system.ts"],
4
- "sourcesContent": ["import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport { dirname, extname, isAbsolute, join, normalize } from 'node:path';\n\nexport async function getFile(filePath: string) {\n if (await exist(filePath)) {\n return readFile(filePath, 'utf-8');\n }\n return null;\n}\n\nexport async function exist(file: string): Promise<boolean> {\n return stat(file)\n .then(() => true)\n .catch(() => false);\n}\n\nexport async function readFolder(\n path: string,\n recursive = false,\n): Promise<string[]> {\n if (!(await exist(path))) {\n return [];\n }\n const entries = await readdir(path, { withFileTypes: true });\n const results: string[] = [];\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (recursive) {\n const subFiles = await readFolder(join(path, entry.name), true);\n for (const sub of subFiles) {\n results.push(`${entry.name}/${sub}`);\n }\n }\n } else {\n results.push(entry.name);\n }\n }\n return results;\n}\n\nexport type WriteContent = Record<\n string,\n null | string | { content: string; ignoreIfExists?: boolean }\n>;\n\nexport type ReadFolderFn = (\n folder: string,\n) => Promise<{ filePath: string; fileName: string; isFolder: boolean }[]>;\nexport type Writer = (dir: string, contents: WriteContent) => Promise<void>;\n\nexport async function writeFiles(dir: string, contents: WriteContent) {\n await Promise.all(\n Object.entries(contents).map(async ([file, content]) => {\n if (content === null) {\n return;\n }\n const filePath = isAbsolute(file) ? file : join(dir, file);\n await mkdir(dirname(filePath), { recursive: true });\n if (typeof content === 'string') {\n await writeFile(filePath, content, 'utf-8');\n } else {\n if (content.ignoreIfExists) {\n if (!(await exist(filePath))) {\n await writeFile(filePath, content.content, 'utf-8');\n }\n } else {\n await writeFile(filePath, content.content, 'utf-8');\n }\n }\n }),\n );\n}\n\n/**\n * @deprecated use getFolderExportsV2 instead\n */\nexport async function getFolderExports(\n folder: string,\n readFolder: ReadFolderFn,\n includeExtension = true,\n extensions = ['ts'],\n ignore: (config: {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n }) => boolean = () => false,\n) {\n const files = await readFolder(folder);\n const exports: string[] = [];\n for (const file of files) {\n if (ignore(file)) {\n continue;\n }\n if (file.isFolder) {\n if (await exist(`${file.filePath}/index.ts`)) {\n exports.push(\n `export * from './${file.fileName}/index${includeExtension ? '.ts' : ''}';`,\n );\n }\n } else if (\n file.fileName !== 'index.ts' &&\n extensions.includes(getExt(file.fileName))\n ) {\n exports.push(\n `export * from './${includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), '')}';`,\n );\n }\n }\n return exports.join('\\n');\n}\n\nexport async function getFolderExportsV2(\n folder: string,\n readFolder: ReadFolderFn,\n options: {\n includeExtension?: boolean;\n extensions: string;\n ignore?: (fileInfo: {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n }) => boolean;\n exportSyntax: string;\n } = {\n extensions: 'ts',\n ignore: () => false,\n includeExtension: true,\n exportSyntax: 'export * from ',\n },\n) {\n options.includeExtension ??= true;\n if (!(await exist(folder))) {\n return '';\n }\n const files = await readFolder(folder);\n const exports: string[] = [];\n for (const file of files) {\n if (options.ignore?.(file)) {\n continue;\n }\n if (file.isFolder) {\n if (await exist(`${file.filePath}/index.${options.extensions}`)) {\n exports.push(\n `${options.exportSyntax} './${file.fileName}/index${\n options.includeExtension ? `.${options.extensions}` : ''\n }';`,\n );\n }\n } else if (\n file.fileName !== `index.${options.extensions}` &&\n options.extensions.includes(getExt(file.fileName))\n ) {\n exports.push(\n `${options.exportSyntax} './${options.includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), '')}';`,\n );\n }\n }\n return exports.join('\\n');\n}\n\nexport const getExt = (fileName?: string) => {\n if (!fileName) {\n return ''; // shouldn't happen as there will always be a file name\n }\n const lastDot = fileName.lastIndexOf('.');\n if (lastDot === -1) {\n return '';\n }\n const ext = fileName\n .slice(lastDot + 1)\n .split('/')\n .filter(Boolean)\n .join('');\n if (ext === fileName) {\n // files that have no extension\n return '';\n }\n return ext || 'txt';\n};\n\nexport function addLeadingSlash(path: string) {\n return normalize(join('/', path));\n}\n\nexport function removeTrialingSlashes(path: string, keepLastOne = false) {\n while (path.endsWith('/')) {\n path = path.slice(0, -1);\n }\n return path + (keepLastOne ? '/' : '');\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport {\n dirname,\n extname,\n isAbsolute,\n join,\n normalize,\n relative,\n} from 'node:path';\n\nexport async function getFile(filePath: string) {\n if (await exist(filePath)) {\n return readFile(filePath, 'utf-8');\n }\n return null;\n}\n\nexport async function exist(file: string): Promise<boolean> {\n return stat(file)\n .then(() => true)\n .catch(() => false);\n}\n\nexport async function readFolder(\n path: string,\n recursive = false,\n): Promise<string[]> {\n if (!(await exist(path))) {\n return [];\n }\n const entries = await readdir(path, { withFileTypes: true });\n const results: string[] = [];\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (recursive) {\n const subFiles = await readFolder(join(path, entry.name), true);\n for (const sub of subFiles) {\n results.push(`${entry.name}/${sub}`);\n }\n }\n } else {\n results.push(entry.name);\n }\n }\n return results;\n}\n\nexport type WriteContent = Record<\n string,\n null | string | { content: string; ignoreIfExists?: boolean }\n>;\n\nexport type ReadFolderFn = (folder: string) => Promise<\n {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n content?: string;\n }[]\n>;\nexport type Writer = (dir: string, contents: WriteContent) => Promise<void>;\n\nexport async function writeFiles(dir: string, contents: WriteContent) {\n await Promise.all(\n Object.entries(contents).map(async ([file, content]) => {\n if (content === null) {\n return;\n }\n const filePath = isAbsolute(file) ? file : join(dir, file);\n await mkdir(dirname(filePath), { recursive: true });\n if (typeof content === 'string') {\n await writeFile(filePath, content, 'utf-8');\n } else {\n if (content.ignoreIfExists) {\n if (!(await exist(filePath))) {\n await writeFile(filePath, content.content, 'utf-8');\n }\n } else {\n await writeFile(filePath, content.content, 'utf-8');\n }\n }\n }),\n );\n}\n\n/**\n * @deprecated use getFolderExportsV2 instead\n */\nexport async function getFolderExports(\n folder: string,\n readFolder: ReadFolderFn,\n includeExtension = true,\n extensions = ['ts'],\n ignore: (config: {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n }) => boolean = () => false,\n) {\n const files = await readFolder(folder);\n const exports: string[] = [];\n for (const file of files) {\n if (ignore(file)) {\n continue;\n }\n if (file.isFolder) {\n if (\n (await exist(`${file.filePath}/index.ts`)) &&\n (await readFile(`${file.filePath}/index.ts`, 'utf-8')) !== ''\n ) {\n exports.push(\n `export * from './${file.fileName}/index${includeExtension ? '.ts' : ''}';`,\n );\n }\n } else if (\n file.fileName !== 'index.ts' &&\n extensions.includes(getExt(file.fileName))\n ) {\n exports.push(\n `export * from './${includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), '')}';`,\n );\n }\n }\n return exports.join('\\n');\n}\n\nexport async function getFolderExportsV2(\n folder: string,\n readFolder: ReadFolderFn,\n options: {\n includeExtension?: boolean;\n extensions: string;\n ignore?: (fileInfo: {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n }) => boolean;\n exportSyntax: string;\n } = {\n extensions: 'ts',\n ignore: () => false,\n includeExtension: true,\n exportSyntax: 'export * from ',\n },\n) {\n options.includeExtension ??= true;\n if (!(await exist(folder))) {\n return '';\n }\n const files = await readFolder(folder);\n const exports: string[] = [];\n for (const file of files) {\n if (options.ignore?.(file)) {\n continue;\n }\n if (file.isFolder) {\n if (await exist(`${file.filePath}/index.${options.extensions}`)) {\n exports.push(\n `${options.exportSyntax} './${file.fileName}/index${\n options.includeExtension ? `.${options.extensions}` : ''\n }';`,\n );\n }\n } else if (\n file.fileName !== `index.${options.extensions}` &&\n options.extensions.includes(getExt(file.fileName))\n ) {\n let name = options.includeExtension\n ? file.fileName\n : file.fileName.replace(extname(file.fileName), '');\n if (name.startsWith('$')) {\n name = `\\\\${name}`\n }\n exports.push(`${options.exportSyntax} './${name}';`);\n }\n }\n return exports.join('\\n');\n}\n\nexport const getExt = (fileName?: string) => {\n if (!fileName) {\n return ''; // shouldn't happen as there will always be a file name\n }\n const lastDot = fileName.lastIndexOf('.');\n if (lastDot === -1) {\n return '';\n }\n const ext = fileName\n .slice(lastDot + 1)\n .split('/')\n .filter(Boolean)\n .join('');\n if (ext === fileName) {\n // files that have no extension\n return '';\n }\n return ext || 'txt';\n};\n\nexport function addLeadingSlash(path: string) {\n return normalize(join('/', path));\n}\n\nexport function removeTrialingSlashes(path: string, keepLastOne = false) {\n while (path.endsWith('/')) {\n path = path.slice(0, -1);\n }\n return path + (keepLastOne ? '/' : '');\n}\n\nexport function isNullOrUndefined(value: any): value is undefined | null {\n return value === undefined || value === null;\n}\nexport function notNullOrUndefined<T>(\n value: T,\n): value is Exclude<T, null | undefined> {\n return !isNullOrUndefined(value);\n}\n\nexport function createWriterProxy(\n writer: Writer,\n output: string,\n): { writer: Writer; files: Set<string> } {\n const writtenFiles = new Set<string>();\n return {\n files: writtenFiles,\n writer: async (dir: string, contents: WriteContent) => {\n await writer(dir, contents);\n for (const file of Object.keys(contents)) {\n if (contents[file] !== null) {\n writtenFiles.add(addLeadingSlash(`${relative(output, dir)}/${file}`));\n }\n }\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["readFolder"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=file-system.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-system.test.d.ts","sourceRoot":"","sources":["../../src/lib/file-system.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,357 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { after, before, describe, it } from "node:test";
6
+ import {
7
+ addLeadingSlash,
8
+ exist,
9
+ getExt,
10
+ getFile,
11
+ getFolderExports,
12
+ isNullOrUndefined,
13
+ readFolder,
14
+ removeTrialingSlashes,
15
+ writeFiles
16
+ } from "./file-system.ts";
17
+ describe("File System Utilities - Error-First Testing", () => {
18
+ let testDir;
19
+ let nonExistentPath;
20
+ before(async () => {
21
+ testDir = join(tmpdir(), `fs-test-${Date.now()}`);
22
+ await mkdir(testDir, { recursive: true });
23
+ nonExistentPath = join(testDir, "non-existent-directory", "deep", "path");
24
+ });
25
+ after(async () => {
26
+ const { rm } = await import("node:fs/promises");
27
+ await rm(testDir, { recursive: true, force: true });
28
+ });
29
+ describe("getFile - Attack Phase: Invalid Inputs & File System Errors", () => {
30
+ it("should return null for non-existent file", async () => {
31
+ const result = await getFile(join(testDir, "does-not-exist.txt"));
32
+ assert.strictEqual(result, null);
33
+ });
34
+ it("should return null for empty string path", async () => {
35
+ const result = await getFile("");
36
+ assert.strictEqual(result, null);
37
+ });
38
+ it("should throw error when trying to read a directory path instead of file", async () => {
39
+ const dirPath = join(testDir, "test-dir-for-getfile");
40
+ await mkdir(dirPath);
41
+ await assert.rejects(() => getFile(dirPath), {
42
+ code: "EISDIR"
43
+ });
44
+ });
45
+ it("should handle permission denied scenarios gracefully", async () => {
46
+ const result = await getFile("/root/protected-file.txt");
47
+ assert.strictEqual(result, null);
48
+ });
49
+ it("should handle malformed paths", async () => {
50
+ const malformedPaths = [
51
+ "\0invalid",
52
+ "file\nwith\nnewlines",
53
+ "file with tabs"
54
+ ];
55
+ for (const path of malformedPaths) {
56
+ const result = await getFile(path);
57
+ assert.strictEqual(result, null, `Failed for path: ${path}`);
58
+ }
59
+ });
60
+ });
61
+ describe("exist - Attack Phase: Edge Cases & Invalid Paths", () => {
62
+ it("should return false for non-existent paths", async () => {
63
+ assert.strictEqual(await exist(nonExistentPath), false);
64
+ });
65
+ it("should return false for empty string", async () => {
66
+ assert.strictEqual(await exist(""), false);
67
+ });
68
+ it("should handle null bytes in path gracefully", async () => {
69
+ assert.strictEqual(await exist("path\0with\0nulls"), false);
70
+ });
71
+ it("should handle extremely long paths", async () => {
72
+ const longPath = "a".repeat(1e4);
73
+ assert.strictEqual(await exist(longPath), false);
74
+ });
75
+ });
76
+ describe("readFolder - Attack Phase: Directory Corruption & Invalid States", () => {
77
+ it("should return empty array for non-existent directory", async () => {
78
+ const result = await readFolder(nonExistentPath);
79
+ assert.deepStrictEqual(result, []);
80
+ });
81
+ it("should throw error for file path instead of directory", async () => {
82
+ const filePath = join(testDir, "test-file-for-readfolder.txt");
83
+ await writeFile(filePath, "content");
84
+ await assert.rejects(() => readFolder(filePath), {
85
+ code: "ENOTDIR"
86
+ });
87
+ });
88
+ it("should handle permission denied on directory", async () => {
89
+ const result = await readFolder("/root");
90
+ assert.deepStrictEqual(result, []);
91
+ });
92
+ it("should handle recursive reading with circular symlinks gracefully", async () => {
93
+ const result = await readFolder(testDir, true);
94
+ assert.ok(Array.isArray(result));
95
+ });
96
+ });
97
+ describe("writeFiles - Attack Phase: Write Failures & State Corruption", () => {
98
+ it("should handle null content gracefully", async () => {
99
+ const content = {
100
+ "test.txt": null,
101
+ "valid.txt": "content"
102
+ };
103
+ await writeFiles(testDir, content);
104
+ assert.strictEqual(await exist(join(testDir, "test.txt")), false);
105
+ assert.strictEqual(await exist(join(testDir, "valid.txt")), true);
106
+ });
107
+ it("should fail gracefully when writing to read-only directory", async () => {
108
+ const readOnlyPath = "/read-only-system-path";
109
+ const content = {
110
+ "test.txt": "content"
111
+ };
112
+ try {
113
+ await writeFiles(readOnlyPath, content);
114
+ assert.fail("Should have thrown an error for read-only directory");
115
+ } catch (error) {
116
+ assert.ok(error instanceof Error);
117
+ }
118
+ });
119
+ it("should handle ignoreIfExists flag correctly when file exists", async () => {
120
+ const filePath = join(testDir, "existing.txt");
121
+ await writeFile(filePath, "original");
122
+ const content = {
123
+ "existing.txt": {
124
+ content: "should not overwrite",
125
+ ignoreIfExists: true
126
+ }
127
+ };
128
+ await writeFiles(testDir, content);
129
+ const result = await getFile(filePath);
130
+ assert.strictEqual(result, "original");
131
+ });
132
+ it("should create deeply nested directories", async () => {
133
+ const content = {
134
+ "deep/nested/structure/file.txt": "content"
135
+ };
136
+ await writeFiles(testDir, content);
137
+ const result = await getFile(
138
+ join(testDir, "deep/nested/structure/file.txt")
139
+ );
140
+ assert.strictEqual(result, "content");
141
+ });
142
+ });
143
+ describe("getFolderExports - Attack Phase: Invalid Folder States", () => {
144
+ const mockReadFolder = async (folder) => {
145
+ if (folder === nonExistentPath) {
146
+ return [];
147
+ }
148
+ return [
149
+ {
150
+ filePath: join(folder, "valid.ts"),
151
+ fileName: "valid.ts",
152
+ isFolder: false
153
+ },
154
+ {
155
+ filePath: join(folder, "index.ts"),
156
+ fileName: "index.ts",
157
+ isFolder: false
158
+ },
159
+ {
160
+ filePath: join(folder, "subfolder"),
161
+ fileName: "subfolder",
162
+ isFolder: true
163
+ }
164
+ ];
165
+ };
166
+ it("should handle non-existent folder gracefully", async () => {
167
+ const result = await getFolderExports(nonExistentPath, mockReadFolder);
168
+ assert.strictEqual(result, "");
169
+ });
170
+ it("should ignore files with unsupported extensions", async () => {
171
+ const mockReadFolderWithJs = async () => [
172
+ { filePath: "test.js", fileName: "test.js", isFolder: false },
173
+ { filePath: "test.ts", fileName: "test.ts", isFolder: false }
174
+ ];
175
+ const result = await getFolderExports(
176
+ testDir,
177
+ mockReadFolderWithJs,
178
+ true,
179
+ ["ts"]
180
+ );
181
+ assert.ok(!result.includes("test.js"));
182
+ assert.ok(result.includes("test.ts"));
183
+ });
184
+ });
185
+ describe("getExt - Attack Phase: Malformed File Names", () => {
186
+ it("should handle undefined filename", () => {
187
+ assert.strictEqual(getExt(void 0), "");
188
+ });
189
+ it("should handle empty string filename", () => {
190
+ assert.strictEqual(getExt(""), "");
191
+ });
192
+ it("should handle filename with no extension", () => {
193
+ assert.strictEqual(getExt("filename"), "");
194
+ });
195
+ it("should handle filename with multiple dots", () => {
196
+ assert.strictEqual(getExt("file.name.with.multiple.dots.ts"), "ts");
197
+ });
198
+ it("should handle filename starting with dot", () => {
199
+ assert.strictEqual(getExt(".hidden"), "hidden");
200
+ });
201
+ it("should handle filename with only dots", () => {
202
+ assert.strictEqual(getExt("..."), "txt");
203
+ });
204
+ it("should handle filename with path separators", () => {
205
+ assert.strictEqual(getExt("path/to/file.txt"), "txt");
206
+ });
207
+ });
208
+ describe("Path Utilities - Attack Phase: Malformed Paths", () => {
209
+ describe("addLeadingSlash", () => {
210
+ it("should handle empty string", () => {
211
+ assert.strictEqual(addLeadingSlash(""), "/");
212
+ });
213
+ it("should handle path with multiple leading slashes", () => {
214
+ assert.strictEqual(addLeadingSlash("///path"), "/path");
215
+ });
216
+ it("should handle path with backslashes", () => {
217
+ const result = addLeadingSlash("path\\to\\file");
218
+ assert.ok(result.startsWith("/"));
219
+ });
220
+ });
221
+ describe("removeTrialingSlashes", () => {
222
+ it("should handle empty string", () => {
223
+ assert.strictEqual(removeTrialingSlashes(""), "");
224
+ });
225
+ it("should handle string with only slashes", () => {
226
+ assert.strictEqual(removeTrialingSlashes("///"), "");
227
+ });
228
+ it("should preserve single slash when keepLastOne is true", () => {
229
+ assert.strictEqual(removeTrialingSlashes("path///", true), "path/");
230
+ });
231
+ });
232
+ });
233
+ describe("Type Guards - Attack Phase: Type Coercion Vulnerabilities", () => {
234
+ describe("isNullOrUndefined", () => {
235
+ it("should correctly identify falsy values that are not null/undefined", () => {
236
+ assert.strictEqual(isNullOrUndefined(0), false);
237
+ assert.strictEqual(isNullOrUndefined(""), false);
238
+ assert.strictEqual(isNullOrUndefined(false), false);
239
+ assert.strictEqual(isNullOrUndefined(NaN), false);
240
+ });
241
+ it("should correctly identify null and undefined", () => {
242
+ assert.strictEqual(isNullOrUndefined(null), true);
243
+ assert.strictEqual(isNullOrUndefined(void 0), true);
244
+ });
245
+ });
246
+ describe("type safety verification", () => {
247
+ it("should handle type coercion edge cases", () => {
248
+ const value = null;
249
+ if (isNullOrUndefined(value)) {
250
+ assert.ok(value === null || value === void 0);
251
+ }
252
+ });
253
+ });
254
+ });
255
+ describe("State Transition Testing - File System Operations", () => {
256
+ it("should maintain file system consistency during complex operations", async () => {
257
+ const complexContent = {
258
+ "folder1/file1.txt": "content1",
259
+ "folder1/file2.txt": null,
260
+ // Should not be created
261
+ "folder2/subfolder/file3.txt": {
262
+ content: "content3",
263
+ ignoreIfExists: false
264
+ },
265
+ "folder2/existing.txt": {
266
+ content: "new content",
267
+ ignoreIfExists: true
268
+ }
269
+ };
270
+ await mkdir(join(testDir, "folder2"), { recursive: true });
271
+ await writeFile(join(testDir, "folder2/existing.txt"), "original");
272
+ await writeFiles(testDir, complexContent);
273
+ assert.strictEqual(
274
+ await getFile(join(testDir, "folder1/file1.txt")),
275
+ "content1"
276
+ );
277
+ assert.strictEqual(
278
+ await exist(join(testDir, "folder1/file2.txt")),
279
+ false
280
+ );
281
+ assert.strictEqual(
282
+ await getFile(join(testDir, "folder2/subfolder/file3.txt")),
283
+ "content3"
284
+ );
285
+ assert.strictEqual(
286
+ await getFile(join(testDir, "folder2/existing.txt")),
287
+ "original"
288
+ );
289
+ });
290
+ it("should maintain directory structure consistency during recursive operations", async () => {
291
+ const nestedDir = join(testDir, "nested/deep/structure");
292
+ await mkdir(nestedDir, { recursive: true });
293
+ await writeFile(join(nestedDir, "file.txt"), "content");
294
+ await writeFile(join(testDir, "nested/file2.txt"), "content2");
295
+ const files = await readFolder(join(testDir, "nested"), true);
296
+ assert.ok(files.includes("deep/structure/file.txt"));
297
+ assert.ok(files.includes("file2.txt"));
298
+ assert.strictEqual(files.filter((f) => f.startsWith("deep/")).length, 1);
299
+ });
300
+ });
301
+ describe("Happy Path - Basic Functionality Confirmation", () => {
302
+ it("should successfully read existing file", async () => {
303
+ const filePath = join(testDir, "test-read.txt");
304
+ const content = "test content";
305
+ await writeFile(filePath, content);
306
+ const result = await getFile(filePath);
307
+ assert.strictEqual(result, content);
308
+ });
309
+ it("should correctly identify existing files and directories", async () => {
310
+ const filePath = join(testDir, "test-exist-unique.txt");
311
+ const dirPath = join(testDir, "test-dir-unique");
312
+ await writeFile(filePath, "content");
313
+ await mkdir(dirPath);
314
+ assert.strictEqual(await exist(filePath), true);
315
+ assert.strictEqual(await exist(dirPath), true);
316
+ });
317
+ it("should read folder contents correctly", async () => {
318
+ const folderPath = join(testDir, "read-test");
319
+ await mkdir(folderPath);
320
+ await writeFile(join(folderPath, "file1.txt"), "content1");
321
+ await writeFile(join(folderPath, "file2.txt"), "content2");
322
+ const files = await readFolder(folderPath);
323
+ assert.deepStrictEqual(files.sort(), ["file1.txt", "file2.txt"]);
324
+ });
325
+ it("should write files correctly", async () => {
326
+ const content = {
327
+ "simple.txt": "simple content",
328
+ "complex.txt": {
329
+ content: "complex content",
330
+ ignoreIfExists: false
331
+ }
332
+ };
333
+ await writeFiles(testDir, content);
334
+ assert.strictEqual(
335
+ await getFile(join(testDir, "simple.txt")),
336
+ "simple content"
337
+ );
338
+ assert.strictEqual(
339
+ await getFile(join(testDir, "complex.txt")),
340
+ "complex content"
341
+ );
342
+ });
343
+ it("should extract file extensions correctly", () => {
344
+ assert.strictEqual(getExt("file.txt"), "txt");
345
+ assert.strictEqual(getExt("script.js"), "js");
346
+ assert.strictEqual(getExt("component.tsx"), "tsx");
347
+ });
348
+ it("should format paths correctly", () => {
349
+ assert.strictEqual(addLeadingSlash("path/to/file"), "/path/to/file");
350
+ assert.strictEqual(
351
+ removeTrialingSlashes("path/to/file///"),
352
+ "path/to/file"
353
+ );
354
+ });
355
+ });
356
+ });
357
+ //# sourceMappingURL=file-system.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/file-system.test.ts"],
4
+ "sourcesContent": ["import assert from 'node:assert/strict';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { after, before, describe, it } from 'node:test';\n\nimport {\n type WriteContent,\n addLeadingSlash,\n exist,\n getExt,\n getFile,\n getFolderExports,\n isNullOrUndefined,\n readFolder,\n removeTrialingSlashes,\n writeFiles,\n} from './file-system.ts';\n\ndescribe('File System Utilities - Error-First Testing', () => {\n let testDir: string;\n let nonExistentPath: string;\n\n before(async () => {\n testDir = join(tmpdir(), `fs-test-${Date.now()}`);\n await mkdir(testDir, { recursive: true });\n nonExistentPath = join(testDir, 'non-existent-directory', 'deep', 'path');\n });\n\n after(async () => {\n const { rm } = await import('node:fs/promises');\n await rm(testDir, { recursive: true, force: true });\n });\n\n describe('getFile - Attack Phase: Invalid Inputs & File System Errors', () => {\n it('should return null for non-existent file', async () => {\n const result = await getFile(join(testDir, 'does-not-exist.txt'));\n assert.strictEqual(result, null);\n });\n\n it('should return null for empty string path', async () => {\n const result = await getFile('');\n assert.strictEqual(result, null);\n });\n\n it('should throw error when trying to read a directory path instead of file', async () => {\n const dirPath = join(testDir, 'test-dir-for-getfile');\n await mkdir(dirPath);\n\n await assert.rejects(() => getFile(dirPath), {\n code: 'EISDIR',\n });\n });\n\n it('should handle permission denied scenarios gracefully', async () => {\n // Test with invalid path that would cause permission errors\n const result = await getFile('/root/protected-file.txt');\n assert.strictEqual(result, null);\n });\n\n it('should handle malformed paths', async () => {\n const malformedPaths = [\n '\\0invalid',\n 'file\\nwith\\nnewlines',\n 'file\\twith\\ttabs',\n ];\n\n for (const path of malformedPaths) {\n const result = await getFile(path);\n assert.strictEqual(result, null, `Failed for path: ${path}`);\n }\n });\n });\n\n describe('exist - Attack Phase: Edge Cases & Invalid Paths', () => {\n it('should return false for non-existent paths', async () => {\n assert.strictEqual(await exist(nonExistentPath), false);\n });\n\n it('should return false for empty string', async () => {\n assert.strictEqual(await exist(''), false);\n });\n\n it('should handle null bytes in path gracefully', async () => {\n assert.strictEqual(await exist('path\\0with\\0nulls'), false);\n });\n\n it('should handle extremely long paths', async () => {\n const longPath = 'a'.repeat(10000);\n assert.strictEqual(await exist(longPath), false);\n });\n });\n\n describe('readFolder - Attack Phase: Directory Corruption & Invalid States', () => {\n it('should return empty array for non-existent directory', async () => {\n const result = await readFolder(nonExistentPath);\n assert.deepStrictEqual(result, []);\n });\n\n it('should throw error for file path instead of directory', async () => {\n const filePath = join(testDir, 'test-file-for-readfolder.txt');\n await writeFile(filePath, 'content');\n\n // readFolder should throw an error when given a file path\n await assert.rejects(() => readFolder(filePath), {\n code: 'ENOTDIR',\n });\n });\n\n it('should handle permission denied on directory', async () => {\n const result = await readFolder('/root');\n assert.deepStrictEqual(result, []);\n });\n\n it('should handle recursive reading with circular symlinks gracefully', async () => {\n // This tests the function's resilience to infinite loops\n const result = await readFolder(testDir, true);\n assert.ok(Array.isArray(result));\n });\n });\n\n describe('writeFiles - Attack Phase: Write Failures & State Corruption', () => {\n it('should handle null content gracefully', async () => {\n const content: WriteContent = {\n 'test.txt': null,\n 'valid.txt': 'content',\n };\n\n await writeFiles(testDir, content);\n\n // Verify null content file was not created\n assert.strictEqual(await exist(join(testDir, 'test.txt')), false);\n // Verify valid file was created\n assert.strictEqual(await exist(join(testDir, 'valid.txt')), true);\n });\n\n it('should fail gracefully when writing to read-only directory', async () => {\n const readOnlyPath = '/read-only-system-path';\n const content: WriteContent = {\n 'test.txt': 'content',\n };\n\n try {\n await writeFiles(readOnlyPath, content);\n assert.fail('Should have thrown an error for read-only directory');\n } catch (error) {\n assert.ok(error instanceof Error);\n }\n });\n\n it('should handle ignoreIfExists flag correctly when file exists', async () => {\n const filePath = join(testDir, 'existing.txt');\n await writeFile(filePath, 'original');\n\n const content: WriteContent = {\n 'existing.txt': {\n content: 'should not overwrite',\n ignoreIfExists: true,\n },\n };\n\n await writeFiles(testDir, content);\n const result = await getFile(filePath);\n assert.strictEqual(result, 'original');\n });\n\n it('should create deeply nested directories', async () => {\n const content: WriteContent = {\n 'deep/nested/structure/file.txt': 'content',\n };\n\n await writeFiles(testDir, content);\n const result = await getFile(\n join(testDir, 'deep/nested/structure/file.txt'),\n );\n assert.strictEqual(result, 'content');\n });\n });\n\n describe('getFolderExports - Attack Phase: Invalid Folder States', () => {\n const mockReadFolder = async (folder: string) => {\n if (folder === nonExistentPath) {\n return [];\n }\n return [\n {\n filePath: join(folder, 'valid.ts'),\n fileName: 'valid.ts',\n isFolder: false,\n },\n {\n filePath: join(folder, 'index.ts'),\n fileName: 'index.ts',\n isFolder: false,\n },\n {\n filePath: join(folder, 'subfolder'),\n fileName: 'subfolder',\n isFolder: true,\n },\n ];\n };\n\n it('should handle non-existent folder gracefully', async () => {\n const result = await getFolderExports(nonExistentPath, mockReadFolder);\n assert.strictEqual(result, '');\n });\n\n it('should ignore files with unsupported extensions', async () => {\n const mockReadFolderWithJs = async () => [\n { filePath: 'test.js', fileName: 'test.js', isFolder: false },\n { filePath: 'test.ts', fileName: 'test.ts', isFolder: false },\n ];\n\n const result = await getFolderExports(\n testDir,\n mockReadFolderWithJs,\n true,\n ['ts'],\n );\n assert.ok(!result.includes('test.js'));\n assert.ok(result.includes('test.ts'));\n });\n });\n\n describe('getExt - Attack Phase: Malformed File Names', () => {\n it('should handle undefined filename', () => {\n assert.strictEqual(getExt(undefined), '');\n });\n\n it('should handle empty string filename', () => {\n assert.strictEqual(getExt(''), '');\n });\n\n it('should handle filename with no extension', () => {\n assert.strictEqual(getExt('filename'), '');\n });\n\n it('should handle filename with multiple dots', () => {\n assert.strictEqual(getExt('file.name.with.multiple.dots.ts'), 'ts');\n });\n\n it('should handle filename starting with dot', () => {\n // Based on the actual implementation, '.hidden' should return 'hidden'\n assert.strictEqual(getExt('.hidden'), 'hidden');\n });\n\n it('should handle filename with only dots', () => {\n // Based on the actual implementation, '...' returns 'txt' as fallback\n assert.strictEqual(getExt('...'), 'txt');\n });\n\n it('should handle filename with path separators', () => {\n assert.strictEqual(getExt('path/to/file.txt'), 'txt');\n });\n });\n\n describe('Path Utilities - Attack Phase: Malformed Paths', () => {\n describe('addLeadingSlash', () => {\n it('should handle empty string', () => {\n assert.strictEqual(addLeadingSlash(''), '/');\n });\n\n it('should handle path with multiple leading slashes', () => {\n assert.strictEqual(addLeadingSlash('///path'), '/path');\n });\n\n it('should handle path with backslashes', () => {\n const result = addLeadingSlash('path\\\\to\\\\file');\n assert.ok(result.startsWith('/'));\n });\n });\n\n describe('removeTrialingSlashes', () => {\n it('should handle empty string', () => {\n assert.strictEqual(removeTrialingSlashes(''), '');\n });\n\n it('should handle string with only slashes', () => {\n assert.strictEqual(removeTrialingSlashes('///'), '');\n });\n\n it('should preserve single slash when keepLastOne is true', () => {\n assert.strictEqual(removeTrialingSlashes('path///', true), 'path/');\n });\n });\n });\n\n describe('Type Guards - Attack Phase: Type Coercion Vulnerabilities', () => {\n describe('isNullOrUndefined', () => {\n it('should correctly identify falsy values that are not null/undefined', () => {\n assert.strictEqual(isNullOrUndefined(0), false);\n assert.strictEqual(isNullOrUndefined(''), false);\n assert.strictEqual(isNullOrUndefined(false), false);\n assert.strictEqual(isNullOrUndefined(NaN), false);\n });\n\n it('should correctly identify null and undefined', () => {\n assert.strictEqual(isNullOrUndefined(null), true);\n assert.strictEqual(isNullOrUndefined(undefined), true);\n });\n });\n\n // Remove notNullOrUndefined tests since it doesn't exist in the source\n describe('type safety verification', () => {\n it('should handle type coercion edge cases', () => {\n // Test that isNullOrUndefined works as a proper type guard\n const value: unknown = null;\n if (isNullOrUndefined(value)) {\n // TypeScript should narrow the type here\n assert.ok(value === null || value === undefined);\n }\n });\n });\n });\n\n describe('State Transition Testing - File System Operations', () => {\n it('should maintain file system consistency during complex operations', async () => {\n const complexContent: WriteContent = {\n 'folder1/file1.txt': 'content1',\n 'folder1/file2.txt': null, // Should not be created\n 'folder2/subfolder/file3.txt': {\n content: 'content3',\n ignoreIfExists: false,\n },\n 'folder2/existing.txt': {\n content: 'new content',\n ignoreIfExists: true,\n },\n };\n\n // Pre-create existing file\n await mkdir(join(testDir, 'folder2'), { recursive: true });\n await writeFile(join(testDir, 'folder2/existing.txt'), 'original');\n\n await writeFiles(testDir, complexContent);\n\n // Verify final state\n assert.strictEqual(\n await getFile(join(testDir, 'folder1/file1.txt')),\n 'content1',\n );\n assert.strictEqual(\n await exist(join(testDir, 'folder1/file2.txt')),\n false,\n );\n assert.strictEqual(\n await getFile(join(testDir, 'folder2/subfolder/file3.txt')),\n 'content3',\n );\n assert.strictEqual(\n await getFile(join(testDir, 'folder2/existing.txt')),\n 'original',\n );\n });\n\n it('should maintain directory structure consistency during recursive operations', async () => {\n // Create nested structure\n const nestedDir = join(testDir, 'nested/deep/structure');\n await mkdir(nestedDir, { recursive: true });\n await writeFile(join(nestedDir, 'file.txt'), 'content');\n await writeFile(join(testDir, 'nested/file2.txt'), 'content2');\n\n const files = await readFolder(join(testDir, 'nested'), true);\n\n // Verify structure is maintained\n assert.ok(files.includes('deep/structure/file.txt'));\n assert.ok(files.includes('file2.txt'));\n assert.strictEqual(files.filter((f) => f.startsWith('deep/')).length, 1);\n });\n });\n\n describe('Happy Path - Basic Functionality Confirmation', () => {\n it('should successfully read existing file', async () => {\n const filePath = join(testDir, 'test-read.txt');\n const content = 'test content';\n await writeFile(filePath, content);\n\n const result = await getFile(filePath);\n assert.strictEqual(result, content);\n });\n\n it('should correctly identify existing files and directories', async () => {\n const filePath = join(testDir, 'test-exist-unique.txt');\n const dirPath = join(testDir, 'test-dir-unique');\n\n await writeFile(filePath, 'content');\n await mkdir(dirPath);\n\n assert.strictEqual(await exist(filePath), true);\n assert.strictEqual(await exist(dirPath), true);\n });\n\n it('should read folder contents correctly', async () => {\n const folderPath = join(testDir, 'read-test');\n await mkdir(folderPath);\n await writeFile(join(folderPath, 'file1.txt'), 'content1');\n await writeFile(join(folderPath, 'file2.txt'), 'content2');\n\n const files = await readFolder(folderPath);\n assert.deepStrictEqual(files.sort(), ['file1.txt', 'file2.txt']);\n });\n\n it('should write files correctly', async () => {\n const content: WriteContent = {\n 'simple.txt': 'simple content',\n 'complex.txt': {\n content: 'complex content',\n ignoreIfExists: false,\n },\n };\n\n await writeFiles(testDir, content);\n\n assert.strictEqual(\n await getFile(join(testDir, 'simple.txt')),\n 'simple content',\n );\n assert.strictEqual(\n await getFile(join(testDir, 'complex.txt')),\n 'complex content',\n );\n });\n\n it('should extract file extensions correctly', () => {\n assert.strictEqual(getExt('file.txt'), 'txt');\n assert.strictEqual(getExt('script.js'), 'js');\n assert.strictEqual(getExt('component.tsx'), 'tsx');\n });\n\n it('should format paths correctly', () => {\n assert.strictEqual(addLeadingSlash('path/to/file'), '/path/to/file');\n assert.strictEqual(\n removeTrialingSlashes('path/to/file///'),\n 'path/to/file',\n );\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,5 +1,9 @@
1
1
  import type { OperationObject, PathsObject } from 'openapi3-ts/oas31';
2
2
  import { $types } from './deriver.js';
3
+ export type InjectImport = {
4
+ import: string;
5
+ from: string;
6
+ };
3
7
  export type Method = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'trace' | 'head';
4
8
  export declare const methods: readonly ["get", "post", "put", "patch", "delete", "trace", "head"];
5
9
  export type SemanticSource = 'query' | 'queries' | 'body' | 'params' | 'headers';
@@ -21,7 +25,7 @@ export type OnOperation = (sourceFile: string, method: Method, path: string, ope
21
25
  export declare class Paths {
22
26
  #private;
23
27
  constructor(config: {
24
- commonZodImport?: string;
28
+ imports: InjectImport[];
25
29
  onOperation?: OnOperation;
26
30
  });
27
31
  addPath(name: string, path: string, method: Method, contentType: string | undefined, selectors: Selector[], responses: ResponseItem[], sourceFile: string, tags?: string[], description?: string): this;
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,eAAe,EAEf,WAAW,EAIZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,MAAM,MAAM,GACd,KAAK,GACL,MAAM,GACN,KAAK,GACL,OAAO,GACP,QAAQ,GACR,OAAO,GACP,MAAM,CAAC;AACX,eAAO,MAAM,OAAO,qEAQV,CAAC;AACX,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,SAAS,GACT,MAAM,GACN,QAAQ,GACR,SAAS,CAAC;AAQd,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;CAChD;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,eAAe,KACvB,WAAW,CAAC;AACjB,qBAAa,KAAK;;gBAeJ,MAAM,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,WAAW,CAAA;KAAE;IAK3E,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,QAAQ,EAAE,EACrB,SAAS,EAAE,YAAY,EAAE,EACzB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM,EAAE,EACf,WAAW,CAAC,EAAE,MAAM;IA2GhB,QAAQ;CA6Df;AA2BD,UAAU,QAAQ;IAChB,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,GAAG,CAyCxE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,MAAM,CAEzD"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,eAAe,EAEf,WAAW,EAIZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AACF,MAAM,MAAM,MAAM,GACd,KAAK,GACL,MAAM,GACN,KAAK,GACL,OAAO,GACP,QAAQ,GACR,OAAO,GACP,MAAM,CAAC;AACX,eAAO,MAAM,OAAO,qEAQV,CAAC;AACX,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,SAAS,GACT,MAAM,GACN,QAAQ,GACR,SAAS,CAAC;AAQd,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;CAChD;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,eAAe,KACvB,WAAW,CAAC;AACjB,qBAAa,KAAK;;gBAeJ,MAAM,EAAE;QAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,WAAW,CAAA;KAAE;IAK1E,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,QAAQ,EAAE,EACrB,SAAS,EAAE,YAAY,EAAE,EACzB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM,EAAE,EACf,WAAW,CAAC,EAAE,MAAM;IA2GhB,QAAQ;CA6Df;AA4BD,UAAU,QAAQ;IAChB,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,GAAG,CAyCxE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,MAAM,CAEzD"}
package/dist/lib/paths.js CHANGED
@@ -15,11 +15,11 @@ const semanticSourceToOpenAPI = {
15
15
  params: "path"
16
16
  };
17
17
  class Paths {
18
- #commonZodImport;
18
+ #imports = [];
19
19
  #onOperation;
20
20
  #operations = [];
21
21
  constructor(config) {
22
- this.#commonZodImport = config.commonZodImport;
22
+ this.#imports = config.imports;
23
23
  this.#onOperation = config.onOperation;
24
24
  }
25
25
  addPath(name, path, method, contentType, selectors, responses, sourceFile, tags, description) {
@@ -94,7 +94,7 @@ class Paths {
94
94
  if (selector.source === "body") {
95
95
  bodySchemaProps[selector.name] = {
96
96
  required: selector.required,
97
- schema: await evalZod(selector.against, this.#commonZodImport)
97
+ schema: await evalZod(selector.against, this.#imports)
98
98
  };
99
99
  continue;
100
100
  }
@@ -102,7 +102,7 @@ class Paths {
102
102
  in: semanticSourceToOpenAPI[selector.source],
103
103
  name: selector.name,
104
104
  required: selector.required,
105
- schema: await evalZod(selector.against, this.#commonZodImport)
105
+ schema: await evalZod(selector.against, this.#imports)
106
106
  };
107
107
  parameters.push(parameter);
108
108
  }
@@ -163,13 +163,13 @@ class Paths {
163
163
  return path.replace(/:([^/]+)/g, "{$1}");
164
164
  }
165
165
  }
166
- async function evalZod(schema, commonZodImport) {
166
+ async function evalZod(schema, imports = []) {
167
167
  const lines = [
168
168
  `import { createRequire } from "node:module";`,
169
169
  `const filename = "${import.meta.url}";`,
170
170
  `const require = createRequire(filename);`,
171
171
  `const z = require("zod");`,
172
- commonZodImport ? `const commonZod = require('${commonZodImport}');` : "",
172
+ ...imports.map((imp) => `const ${imp.import} = require('${imp.from}');`),
173
173
  `const {zodToJsonSchema} = require('zod-to-json-schema');`,
174
174
  `const schema = ${schema.replace(".optional()", "").replaceAll("instanceof(File)", "string().base64()")};`,
175
175
  `const jsonSchema = zodToJsonSchema(schema, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/paths.ts"],
4
- "sourcesContent": ["import type {\n HeadersObject,\n OperationObject,\n ParameterObject,\n PathsObject,\n ResponseObject,\n ResponsesObject,\n SchemaObject,\n} from 'openapi3-ts/oas31';\n\nimport { $types } from './deriver.js';\n\nexport type Method =\n | 'get'\n | 'post'\n | 'put'\n | 'patch'\n | 'delete'\n | 'trace'\n | 'head';\nexport const methods = [\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'trace',\n 'head',\n] as const;\nexport type SemanticSource =\n | 'query'\n | 'queries'\n | 'body'\n | 'params'\n | 'headers';\n\nconst semanticSourceToOpenAPI = {\n queries: 'query',\n query: 'query',\n headers: 'header',\n params: 'path',\n} as const;\nexport interface Selector {\n name: string;\n select: string;\n against: string;\n source: SemanticSource;\n nullable: boolean;\n required: boolean;\n}\n\nexport interface ResponseItem {\n statusCode: string;\n response?: DateType;\n contentType: string;\n headers: (string | Record<string, string[]>)[];\n}\n\nexport type OnOperation = (\n sourceFile: string,\n method: Method,\n path: string,\n operation: OperationObject,\n) => PathsObject;\nexport class Paths {\n #commonZodImport?: string;\n #onOperation?: OnOperation;\n #operations: Array<{\n sourceFile: string;\n name: string;\n path: string;\n method: Method;\n selectors: Selector[];\n responses: ResponsesObject;\n contentType?: string;\n tags?: string[];\n description?: string;\n }> = [];\n\n constructor(config: { commonZodImport?: string; onOperation?: OnOperation }) {\n this.#commonZodImport = config.commonZodImport;\n this.#onOperation = config.onOperation;\n }\n\n addPath(\n name: string,\n path: string,\n method: Method,\n contentType: string | undefined,\n selectors: Selector[],\n responses: ResponseItem[],\n sourceFile: string,\n tags?: string[],\n description?: string,\n ) {\n const responsesObject = this.#responseItemToResponses(responses);\n\n this.#operations.push({\n name,\n path: this.#tunePath(path),\n sourceFile,\n contentType: contentType,\n method,\n selectors,\n responses: responsesObject,\n tags,\n description,\n });\n return this;\n }\n\n #responseItemToResponses(responses: ResponseItem[]): ResponsesObject {\n const responsesObject: ResponsesObject = {};\n for (const item of responses) {\n const ct = item.contentType;\n const schema = item.response ? toSchema(item.response) : {};\n if (!responsesObject[item.statusCode]) {\n responsesObject[item.statusCode] = {\n description: `Response for ${item.statusCode}`,\n content:\n ct !== 'empty'\n ? {\n [ct]:\n ct === 'application/octet-stream'\n ? { schema: { type: 'string', format: 'binary' } }\n : { schema },\n }\n : undefined,\n headers: item.headers.length\n ? item.headers.reduce<HeadersObject>((acc, current) => {\n const headers =\n typeof current === 'string' ? { [current]: [] } : current;\n return Object.entries(headers).reduce<HeadersObject>(\n (subAcc, [key, value]) => {\n const header: HeadersObject = {\n [key]: {\n schema: {\n type: 'string',\n enum: value.length ? value : undefined,\n },\n },\n };\n return { ...subAcc, ...header };\n },\n acc,\n );\n }, {})\n : undefined,\n } satisfies ResponseObject;\n } else {\n if (!responsesObject[item.statusCode].content[ct]) {\n responsesObject[item.statusCode].content[ct] = { schema };\n } else {\n const existing = responsesObject[item.statusCode].content[ct]\n .schema as SchemaObject;\n if (existing.oneOf) {\n if (\n !existing.oneOf.find(\n (it) => JSON.stringify(it) === JSON.stringify(schema),\n )\n ) {\n existing.oneOf.push(schema);\n }\n } else if (JSON.stringify(existing) !== JSON.stringify(schema)) {\n responsesObject[item.statusCode].content[ct].schema = {\n oneOf: [existing, schema],\n };\n }\n }\n }\n }\n return responsesObject;\n }\n\n async #selectosToParameters(selectors: Selector[]) {\n const parameters: ParameterObject[] = [];\n const bodySchemaProps: Record<\n string,\n { required: boolean; schema: SchemaObject }\n > = {};\n for (const selector of selectors) {\n if (selector.source === 'body') {\n bodySchemaProps[selector.name] = {\n required: selector.required,\n schema: await evalZod(selector.against, this.#commonZodImport),\n };\n continue;\n }\n\n const parameter: ParameterObject = {\n in: semanticSourceToOpenAPI[selector.source],\n name: selector.name,\n required: selector.required,\n schema: await evalZod(selector.against, this.#commonZodImport),\n };\n parameters.push(parameter);\n }\n return { parameters, bodySchemaProps };\n }\n\n async getPaths() {\n const operations: PathsObject = {};\n for (const operation of this.#operations) {\n const { path, method, selectors } = operation;\n const { parameters, bodySchemaProps } =\n await this.#selectosToParameters(selectors);\n const bodySchema: Record<string, SchemaObject> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(bodySchemaProps)) {\n if (value.required) {\n required.push(key);\n }\n bodySchema[key] = value.schema;\n }\n const operationObject: OperationObject = {\n operationId: operation.name,\n parameters,\n tags: operation.tags,\n description: operation.description,\n requestBody: Object.keys(bodySchema).length\n ? {\n required: required.length ? true : false,\n content: {\n [operation.contentType || 'application/json']: {\n schema: {\n required: required.length ? required : undefined,\n type: 'object',\n properties: bodySchema,\n },\n },\n },\n }\n : undefined,\n responses:\n Object.keys(operation.responses).length === 0\n ? undefined\n : operation.responses,\n };\n if (!operations[path]) {\n operations[path] = {};\n }\n operations[path][method] = operationObject;\n if (this.#onOperation) {\n const paths = this.#onOperation?.(\n operation.sourceFile,\n method,\n path,\n operationObject,\n );\n Object.assign(operations, paths ?? {});\n }\n }\n return operations;\n }\n\n /**\n * Converts Express/Node.js style path parameters (/path/:param) to OpenAPI style (/path/{param})\n */\n #tunePath(path: string): string {\n return path.replace(/:([^/]+)/g, '{$1}');\n }\n}\n\nasync function evalZod(schema: string, commonZodImport?: string) {\n // https://github.com/nodejs/node/issues/51956\n const lines = [\n `import { createRequire } from \"node:module\";`,\n `const filename = \"${import.meta.url}\";`,\n `const require = createRequire(filename);`,\n `const z = require(\"zod\");`,\n commonZodImport ? `const commonZod = require('${commonZodImport}');` : '',\n `const {zodToJsonSchema} = require('zod-to-json-schema');`,\n `const schema = ${schema.replace('.optional()', '').replaceAll('instanceof(File)', 'string().base64()')};`,\n `const jsonSchema = zodToJsonSchema(schema, {\n $refStrategy: 'root',\n basePath: ['#', 'components', 'schemas'],\n target: 'jsonSchema7',\n base64Strategy: 'format:binary',\n });`,\n `export default jsonSchema;`,\n ];\n\n const base64 = Buffer.from(lines.join('\\n')).toString('base64');\n return import(`data:text/javascript;base64,${base64}`)\n .then((mod) => mod.default)\n .then(({ $schema, ...result }) => result);\n}\n\ninterface DateType {\n [$types]: any[];\n kind: string;\n optional: boolean;\n value?: string;\n}\n\nexport function toSchema(data: DateType | string | null | undefined): any {\n if (data === null || data === undefined) {\n return { type: 'any' };\n } else if (typeof data === 'string') {\n const isRef = data.startsWith('#');\n if (isRef) {\n return { $ref: data };\n }\n return { type: data };\n } else if (data.kind === 'literal') {\n return { enum: [data.value], type: data[$types][0] };\n } else if (data.kind === 'record') {\n return {\n type: 'object',\n additionalProperties: toSchema(data[$types][0]),\n };\n } else if (data.kind === 'array') {\n const items = data[$types].map(toSchema);\n return { type: 'array', items: data[$types].length ? items[0] : {} };\n } else if (data.kind === 'union') {\n return { anyOf: data[$types].map(toSchema) };\n } else if (data.kind === 'intersection') {\n return { allOf: data[$types].map(toSchema) };\n } else if ($types in data) {\n return data[$types].map(toSchema)[0] ?? {};\n } else {\n const props: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(data)) {\n props[key] = toSchema(value as any);\n if (!(value as any).optional) {\n required.push(key);\n }\n }\n return {\n type: 'object',\n properties: props,\n required,\n additionalProperties: false,\n };\n }\n}\n\nexport function isHttpMethod(name: string): name is Method {\n return ['get', 'post', 'put', 'delete', 'patch'].includes(name);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n HeadersObject,\n OperationObject,\n ParameterObject,\n PathsObject,\n ResponseObject,\n ResponsesObject,\n SchemaObject,\n} from 'openapi3-ts/oas31';\n\nimport { $types } from './deriver.js';\n\nexport type InjectImport = {\n import: string;\n from: string;\n};\nexport type Method =\n | 'get'\n | 'post'\n | 'put'\n | 'patch'\n | 'delete'\n | 'trace'\n | 'head';\nexport const methods = [\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'trace',\n 'head',\n] as const;\nexport type SemanticSource =\n | 'query'\n | 'queries'\n | 'body'\n | 'params'\n | 'headers';\n\nconst semanticSourceToOpenAPI = {\n queries: 'query',\n query: 'query',\n headers: 'header',\n params: 'path',\n} as const;\nexport interface Selector {\n name: string;\n select: string;\n against: string;\n source: SemanticSource;\n nullable: boolean;\n required: boolean;\n}\n\nexport interface ResponseItem {\n statusCode: string;\n response?: DateType;\n contentType: string;\n headers: (string | Record<string, string[]>)[];\n}\n\nexport type OnOperation = (\n sourceFile: string,\n method: Method,\n path: string,\n operation: OperationObject,\n) => PathsObject;\nexport class Paths {\n #imports: InjectImport[] = [];\n #onOperation?: OnOperation;\n #operations: Array<{\n sourceFile: string;\n name: string;\n path: string;\n method: Method;\n selectors: Selector[];\n responses: ResponsesObject;\n contentType?: string;\n tags?: string[];\n description?: string;\n }> = [];\n\n constructor(config: { imports: InjectImport[]; onOperation?: OnOperation }) {\n this.#imports = config.imports;\n this.#onOperation = config.onOperation;\n }\n\n addPath(\n name: string,\n path: string,\n method: Method,\n contentType: string | undefined,\n selectors: Selector[],\n responses: ResponseItem[],\n sourceFile: string,\n tags?: string[],\n description?: string,\n ) {\n const responsesObject = this.#responseItemToResponses(responses);\n\n this.#operations.push({\n name,\n path: this.#tunePath(path),\n sourceFile,\n contentType: contentType,\n method,\n selectors,\n responses: responsesObject,\n tags,\n description,\n });\n return this;\n }\n\n #responseItemToResponses(responses: ResponseItem[]): ResponsesObject {\n const responsesObject: ResponsesObject = {};\n for (const item of responses) {\n const ct = item.contentType;\n const schema = item.response ? toSchema(item.response) : {};\n if (!responsesObject[item.statusCode]) {\n responsesObject[item.statusCode] = {\n description: `Response for ${item.statusCode}`,\n content:\n ct !== 'empty'\n ? {\n [ct]:\n ct === 'application/octet-stream'\n ? { schema: { type: 'string', format: 'binary' } }\n : { schema },\n }\n : undefined,\n headers: item.headers.length\n ? item.headers.reduce<HeadersObject>((acc, current) => {\n const headers =\n typeof current === 'string' ? { [current]: [] } : current;\n return Object.entries(headers).reduce<HeadersObject>(\n (subAcc, [key, value]) => {\n const header: HeadersObject = {\n [key]: {\n schema: {\n type: 'string',\n enum: value.length ? value : undefined,\n },\n },\n };\n return { ...subAcc, ...header };\n },\n acc,\n );\n }, {})\n : undefined,\n } satisfies ResponseObject;\n } else {\n if (!responsesObject[item.statusCode].content[ct]) {\n responsesObject[item.statusCode].content[ct] = { schema };\n } else {\n const existing = responsesObject[item.statusCode].content[ct]\n .schema as SchemaObject;\n if (existing.oneOf) {\n if (\n !existing.oneOf.find(\n (it) => JSON.stringify(it) === JSON.stringify(schema),\n )\n ) {\n existing.oneOf.push(schema);\n }\n } else if (JSON.stringify(existing) !== JSON.stringify(schema)) {\n responsesObject[item.statusCode].content[ct].schema = {\n oneOf: [existing, schema],\n };\n }\n }\n }\n }\n return responsesObject;\n }\n\n async #selectosToParameters(selectors: Selector[]) {\n const parameters: ParameterObject[] = [];\n const bodySchemaProps: Record<\n string,\n { required: boolean; schema: SchemaObject }\n > = {};\n for (const selector of selectors) {\n if (selector.source === 'body') {\n bodySchemaProps[selector.name] = {\n required: selector.required,\n schema: await evalZod(selector.against, this.#imports),\n };\n continue;\n }\n\n const parameter: ParameterObject = {\n in: semanticSourceToOpenAPI[selector.source],\n name: selector.name,\n required: selector.required,\n schema: await evalZod(selector.against, this.#imports),\n };\n parameters.push(parameter);\n }\n return { parameters, bodySchemaProps };\n }\n\n async getPaths() {\n const operations: PathsObject = {};\n for (const operation of this.#operations) {\n const { path, method, selectors } = operation;\n const { parameters, bodySchemaProps } =\n await this.#selectosToParameters(selectors);\n const bodySchema: Record<string, SchemaObject> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(bodySchemaProps)) {\n if (value.required) {\n required.push(key);\n }\n bodySchema[key] = value.schema;\n }\n const operationObject: OperationObject = {\n operationId: operation.name,\n parameters,\n tags: operation.tags,\n description: operation.description,\n requestBody: Object.keys(bodySchema).length\n ? {\n required: required.length ? true : false,\n content: {\n [operation.contentType || 'application/json']: {\n schema: {\n required: required.length ? required : undefined,\n type: 'object',\n properties: bodySchema,\n },\n },\n },\n }\n : undefined,\n responses:\n Object.keys(operation.responses).length === 0\n ? undefined\n : operation.responses,\n };\n if (!operations[path]) {\n operations[path] = {};\n }\n operations[path][method] = operationObject;\n if (this.#onOperation) {\n const paths = this.#onOperation?.(\n operation.sourceFile,\n method,\n path,\n operationObject,\n );\n Object.assign(operations, paths ?? {});\n }\n }\n return operations;\n }\n\n /**\n * Converts Express/Node.js style path parameters (/path/:param) to OpenAPI style (/path/{param})\n */\n #tunePath(path: string): string {\n return path.replace(/:([^/]+)/g, '{$1}');\n }\n}\n\nasync function evalZod(schema: string, imports: InjectImport[] = []) {\n // https://github.com/nodejs/node/issues/51956\n const lines = [\n `import { createRequire } from \"node:module\";`,\n `const filename = \"${import.meta.url}\";`,\n `const require = createRequire(filename);`,\n `const z = require(\"zod\");`,\n ...imports.map((imp) => `const ${imp.import} = require('${imp.from}');`),\n `const {zodToJsonSchema} = require('zod-to-json-schema');`,\n `const schema = ${schema.replace('.optional()', '').replaceAll('instanceof(File)', 'string().base64()')};`,\n `const jsonSchema = zodToJsonSchema(schema, {\n $refStrategy: 'root',\n basePath: ['#', 'components', 'schemas'],\n target: 'jsonSchema7',\n base64Strategy: 'format:binary',\n });`,\n `export default jsonSchema;`,\n ];\n\n const base64 = Buffer.from(lines.join('\\n')).toString('base64');\n /* @vite-ignore */\n return import(`data:text/javascript;base64,${base64}`)\n .then((mod) => mod.default)\n .then(({ $schema, ...result }) => result);\n}\n\ninterface DateType {\n [$types]: any[];\n kind: string;\n optional: boolean;\n value?: string;\n}\n\nexport function toSchema(data: DateType | string | null | undefined): any {\n if (data === null || data === undefined) {\n return { type: 'any' };\n } else if (typeof data === 'string') {\n const isRef = data.startsWith('#');\n if (isRef) {\n return { $ref: data };\n }\n return { type: data };\n } else if (data.kind === 'literal') {\n return { enum: [data.value], type: data[$types][0] };\n } else if (data.kind === 'record') {\n return {\n type: 'object',\n additionalProperties: toSchema(data[$types][0]),\n };\n } else if (data.kind === 'array') {\n const items = data[$types].map(toSchema);\n return { type: 'array', items: data[$types].length ? items[0] : {} };\n } else if (data.kind === 'union') {\n return { anyOf: data[$types].map(toSchema) };\n } else if (data.kind === 'intersection') {\n return { allOf: data[$types].map(toSchema) };\n } else if ($types in data) {\n return data[$types].map(toSchema)[0] ?? {};\n } else {\n const props: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(data)) {\n props[key] = toSchema(value as any);\n if (!(value as any).optional) {\n required.push(key);\n }\n }\n return {\n type: 'object',\n properties: props,\n required,\n additionalProperties: false,\n };\n }\n}\n\nexport function isHttpMethod(name: string): name is Method {\n return ['get', 'post', 'put', 'delete', 'patch'].includes(name);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/lib/ref.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { HeaderObject, OpenAPIObject, ParameterObject, ReferenceObject, RequestBodyObject, SchemaObject } from 'openapi3-ts/oas31';
1
+ import type { HeaderObject, OpenAPIObject, ParameterObject, ReferenceObject, RequestBodyObject, SchemaObject, SecuritySchemeObject } from 'openapi3-ts/oas31';
2
2
  export declare function isRef(obj: any): obj is ReferenceObject;
3
3
  export declare function notRef(obj: any): obj is SchemaObject;
4
4
  export declare function cleanRef(ref: string): string;
@@ -7,6 +7,8 @@ export declare function parseRef(ref: string): {
7
7
  namespace: string;
8
8
  path: string;
9
9
  };
10
+ export declare function resolveRef<T extends SchemaObject | HeaderObject | ParameterObject | ReferenceObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, maybeRef: SchemaObject | ReferenceObject | ParameterObject | SecuritySchemeObject): T;
10
11
  export declare function followRef<T extends SchemaObject | HeaderObject | ParameterObject | ReferenceObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, ref: string): T;
12
+ export declare function tapRef<T extends SchemaObject | HeaderObject | ParameterObject | ReferenceObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, maybeRef: SchemaObject | ReferenceObject): T;
11
13
  export declare function distillRef<T extends SchemaObject | HeaderObject | ParameterObject | ReferenceObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, ref: string): T;
12
14
  //# sourceMappingURL=ref.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/lib/ref.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,EACb,MAAM,mBAAmB,CAAC;AAI3B,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,eAAe,CAEtD;AACD,wBAAgB,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,YAAY,CAEpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,UAEnC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM;;;;EASnC;AACD,wBAAgB,SAAS,CACvB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAOrC;AACD,wBAAgB,UAAU,CACxB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CA8CrC"}
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/lib/ref.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAI3B,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,eAAe,CAEtD;AACD,wBAAgB,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,YAAY,CAEpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,UAEnC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM;;;;EAgBnC;AAED,wBAAgB,UAAU,CACxB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EAEpC,IAAI,EAAE,aAAa,EACnB,QAAQ,EACJ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,oBAAoB,GACvB,CAAC,CAKH;AAED,wBAAgB,SAAS,CACvB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAOrC;AAED,wBAAgB,MAAM,CACpB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,GAAG,eAAe,GAAG,CAAC,CAMlE;AAED,wBAAgB,UAAU,CACxB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CA8CrC"}
package/dist/lib/ref.js CHANGED
@@ -11,22 +11,42 @@ function cleanRef(ref) {
11
11
  }
12
12
  function parseRef(ref) {
13
13
  const parts = ref.split("/");
14
+ const names = [];
14
15
  const [model] = parts.splice(-1);
16
+ names.push(model);
17
+ while (parts.lastIndexOf("properties") !== -1) {
18
+ parts.splice(parts.lastIndexOf("properties"), 1);
19
+ const [model2] = parts.splice(-1);
20
+ names.push(model2);
21
+ }
15
22
  const [namespace] = parts.splice(-1);
16
23
  return {
17
- model,
24
+ model: names.reverse().join(" "),
18
25
  namespace,
19
26
  path: cleanRef(parts.join("/"))
20
27
  };
21
28
  }
29
+ function resolveRef(spec, maybeRef) {
30
+ if (isRef(maybeRef)) {
31
+ return followRef(spec, maybeRef.$ref);
32
+ }
33
+ return maybeRef;
34
+ }
22
35
  function followRef(spec, ref) {
23
36
  const pathParts = cleanRef(ref).split("/");
24
37
  const entry = get(spec, pathParts);
25
- if (entry && "$ref" in entry) {
38
+ if (isRef(entry)) {
26
39
  return followRef(spec, entry.$ref);
27
40
  }
28
41
  return entry;
29
42
  }
43
+ function tapRef(spec, maybeRef) {
44
+ if (isRef(maybeRef)) {
45
+ const pathParts = cleanRef(maybeRef.$ref).split("/");
46
+ return get(spec, pathParts);
47
+ }
48
+ return maybeRef;
49
+ }
30
50
  function distillRef(spec, ref) {
31
51
  const def = followRef(spec, ref);
32
52
  if (!def) {
@@ -78,6 +98,8 @@ export {
78
98
  followRef,
79
99
  isRef,
80
100
  notRef,
81
- parseRef
101
+ parseRef,
102
+ resolveRef,
103
+ tapRef
82
104
  };
83
105
  //# sourceMappingURL=ref.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/ref.ts"],
4
- "sourcesContent": ["import { get } from 'lodash-es';\nimport type {\n HeaderObject,\n OpenAPIObject,\n ParameterObject,\n ReferenceObject,\n RequestBodyObject,\n SchemaObject,\n} from 'openapi3-ts/oas31';\n\nimport { isEmpty } from './utils.js';\n\nexport function isRef(obj: any): obj is ReferenceObject {\n return obj && '$ref' in obj;\n}\nexport function notRef(obj: any): obj is SchemaObject {\n return !isRef(obj);\n}\n\nexport function cleanRef(ref: string) {\n return ref.replace(/^#\\//, '');\n}\n\nexport function parseRef(ref: string) {\n const parts = ref.split('/');\n const [model] = parts.splice(-1);\n const [namespace] = parts.splice(-1);\n return {\n model,\n namespace,\n path: cleanRef(parts.join('/')),\n };\n}\nexport function followRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, ref: string): T {\n const pathParts = cleanRef(ref).split('/');\n const entry = get(spec, pathParts) as T | ReferenceObject;\n if (entry && '$ref' in entry) {\n return followRef<T>(spec, entry.$ref!);\n }\n return entry;\n}\nexport function distillRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, ref: string): T {\n const def = followRef<T>(spec, ref);\n if (!def) {\n return def;\n }\n\n if ('properties' in def) {\n def.properties ??= {};\n for (const key in def.properties) {\n const prop = def.properties[key];\n if (isRef(prop)) {\n def.properties[key] = distillRef(spec, prop.$ref);\n }\n }\n }\n if ('items' in def) {\n if (isRef(def.items)) {\n def.items = distillRef<SchemaObject>(spec, def.items.$ref);\n }\n }\n if ('allOf' in def && !isEmpty(def.allOf)) {\n def.allOf = def.allOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n if ('oneOf' in def && !isEmpty(def.oneOf)) {\n def.oneOf = def.oneOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n if ('anyOf' in def && !isEmpty(def.anyOf)) {\n def.anyOf = def.anyOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n\n return def;\n}\n"],
5
- "mappings": "AAAA,SAAS,WAAW;AAUpB,SAAS,eAAe;AAEjB,SAAS,MAAM,KAAkC;AACtD,SAAO,OAAO,UAAU;AAC1B;AACO,SAAS,OAAO,KAA+B;AACpD,SAAO,CAAC,MAAM,GAAG;AACnB;AAEO,SAAS,SAAS,KAAa;AACpC,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAEO,SAAS,SAAS,KAAa;AACpC,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAM,CAAC,KAAK,IAAI,MAAM,OAAO,EAAE;AAC/B,QAAM,CAAC,SAAS,IAAI,MAAM,OAAO,EAAE;AACnC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,SAAS,MAAM,KAAK,GAAG,CAAC;AAAA,EAChC;AACF;AACO,SAAS,UAOd,MAAqB,KAAgB;AACrC,QAAM,YAAY,SAAS,GAAG,EAAE,MAAM,GAAG;AACzC,QAAM,QAAQ,IAAI,MAAM,SAAS;AACjC,MAAI,SAAS,UAAU,OAAO;AAC5B,WAAO,UAAa,MAAM,MAAM,IAAK;AAAA,EACvC;AACA,SAAO;AACT;AACO,SAAS,WAOd,MAAqB,KAAgB;AACrC,QAAM,MAAM,UAAa,MAAM,GAAG;AAClC,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,MAAI,gBAAgB,KAAK;AACvB,QAAI,eAAe,CAAC;AACpB,eAAW,OAAO,IAAI,YAAY;AAChC,YAAM,OAAO,IAAI,WAAW,GAAG;AAC/B,UAAI,MAAM,IAAI,GAAG;AACf,YAAI,WAAW,GAAG,IAAI,WAAW,MAAM,KAAK,IAAI;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACA,MAAI,WAAW,KAAK;AAClB,QAAI,MAAM,IAAI,KAAK,GAAG;AACpB,UAAI,QAAQ,WAAyB,MAAM,IAAI,MAAM,IAAI;AAAA,IAC3D;AAAA,EACF;AACA,MAAI,WAAW,OAAO,CAAC,QAAQ,IAAI,KAAK,GAAG;AACzC,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS;AAClC,UAAI,MAAM,IAAI,GAAG;AACf,eAAO,WAAyB,MAAM,KAAK,IAAI;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,WAAW,OAAO,CAAC,QAAQ,IAAI,KAAK,GAAG;AACzC,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS;AAClC,UAAI,MAAM,IAAI,GAAG;AACf,eAAO,WAAyB,MAAM,KAAK,IAAI;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,WAAW,OAAO,CAAC,QAAQ,IAAI,KAAK,GAAG;AACzC,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS;AAClC,UAAI,MAAM,IAAI,GAAG;AACf,eAAO,WAAyB,MAAM,KAAK,IAAI;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AACT;",
6
- "names": []
4
+ "sourcesContent": ["import { get } from 'lodash-es';\nimport type {\n HeaderObject,\n OpenAPIObject,\n ParameterObject,\n ReferenceObject,\n RequestBodyObject,\n SchemaObject,\n SecuritySchemeObject,\n} from 'openapi3-ts/oas31';\n\nimport { isEmpty } from './utils.js';\n\nexport function isRef(obj: any): obj is ReferenceObject {\n return obj && '$ref' in obj;\n}\nexport function notRef(obj: any): obj is SchemaObject {\n return !isRef(obj);\n}\n\nexport function cleanRef(ref: string) {\n return ref.replace(/^#\\//, '');\n}\n\nexport function parseRef(ref: string) {\n const parts = ref.split('/');\n const names: string[] = [];\n const [model] = parts.splice(-1);\n names.push(model);\n while (parts.lastIndexOf('properties') !== -1) {\n parts.splice(parts.lastIndexOf('properties'), 1);\n const [model] = parts.splice(-1);\n names.push(model);\n }\n const [namespace] = parts.splice(-1);\n return {\n model: names.reverse().join(' '),\n namespace,\n path: cleanRef(parts.join('/')),\n };\n}\n\nexport function resolveRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(\n spec: OpenAPIObject,\n maybeRef:\n | SchemaObject\n | ReferenceObject\n | ParameterObject\n | SecuritySchemeObject,\n): T {\n if (isRef(maybeRef)) {\n return followRef<T>(spec, maybeRef.$ref!);\n }\n return maybeRef as T;\n}\n\nexport function followRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, ref: string): T {\n const pathParts = cleanRef(ref).split('/');\n const entry = get(spec, pathParts) as T | ReferenceObject;\n if (isRef(entry)) {\n return followRef<T>(spec, entry.$ref!);\n }\n return entry;\n}\n\nexport function tapRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, maybeRef: SchemaObject | ReferenceObject): T {\n if (isRef(maybeRef)) {\n const pathParts = cleanRef(maybeRef.$ref).split('/');\n return get(spec, pathParts) as T;\n }\n return maybeRef as T;\n}\n\nexport function distillRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, ref: string): T {\n const def = followRef<T>(spec, ref);\n if (!def) {\n return def;\n }\n\n if ('properties' in def) {\n def.properties ??= {};\n for (const key in def.properties) {\n const prop = def.properties[key];\n if (isRef(prop)) {\n def.properties[key] = distillRef(spec, prop.$ref);\n }\n }\n }\n if ('items' in def) {\n if (isRef(def.items)) {\n def.items = distillRef<SchemaObject>(spec, def.items.$ref);\n }\n }\n if ('allOf' in def && !isEmpty(def.allOf)) {\n def.allOf = def.allOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n if ('oneOf' in def && !isEmpty(def.oneOf)) {\n def.oneOf = def.oneOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n if ('anyOf' in def && !isEmpty(def.anyOf)) {\n def.anyOf = def.anyOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n\n return def;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["model"]
7
7
  }
@@ -11,4 +11,11 @@ export declare function isEmpty(value: unknown): value is null | undefined | '';
11
11
  export declare function pascalcase(value: string): string;
12
12
  export declare function spinalcase(value: string): string;
13
13
  export declare function snakecase(value: string): string;
14
+ export declare function camelcase(value: string): string;
15
+ /**
16
+ * Joins an array of strings so that elements containing only digits
17
+ * are concatenated without a separator, while all other elements
18
+ * are prefixed by the separator (unless they're the very first element).
19
+ */
20
+ export declare function joinSkipDigits(arr: string[], separator: string): string;
14
21
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,IAAI,EAAE,CAAC,EAAE,EACT,QAAQ,GAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAkB,GACrD,CAAC,EAAE,CAEL;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AAE9E,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvD,GAAG,EAAE,CAAC,EACN,QAAQ,GAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,MAAyB,UAKpE;AAED,MAAM,MAAM,wBAAwB,GAAG,CACrC,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,EAAE,CAAC,IAAI,KACV,YAAY,EAAE,CAAC;AACpB,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,KACjB,YAAY,EAAE,CAAC;AAEpB,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,SAAS,GAAG,EAAE,CAWtE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,UAEvC;AACD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,UAEvC;AACD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,UAEtC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,IAAI,EAAE,CAAC,EAAE,EACT,QAAQ,GAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAkB,GACrD,CAAC,EAAE,CAEL;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AAE9E,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvD,GAAG,EAAE,CAAC,EACN,QAAQ,GAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,MAAyB,UAKpE;AAED,MAAM,MAAM,wBAAwB,GAAG,CACrC,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,EAAE,CAAC,IAAI,KACV,YAAY,EAAE,CAAC;AACpB,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,KACjB,YAAY,EAAE,CAAC;AAEpB,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,SAAS,GAAG,EAAE,CAWtE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,UAEvC;AACD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,UAEvC;AACD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,UAEtC;AACD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAgBvE"}
package/dist/lib/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ camelcase as _camelcase,
2
3
  pascalcase as _pascalcase,
3
4
  snakecase as _snakecase,
4
5
  spinalcase as _spinalcase
@@ -30,8 +31,28 @@ function spinalcase(value) {
30
31
  function snakecase(value) {
31
32
  return _snakecase(value.split("/").join(" "));
32
33
  }
34
+ function camelcase(value) {
35
+ const cleaned = value.replace(/[^A-Za-z0-9]+(?=\d)/g, "");
36
+ return _camelcase(cleaned);
37
+ }
38
+ function joinSkipDigits(arr, separator) {
39
+ if (arr.length === 0)
40
+ return "";
41
+ let result = arr[0];
42
+ for (let i = 1; i < arr.length; i++) {
43
+ const el = arr[i];
44
+ if (/^\d+$/.test(el)) {
45
+ result += el;
46
+ } else {
47
+ result += separator + el;
48
+ }
49
+ }
50
+ return result;
51
+ }
33
52
  export {
53
+ camelcase,
34
54
  isEmpty,
55
+ joinSkipDigits,
35
56
  pascalcase,
36
57
  removeDuplicates,
37
58
  snakecase,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/utils.ts"],
4
- "sourcesContent": ["import {\n pascalcase as _pascalcase,\n snakecase as _snakecase,\n spinalcase as _spinalcase,\n} from 'stringcase';\nimport type ts from 'typescript';\n\nimport type { TypeDeriver } from './deriver';\nimport type { ResponseItem } from './paths';\n\nexport function removeDuplicates<T>(\n data: T[],\n accessor: (item: T) => T[keyof T] | T = (item) => item,\n): T[] {\n return [...new Map(data.map((x) => [accessor(x), x])).values()];\n}\n\nexport type InferRecordValue<T> = T extends Record<string, infer U> ? U : any;\n\nexport function toLitObject<T extends Record<string, any>>(\n obj: T,\n accessor: (value: InferRecordValue<T>) => string = (value) => value,\n) {\n return `{${Object.keys(obj)\n .map((key) => `${key}: ${accessor(obj[key])}`)\n .join(', ')}}`;\n}\n\nexport type NaunceResponseAnalyzerFn = (\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n node: ts.Node,\n) => ResponseItem[];\nexport type NaunceResponseAnalyzer = Record<string, NaunceResponseAnalyzerFn>;\n\nexport type ResponseAnalyzerFn = (\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n) => ResponseItem[];\n\nexport function isEmpty(value: unknown): value is null | undefined | '' {\n if (value === null || value === undefined || value === '') {\n return true;\n }\n if (Array.isArray(value) && value.length === 0) {\n return true;\n }\n if (typeof value === 'object' && Object.keys(value).length === 0) {\n return true;\n }\n return false;\n}\n\nexport function pascalcase(value: string) {\n return _pascalcase(value.split('/').join(' '));\n}\nexport function spinalcase(value: string) {\n return _spinalcase(value.split('/').join(' '));\n}\nexport function snakecase(value: string) {\n return _snakecase(value.split('/').join(' '));\n}\n"],
5
- "mappings": "AAAA;AAAA,EACE,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,OACT;AAMA,SAAS,iBACd,MACA,WAAwC,CAAC,SAAS,MAC7C;AACL,SAAO,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;AAChE;AAIO,SAAS,YACd,KACA,WAAmD,CAAC,UAAU,OAC9D;AACA,SAAO,IAAI,OAAO,KAAK,GAAG,EACvB,IAAI,CAAC,QAAQ,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,CAAC,EAAE,EAC5C,KAAK,IAAI,CAAC;AACf;AAcO,SAAS,QAAQ,OAAgD;AACtE,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,IAAI;AACzD,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC9C,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,OAAO,KAAK,KAAK,EAAE,WAAW,GAAG;AAChE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,WAAW,OAAe;AACxC,SAAO,YAAY,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAC/C;AACO,SAAS,WAAW,OAAe;AACxC,SAAO,YAAY,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAC/C;AACO,SAAS,UAAU,OAAe;AACvC,SAAO,WAAW,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAC9C;",
4
+ "sourcesContent": ["import {\n camelcase as _camelcase,\n pascalcase as _pascalcase,\n snakecase as _snakecase,\n spinalcase as _spinalcase,\n} from 'stringcase';\nimport type ts from 'typescript';\n\nimport type { TypeDeriver } from './deriver';\nimport type { ResponseItem } from './paths';\n\nexport function removeDuplicates<T>(\n data: T[],\n accessor: (item: T) => T[keyof T] | T = (item) => item,\n): T[] {\n return [...new Map(data.map((x) => [accessor(x), x])).values()];\n}\n\nexport type InferRecordValue<T> = T extends Record<string, infer U> ? U : any;\n\nexport function toLitObject<T extends Record<string, any>>(\n obj: T,\n accessor: (value: InferRecordValue<T>) => string = (value) => value,\n) {\n return `{${Object.keys(obj)\n .map((key) => `${key}: ${accessor(obj[key])}`)\n .join(', ')}}`;\n}\n\nexport type NaunceResponseAnalyzerFn = (\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n node: ts.Node,\n) => ResponseItem[];\nexport type NaunceResponseAnalyzer = Record<string, NaunceResponseAnalyzerFn>;\n\nexport type ResponseAnalyzerFn = (\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n) => ResponseItem[];\n\nexport function isEmpty(value: unknown): value is null | undefined | '' {\n if (value === null || value === undefined || value === '') {\n return true;\n }\n if (Array.isArray(value) && value.length === 0) {\n return true;\n }\n if (typeof value === 'object' && Object.keys(value).length === 0) {\n return true;\n }\n return false;\n}\n\nexport function pascalcase(value: string) {\n return _pascalcase(value.split('/').join(' '));\n}\nexport function spinalcase(value: string) {\n return _spinalcase(value.split('/').join(' '));\n}\nexport function snakecase(value: string) {\n return _snakecase(value.split('/').join(' '));\n}\nexport function camelcase(value: string): string {\n // remove any \u201Cspecial\u201D chars immediately preceding a digit\n const cleaned = value.replace(/[^A-Za-z0-9]+(?=\\d)/g, '');\n return _camelcase(cleaned);\n}\n\n/**\n * Joins an array of strings so that elements containing only digits\n * are concatenated without a separator, while all other elements\n * are prefixed by the separator (unless they're the very first element).\n */\nexport function joinSkipDigits(arr: string[], separator: string): string {\n if (arr.length === 0) return '';\n\n let result = arr[0];\n for (let i = 1; i < arr.length; i++) {\n const el = arr[i];\n // If this element is digits-only, append it directly\n if (/^\\d+$/.test(el)) {\n result += el;\n } else {\n // Otherwise, prepend the separator and append the element\n result += separator + el;\n }\n }\n\n return result;\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE,aAAa;AAAA,EACb,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,OACT;AAMA,SAAS,iBACd,MACA,WAAwC,CAAC,SAAS,MAC7C;AACL,SAAO,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;AAChE;AAIO,SAAS,YACd,KACA,WAAmD,CAAC,UAAU,OAC9D;AACA,SAAO,IAAI,OAAO,KAAK,GAAG,EACvB,IAAI,CAAC,QAAQ,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,CAAC,EAAE,EAC5C,KAAK,IAAI,CAAC;AACf;AAcO,SAAS,QAAQ,OAAgD;AACtE,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,IAAI;AACzD,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC9C,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,OAAO,KAAK,KAAK,EAAE,WAAW,GAAG;AAChE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,WAAW,OAAe;AACxC,SAAO,YAAY,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAC/C;AACO,SAAS,WAAW,OAAe;AACxC,SAAO,YAAY,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAC/C;AACO,SAAS,UAAU,OAAe;AACvC,SAAO,WAAW,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAC9C;AACO,SAAS,UAAU,OAAuB;AAE/C,QAAM,UAAU,MAAM,QAAQ,wBAAwB,EAAE;AACxD,SAAO,WAAW,OAAO;AAC3B;AAOO,SAAS,eAAe,KAAe,WAA2B;AACvE,MAAI,IAAI,WAAW;AAAG,WAAO;AAE7B,MAAI,SAAS,IAAI,CAAC;AAClB,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,KAAK,IAAI,CAAC;AAEhB,QAAI,QAAQ,KAAK,EAAE,GAAG;AACpB,gBAAU;AAAA,IACZ,OAAO;AAEL,gBAAU,YAAY;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdk-it/core",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",