v-transform 2.3.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "vt": "src/index.js"
5
5
  },
6
6
  "type": "module",
7
- "version": "2.3.3",
7
+ "version": "3.0.1",
8
8
  "main": "index.js",
9
9
  "scripts": {
10
10
  "clean": "npx rimraf test",
package/src/calibrator.js CHANGED
@@ -1,27 +1,21 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { generate } from 'ts-to-zod'
4
+ import { z } from 'zod'
4
5
 
5
- export async function calibrateTsData(data, name, types, pk) {
6
- if (!types || !fs.existsSync(types)) {
7
- console.warn(
8
- `⚠️ 提示: Excel 同级目录下未找到 [${name}.types.ts],将退回普通无类型导出。`,
9
- )
10
- const isArray = Array.isArray(data)
11
- const extra = {
12
- typeSign: isArray ? 'any[]' : 'Map<any, any>',
13
- types: isArray ? `export type ${name.toUpperCase()} = any;` : '',
14
- isNativeMap: !isArray,
15
- }
16
- return { data, extra }
6
+ export async function calibrateTsData(raw, name, dts) {
7
+ if (!dts || !fs.existsSync(dts)) {
8
+ console.warn(`⚠️ [Calibrator] ${name}.types.ts`)
9
+ return { raw, type: 'any', dts: '' }
17
10
  }
18
11
 
19
- console.info(
20
- `🔍 [vTransform TS] 成功在 Excel 目录定位规范,正在校准 [${name}]...`,
21
- )
22
- const sourceText = fs.readFileSync(types, 'utf-8')
23
- const options = { keepOptionalProperties: true }
24
- const zod = generate({ sourceText, options })
12
+ console.info(`🔍 [Calibrator] ${name}.types.ts`)
13
+ const sourceText = fs.readFileSync(dts, 'utf-8')
14
+ const options = {
15
+ keepOptionalProperties: true,
16
+ getSchemaName: identifier => `${identifier}Schema`,
17
+ }
18
+ const zod = generate({ sourceText, ...options })
25
19
  const tsFileBase64 = Buffer.from(sourceText).toString('base64')
26
20
  const configModule = await import(
27
21
  `data:text/javascript;base64,${tsFileBase64}`
@@ -54,111 +48,74 @@ export async function calibrateTsData(data, name, types, pk) {
54
48
  /z\.boolean\(\)/g,
55
49
  `z.preprocess(globalThis.__MERGED_TRANSFORMERS__.boolean, z.boolean())`,
56
50
  )
57
- Object.keys(customTransformers).forEach(fieldKey => {
58
- if (fieldKey === 'string' || fieldKey === 'boolean') return
59
-
60
- const targetRegex = new RegExp(
61
- `(${fieldKey}:\\s*)(z\\.[a-zA-Z_0-9]+(?:\\([^)]*\\))?)`,
62
- 'g',
63
- )
64
- zodRawCode = zodRawCode.replace(
65
- targetRegex,
66
- (m, prefix, originalZodType) => {
67
- mergedTransformers[fieldKey] = customTransformers[fieldKey]
68
- return `${prefix}z.preprocess(globalThis.__MERGED_TRANSFORMERS__.${fieldKey}, ${originalZodType})`
69
- },
70
- )
71
- })
72
51
 
73
52
  globalThis.__MERGED_TRANSFORMERS__ = mergedTransformers
74
53
  const base64Code = Buffer.from(zodRawCode).toString('base64')
75
54
  const zodModule = await import(`data:text/javascript;base64,${base64Code}`)
76
- delete globalThis.__MERGED_TRANSFORMERS__
77
- const targetSchemaName = `${name}Schema`
78
- const RowZodValidator = zodModule[targetSchemaName]
79
55
 
56
+ const search = `${name.replace(/[-_]/g, '')}Schema`.toLocaleLowerCase()
57
+ const targetSchemaName = Object.keys(zodModule).find(key => {
58
+ if (key.toLocaleLowerCase() === search) {
59
+ console.info(`🎯 [Calibrator] ${name} -> ${key}`)
60
+ return true
61
+ }
62
+ })
63
+ const RowZodValidator = zodModule[targetSchemaName]
80
64
  if (!RowZodValidator) {
81
- throw new Error(
82
- `无法从类型文件中自动推导出名为 ${targetSchemaName} 的主校验器。`,
83
- )
65
+ delete globalThis.__MERGED_TRANSFORMERS__
66
+ throw new Error(`❌ [Calibrator] ${targetSchemaName} Missmatch`)
84
67
  }
68
+ const typeName = targetSchemaName.replace(/Schema$/, '')
69
+ dts = sourceText.split(/.*@vt-types-end.*/i)[0]
70
+ const data = { raw, dts, type: typeName }
71
+ Object.keys(customTransformers).forEach(fieldKey => {
72
+ if (fieldKey === 'string' || fieldKey === 'boolean') return
73
+ if (RowZodValidator.shape && RowZodValidator.shape[fieldKey]) {
74
+ const originalValidator = RowZodValidator.shape[fieldKey]
75
+ RowZodValidator.shape[fieldKey] = z.preprocess(
76
+ customTransformers[fieldKey],
77
+ originalValidator,
78
+ )
79
+ mergedTransformers[fieldKey] = customTransformers[fieldKey]
80
+ }
81
+ })
85
82
 
86
- let cleanData
87
- let typeSign = ''
88
- const typeName = name.charAt(0).toUpperCase() + name.slice(1)
89
-
90
- if (Array.isArray(data)) {
91
- cleanData = []
92
- typeSign = `${typeName}[]`
93
- for (let i = 0; i < data.length; i++) {
94
- const parseResult = RowZodValidator.safeParse(data[i])
83
+ globalThis.__MERGED_TRANSFORMERS__ = mergedTransformers
84
+ if (Array.isArray(raw)) {
85
+ for (let i = 0; i < raw.length; i++) {
86
+ const parseResult = RowZodValidator.safeParse(raw[i])
95
87
  if (!parseResult.success) {
96
- printZodError(
97
- name,
98
- `第 ${i + 1} 条数据`,
99
- parseResult.error.issues,
100
- data[i],
101
- )
88
+ delete globalThis.__MERGED_TRANSFORMERS__
89
+ printZodError(name, i, parseResult.error.issues, raw[i])
102
90
  }
103
- cleanData.push(parseResult.data)
91
+ raw[i] = parseResult.data
104
92
  }
105
- console.info(`📦 [Array 模式] 完美对齐数组规范`)
106
- } else if (typeof data === 'object' && data !== null) {
107
- const tempCleanMap = {}
108
- for (const key in data) {
109
- const parseResult = RowZodValidator.safeParse(data[key])
93
+ console.info(`✅ [Calibrator] array`)
94
+ } else if (typeof raw === 'object' && raw !== null) {
95
+ for (const key in raw) {
96
+ const parseResult = RowZodValidator.safeParse(raw[key])
110
97
  if (!parseResult.success) {
111
- printZodError(
112
- name,
113
- `Key 为 [${key}] 的数据`,
114
- parseResult.error.issues,
115
- data[key],
116
- )
117
- }
118
- tempCleanMap[key] = parseResult.data
119
- }
120
- let keyType = 'string'
121
- if (pk && RowZodValidator.shape && RowZodValidator.shape[pk]) {
122
- const validator = RowZodValidator.shape[pk]
123
- if (
124
- validator.safeParse(1).success &&
125
- !validator.safeParse('1').success
126
- ) {
127
- keyType = 'number'
98
+ delete globalThis.__MERGED_TRANSFORMERS__
99
+ printZodError(name, key, parseResult.error.issues, raw[key])
128
100
  }
101
+ raw[key] = parseResult.data
129
102
  }
130
-
131
- const isNumKey = keyType === 'number'
132
- cleanData = Object.entries(tempCleanMap).map(([k, v]) => [
133
- isNumKey ? Number(k) : k,
134
- v,
135
- ])
136
- typeSign = `Map<${keyType}, ${typeName}>`
137
- console.info(
138
- `🎯 [Map 模式] 完美对齐原生 Map 规范,主键 [${pk}] 自动识别为: ${keyType}`,
139
- )
103
+ console.info(`✅ [Calibrator] object`)
140
104
  }
141
105
 
142
- types = sourceText.split(/.*@vt-types-end.*/i)[0]
143
- const extra = { typeSign, types, isNativeMap: !Array.isArray(data) }
144
- return { data: cleanData, extra }
106
+ delete globalThis.__MERGED_TRANSFORMERS__
107
+ return data
145
108
  }
146
109
 
147
- function printZodError(sheetName, positionLabel, issues, rawRowData) {
148
- console.error(`\n❌ [vTransform 数据校准失败]`)
149
- console.error(
150
- `👉 发生位置: 表格 [${sheetName}] ${positionLabel}不兼容你的 TS 类型限制!`,
151
- )
152
- console.error(`👉 冲突细节详情:`)
110
+ function printZodError(sheetName, key, issues, rawRowData) {
111
+ console.error(`❌ [Calibrator] ${sheetName}\x1b[33m[${key}]\x1b[0m`)
153
112
  issues.forEach(err => {
154
- const wrongField = err.path.join('.') || '根节点'
155
- console.error(` - 错误字段: \x1b[33m${wrongField}\x1b[0m`)
156
- console.error(` - 期待类型/约束: ${err.message}`)
113
+ const wrongField = err.path.join('.') || 'root'
157
114
  console.error(
158
- ` - 当前收到的脏数据值:`,
159
- rawRowData[wrongField] ?? '缺失字段',
115
+ ` - \x1b[33m${wrongField}\x1b[0m:`,
116
+ rawRowData[wrongField],
160
117
  )
161
- console.error(` -----------------------------------------`)
118
+ console.error(` - ${err.message}`)
162
119
  })
163
120
  process.exit(1)
164
121
  }
package/src/dump.js CHANGED
@@ -6,43 +6,102 @@ function j(data, space) {
6
6
  return JSON.stringify(data, null, space)
7
7
  }
8
8
 
9
- function jsonify(name, { data, addition }, space) {
10
- return j(addition ? { [name]: data, ...addition } : data, space)
9
+ function minij(json) {
10
+ return json.replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)"\s*:/g, '$1:')
11
11
  }
12
12
 
13
- function cjsify(name, data, space) {
14
- return `module.exports = ${jsonify(name, data, space)}`
13
+ function jp(data, space) {
14
+ const json = j(data, space).replace(/\\/g, '\\\\').replace(/'/g, "\\'")
15
+ return `JSON.parse(\n\t'${json}'\n)`
15
16
  }
16
17
 
17
- function esmify(name, { data, addition }, space) {
18
- const main = `export const ${name} = ${jsonify(name, { data, addition }, space)}`
19
- const def = `export default ${name}`
20
- if (!addition) return `${main}\n${def}`
21
- const extra = Object.entries(addition)
22
- .map(([key, value]) => `export const ${key} = ${j(value, space)}`)
23
- .join('\n')
24
- return `${main}\n${extra}\n${def}`
18
+ function map(raw, pk, space) {
19
+ const isArray = !pk || Array.isArray(raw)
20
+ if (isArray || typeof raw !== 'object') {
21
+ return { data: jp(raw, space), type: isArray ? 'array' : 'object' }
22
+ }
23
+ const data = Object.values(raw).map(v => [v[pk], v])
24
+ console.info(`🔁 [Dump] Object -> Map [${pk}]`)
25
+ return { data: `new Map(${jp(data, space)})`, type: 'map' }
26
+ }
27
+
28
+ function withType(main, sub, pk) {
29
+ switch (main) {
30
+ case 'array':
31
+ return `${sub}[]`
32
+ case 'map':
33
+ if (sub === 'any') return `Map<${sub}, ${sub}>`
34
+ return `Map<${sub}['${pk}'], ${sub}>`
35
+ case 'object':
36
+ return `{ [key: string]: ${sub} }`
37
+ default:
38
+ return sub
39
+ }
25
40
  }
26
41
 
27
- function yamlify(name, { data, addition }, indent) {
28
- const options = { indent: indent || undefined }
29
- if (!addition) return dumpYAML(data, options)
30
- return dumpYAML({ [name]: data, ...addition }, options)
42
+ function jsonify(name, data, space) {
43
+ const { raw, addition, dts } = data
44
+ const json = j(addition ? { [name]: raw, ...addition } : raw, space)
45
+ return [json, dts]
31
46
  }
32
47
 
33
- function tsify(name, { data, extra, addition }, space) {
34
- const { types, typeSign, isNativeMap } = extra
35
- if (!types)
36
- return `export default ${jsonify(name, { data, addition }, space)}`
37
- const raw = j(data, space)
38
- const d = isNativeMap ? `new Map(${raw})` : raw
39
- const main = `${types}\nexport const ${name} = ${d} as unknown as ${typeSign};`
40
- const def = `export default ${name}`
41
- if (!addition) return `${main}\n${def}`
42
- const extraExports = Object.entries(addition)
43
- .map(([key, value]) => `export const ${key} = ${j(value, space)}`)
44
- .join('\n')
45
- return `${main}\n${extraExports}\n${def}`
48
+ function yamlify(name, data, indent) {
49
+ const { raw, addition, dts } = data
50
+ const opt = { indent: indent || undefined }
51
+ const yaml = dumpYAML(addition ? { [name]: raw, ...addition } : raw, opt)
52
+ return [yaml, dts]
53
+ }
54
+
55
+ function cjsify(name, data, space, types) {
56
+ const { type, pk, dts, addition } = data
57
+ const converted = map(data.raw, pk, space)
58
+ const r = []
59
+ if (types && dts) {
60
+ r.push(`/** @typedef {import('./${name}').${type}} ${type} */`)
61
+ r.push(`/** @type {${withType(converted.type, type, pk)}} */`)
62
+ }
63
+ r.push(`const ${name} = ${converted.data}`)
64
+ const exports = [name]
65
+ if (addition)
66
+ Object.entries(addition).forEach(([key, value]) => {
67
+ exports.push(key)
68
+ r.push(`const ${key} = ${j(value, space)}`)
69
+ })
70
+
71
+ r.push(`module.exports = { ${exports.join(', ')} }`)
72
+ return [r.join('\n'), dts]
73
+ }
74
+
75
+ function esmify(name, data, space, types) {
76
+ const { type, pk, dts, addition } = data
77
+ const converted = map(data.raw, pk, space)
78
+ const rows = []
79
+ if (types && dts) {
80
+ rows.push(`/** @typedef {import('./${name}').${type}} ${type} */`)
81
+ rows.push(`/** @type {${withType(converted.type, type, pk)}} */`)
82
+ }
83
+ rows.push(`export const ${name} = ${converted.data}`)
84
+ if (addition)
85
+ Object.entries(addition).forEach(([key, value]) => {
86
+ rows.push(`export const ${key} = ${j(value, space)}`)
87
+ })
88
+
89
+ rows.push(`export default ${name}`)
90
+ return [rows.join('\n'), dts]
91
+ }
92
+
93
+ function tsify(name, data, space) {
94
+ const { pk, addition } = data
95
+ const converted = map(data.raw, pk, space)
96
+ const type = withType(converted.type, data.type, pk)
97
+ const rows = [data.dts]
98
+ rows.push(`export const ${name} = ${converted.data} as unknown as ${type};`)
99
+ if (addition)
100
+ Object.entries(addition).forEach(([key, value]) =>
101
+ rows.push(`export const ${key} = ${j(value, space)}`),
102
+ )
103
+ rows.push(`export default ${name}`)
104
+ return [rows.join('\n')]
46
105
  }
47
106
 
48
107
  async function mkdirs(dir) {
@@ -58,38 +117,63 @@ async function mkdirs(dir) {
58
117
  }
59
118
 
60
119
  async function write(sheet, data) {
61
- console.info(`Dump ${sheet}`)
120
+ console.info(`📦 -> ${sheet}`)
62
121
  await mkdirs(path.dirname(sheet))
63
122
  await writeFile(sheet, data)
64
123
  }
65
124
 
66
- export async function dump(sheet, data, type, space, name) {
67
- let ext, ify
125
+ export async function dump({ sheet, data, type, space, name, ext, types }) {
126
+ let e, ify
68
127
  switch (type) {
69
128
  case 'ts':
70
- ext = '.ts'
129
+ e = '.ts'
71
130
  ify = tsify
72
131
  break
73
132
  case 'cjs':
74
- ext = '.js'
133
+ e = '.js'
75
134
  ify = cjsify
76
135
  break
77
136
  case 'js':
78
137
  case 'mjs':
79
138
  case 'esm':
80
- ext = '.js'
139
+ e = '.js'
81
140
  ify = esmify
82
141
  break
83
142
  case 'yaml':
84
143
  case 'yml':
85
- ext = '.yaml'
144
+ e = '.yaml'
86
145
  ify = yamlify
87
146
  break
88
147
  case 'json':
89
148
  default:
90
- ext = '.json'
149
+ e = '.json'
91
150
  ify = jsonify
92
151
  break
93
152
  }
94
- return write(`${sheet}${ext}`, ify(name, data, space))
153
+
154
+ const [main, dts] = ify(name, data, space, types)
155
+ const result = await write(`${sheet}${ext || e}`, main)
156
+ if (types && dts) await write(`${sheet}.d.ts`, dts)
157
+
158
+ // if (type !== 'ts' && data?.extra?.types) {
159
+ // const { types, typeSign } = data.extra
160
+ // const cleanTypes = String(types).trim() + '\n'
161
+
162
+ // const dtsLines = [
163
+ // cleanTypes,
164
+ // `export declare const ${name}: ${typeSign};`,
165
+ // `export default ${name};`,
166
+ // ]
167
+
168
+ // if (data.addition) {
169
+ // Object.entries(data.addition).forEach(([key, value]) => {
170
+ // dtsLines.push(`export declare const ${key}: ${typeof value};`)
171
+ // })
172
+ // }
173
+
174
+ // const dtsContent = dtsLines.join('\n') + '\n'
175
+ // await write(`${sheet}.d.ts`, dtsContent)
176
+ // }
177
+
178
+ return result
95
179
  }
package/src/index.js CHANGED
@@ -19,7 +19,9 @@ program
19
19
  .option('-w, --cwd <cwd>', 'current work dir', null)
20
20
  .option('-o, --output <output>', 'output dir', null)
21
21
  .option('-d, --dest <dest>', 'dest dir', null)
22
+ .option('-e, --ext <ext>', 'file ext', null)
22
23
  .option('-a, --addition', 'addition version', false)
24
+ .option('-x, --notypes', 'disable types generation', false)
23
25
  .action((list, options) => {
24
26
  if (!options.dest) options.dest = options.output
25
27
  options.list = list
package/src/loader.js CHANGED
@@ -3,13 +3,15 @@ import { load as loadYAML } from 'js-yaml'
3
3
  import path from 'node:path'
4
4
  import { readFile } from 'node:fs/promises'
5
5
 
6
- export async function load({ type, space, config, dest, list, cwd, addition }) {
6
+ export async function load(args) {
7
+ const { config, list, addition, ext } = args
7
8
  const version = new Date().toISOString()
8
- type = type || 'json'
9
- cwd = cwd || process.cwd()
10
- space = Number(space) || 0
11
- dest = dest || cwd
12
- const def = { cwd, type, space, dest, addition }
9
+ const type = args.type || 'json'
10
+ const cwd = args.cwd || process.cwd()
11
+ const space = Number(args.space) || 0
12
+ const dest = args.dest || cwd
13
+ const types = !args.notypes
14
+ const def = { cwd, type, space, dest, addition, ext, types }
13
15
  const m = (...l) => globit(Object.assign({}, ...l))
14
16
  const cfgs = []
15
17
 
package/src/transform.js CHANGED
@@ -12,26 +12,28 @@ export async function transform(options) {
12
12
  console.info(`Transformed in ${Date.now() - now}ms`)
13
13
  }
14
14
 
15
- async function task({ files, dest, cwd, type, space, addition }) {
16
- console.info('Transform task config:', { files, dest, cwd, type, space })
15
+ async function task(options) {
16
+ const { files, dest, cwd, type, space, addition, ext, types } = options
17
+ console.info('Transform task config:', options)
17
18
  const m = new Map()
18
19
  for (const file of files) {
19
20
  const dir = path.resolve(dest, path.dirname(file))
20
- await processPrepared(path.resolve(cwd, file), dir, m, type == 'ts')
21
+ await processPrepared(path.resolve(cwd, file), dir, m)
21
22
  }
22
23
  for (const [sheet, job] of m) {
23
- const data = await job.result()
24
- await dump(sheet, { ...data, addition }, type, space, job.name)
24
+ const data = await job.result(addition)
25
+ const d = { sheet, data, type, space, name: job.name, ext, types }
26
+ await dump(d)
25
27
  }
26
28
  }
27
29
 
28
- async function processPrepared(src, dir, m, isTs) {
30
+ async function processPrepared(src, dir, m) {
29
31
  const prepared = await prepare(src)
30
32
  const xlsxDir = path.dirname(src)
31
33
  for (const { name, data } of prepared) {
32
34
  if (name.startsWith('#')) continue
33
35
  const { sheet, keys, name: parsedName } = parseSheetAndKeys(name, dir)
34
- if (!m.has(sheet)) m.set(sheet, new JobData(isTs, parsedName, xlsxDir))
36
+ if (!m.has(sheet)) m.set(sheet, new JobData(parsedName, xlsxDir))
35
37
  m.get(sheet).append({ keys, data: parser(data) })
36
38
  }
37
39
  }
@@ -47,15 +49,13 @@ function parseSheetAndKeys(name, dir) {
47
49
  }
48
50
 
49
51
  class JobData {
50
- constructor(isTs, name, xlsxDir) {
51
- this.#isTs = isTs
52
+ constructor(name, xlsxDir) {
52
53
  this.#name = name
53
54
  this.#xlsxDir = xlsxDir
54
55
  }
55
56
 
56
57
  #data = []
57
58
  #pk = null
58
- #isTs = false
59
59
  #name = ''
60
60
  #xlsxDir = ''
61
61
 
@@ -64,8 +64,8 @@ class JobData {
64
64
  if (pk) this.#pk = pk
65
65
  }
66
66
 
67
- async result() {
68
- if (!this.#data.length) return {}
67
+ async result(addition) {
68
+ if (!this.#data.length) return { data: {}, extra: null }
69
69
  let result
70
70
  for (const { keys, data } of this.#data) {
71
71
  if (!keys.length) {
@@ -81,15 +81,9 @@ class JobData {
81
81
  }
82
82
  r[last] = this.#combine(r[last], data)
83
83
  }
84
- if (!this.#isTs) return { data: result }
85
-
86
- // TODO: 这里调用数据清洗
87
- return await calibrateTsData(
88
- result,
89
- this.#name,
90
- path.join(this.#xlsxDir, `${this.#name}.types.ts`),
91
- this.#pk,
92
- )
84
+ const dts = path.join(this.#xlsxDir, `${this.#name}.types.ts`)
85
+ const data = await calibrateTsData(result, this.#name, dts)
86
+ return { ...data, pk: this.#pk, addition }
93
87
  }
94
88
 
95
89
  #combine(a, b) {