• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

scriptype / writ-cms / 29583467882

17 Jul 2026 01:18PM UTC coverage: 48.863% (-1.5%) from 50.411%
29583467882

push

github

scriptype
Implement ContentTypeEditor & updateContentType

todo:
- createContentType

maydo:
- introduce mandatory/required/optional for attributes
- maybe aliases are not necessary in model configs?
- archetype/composition

also:
- Added pathName (file name without extension, similar to title in all
else) and contentRaw (unparsed md content) to contentTypes.

666 of 1409 branches covered (47.27%)

Branch coverage included in aggregate %.

2 of 135 new or added lines in 6 files covered. (1.48%)

2279 of 4618 relevant lines covered (49.35%)

1417.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/cms/api/models/contentTypes.js
NEW
1
const matter = require('gray-matter')
×
NEW
2
const _ = require('lodash')
×
3
const { dump } = require('js-yaml')
×
NEW
4
const { ['default']: filenamify } = require('filenamify')
×
NEW
5
const { unusedFilename } = require('unused-filename')
×
NEW
6
const { join, basename } = require('path')
×
NEW
7
const { mkdir, writeFile, rm } = require('fs/promises')
×
NEW
8
const { replaceFilename, validatePath } = require('../helpers')
×
9

NEW
10
const findContentType = (contentTypes, path) => {
×
NEW
11
  return contentTypes.find(ct => ct.path === path)
×
12
}
13

14
const createContentTypesModel = ({ getSettings, getContentTypes }) => {
×
15
  const createContentType = async ({ name, description = '', attributes, ...config }) => {
×
16
    const { rootDirectory } = getSettings()
×
17
    const schemaDirectory = join(rootDirectory, 'schema')
×
18
    const filePath = join(schemaDirectory, `${name}.md`)
×
19
    const frontMatter = dump({
×
20
      name,
21
      attributes,
22
      ...config
23
    }, {
24
      flowLevel: 2
25
    })
26
    const fileContent = ['---', frontMatter.replace(/\s+$/, ''), '---', description].join('\n')
×
27
    await mkdir(schemaDirectory, { recursive: true })
×
28
    await writeFile(filePath, fileContent)
×
29
    return { ok: true }
×
30
  }
31

NEW
32
  const updateContentType = async (path, data) => {
×
NEW
33
    if (!path) {
×
NEW
34
      throw new Error('path is required')
×
35
    }
36

NEW
37
    if (!data.model) {
×
NEW
38
      throw new Error('data.model is required')
×
39
    }
40

NEW
41
    const opts = {
×
42
      title: data.name || 'Untitled',
×
43
      extension: data.extension || '.md',
×
44
      metadata: _(data)
45
        .omit(['name', 'extension', 'description'])
NEW
46
        .pickBy(value => (!!value || value === 0))
×
47
        .value()
48
    }
49

NEW
50
    const contentType = findContentType(getContentTypes(), path)
×
51

NEW
52
    const isTitleDifferent = opts.title !== contentType.pathName
×
NEW
53
    let absolutePath = contentType.absolutePath
×
54
    let isPathDifferentThanTitle
55

NEW
56
    if (isTitleDifferent) {
×
NEW
57
      const sanitizedTitle = filenamify(opts.title)
×
NEW
58
      const sanitizedPath = replaceFilename(contentType.absolutePath, sanitizedTitle)
×
NEW
59
      const unusedPath = await unusedFilename(sanitizedPath)
×
60

NEW
61
      await rm(contentType.absolutePath)
×
62

NEW
63
      isPathDifferentThanTitle = (sanitizedTitle !== opts.title) || (unusedPath !== sanitizedPath)
×
NEW
64
      absolutePath = `${unusedPath}${opts.extension || contentType.extension}`
×
65
    }
66

NEW
67
    const metadataWithName = isPathDifferentThanTitle ? {
×
68
      ...opts.metadata,
69
      name: `${opts.title}`
70
    } : opts.metadata
71

NEW
72
    const metadataWithDescription = data.description.includes('\n') ?
×
73
      metadataWithName :
74
      {
75
        ...metadataWithName,
76
        description: data.description
77
      }
78

NEW
79
    const dump = {
×
80
      data: metadataWithDescription,
81
      content: ''
82
    }
83

NEW
84
    if (!metadataWithDescription.description) {
×
NEW
85
      dump.content = data.description
×
86
    }
87

NEW
88
    const fileContent = matter.stringify(dump)
×
NEW
89
    await writeFile(absolutePath, fileContent)
×
90

NEW
91
    return {
×
92
      path: basename(absolutePath)
93
    }
94
  }
95

96
  const deleteContentType = async (path) => {
×
97
    if (!path) {
×
98
      throw new Error('path is required')
×
99
    }
100

101
    const contentType = getContentTypes().find(p => p.path === path)
×
102

103
    if (!contentType) {
×
104
      throw new Error('content type not found')
×
105
    }
106

107
    const isPathValid = await validatePath(
×
108
      getSettings(),
109
      contentType.absolutePath,
110
      { rootChild: true }
111
    )
112
    if (!isPathValid) {
×
113
      throw new Error('invalid page path')
×
114
    }
115

116
    await rm(contentType.absolutePath, { recursive: true, force: true })
×
117
  }
118

119
  return {
×
120
    get: getContentTypes,
121
    create: createContentType,
122
    update: updateContentType,
123
    delete: deleteContentType
124
  }
125
}
126

127
module.exports = createContentTypesModel
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc