1 | 'use strict'
|
2 |
|
3 | const util = require('util')
|
4 | const fs = require('fs')
|
5 | const path = require('path')
|
6 | const globrex = require('globrex')
|
7 | const log = require('./log')
|
8 | const defaults = require('./defaults')
|
9 | const Zip = require('./zip')
|
10 |
|
11 | const DATA_PATH = path.resolve(__dirname, '..', 'data')
|
12 | const PACKAGE_CONTENT_PATH = path.join(DATA_PATH, 'package-content')
|
13 | const NT_FOLDER_PATH = path.join(DATA_PATH, 'nt-folder', '.content.xml')
|
14 | const FILTER_ZIP_PATH = 'META-INF/vault/filter.xml'
|
15 | const FILTER_WRAPPER = `<?xml version="1.0" encoding="UTF-8"?>
|
16 | <workspaceFilter version="1.0">%s
|
17 | </workspaceFilter>`
|
18 | const FILTER = `
|
19 | <filter root="%s" />`
|
20 | const FILTER_CHILDREN = `
|
21 | <filter root="%s">
|
22 | <exclude pattern="%s/.*" />
|
23 | <include pattern="%s" />
|
24 | <include pattern="%s/.*" />
|
25 | </filter>`
|
26 |
|
27 |
|
28 | class Package {
|
29 | constructor (exclude = defaults.exclude) {
|
30 | this.zip = new Zip()
|
31 | this.exclude = exclude || []
|
32 | this.entries = []
|
33 | }
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | add (localPath) {
|
40 |
|
41 | localPath = this._cleanPath(localPath)
|
42 |
|
43 |
|
44 | if (!localPath.includes('jcr_root/')) {
|
45 | return null
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 | if (localPath.endsWith('.xml')) {
|
51 | return this.add(path.dirname(localPath))
|
52 | }
|
53 |
|
54 |
|
55 | const entry = this._deduplicateAndAdd(localPath)
|
56 | if (!entry) {
|
57 | return null
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 | this._addContentXml(localPath)
|
63 |
|
64 |
|
65 | for (let parentPath = path.dirname(localPath); !parentPath.endsWith('jcr_root'); parentPath = path.dirname(parentPath)) {
|
66 | this._addContentXml(parentPath)
|
67 | }
|
68 |
|
69 | return entry
|
70 | }
|
71 |
|
72 | _addContentXml (localPath) {
|
73 | try {
|
74 | if (fs.lstatSync(localPath).isDirectory()) {
|
75 | const contentXmlPath = path.join(localPath, '.content.xml')
|
76 | if (fs.existsSync(contentXmlPath)) {
|
77 |
|
78 | this._deduplicateAndAdd(contentXmlPath)
|
79 | } else {
|
80 |
|
81 |
|
82 | this._deduplicateAndAdd(contentXmlPath, NT_FOLDER_PATH)
|
83 | }
|
84 | }
|
85 | } catch (err) {
|
86 | log.debug(err)
|
87 | }
|
88 | }
|
89 |
|
90 | _deduplicateAndAdd (virtualLocalPath, localPath) {
|
91 | virtualLocalPath = this._cleanPath(virtualLocalPath)
|
92 |
|
93 |
|
94 | if (this._isExcluded(virtualLocalPath)) {
|
95 | return null
|
96 | }
|
97 |
|
98 |
|
99 | const zipPath = this._getZipPath(virtualLocalPath)
|
100 | for (let i = this.entries.length - 1; i >= 0; --i) {
|
101 | const existingZipPath = this.entries[i].zipPath
|
102 |
|
103 |
|
104 | if (zipPath === existingZipPath) {
|
105 | return log.debug(`Already added to package, skipping: ${zipPath}`)
|
106 | }
|
107 |
|
108 |
|
109 | if (zipPath.startsWith(existingZipPath) && !zipPath.endsWith('.content.xml')) {
|
110 | return log.debug(`Parent already added to package, skipping: ${zipPath}`)
|
111 | }
|
112 |
|
113 |
|
114 | if (existingZipPath.startsWith(zipPath)) {
|
115 | log.debug(`Removing child: ${existingZipPath}`)
|
116 | this.entries.splice(i, 1)
|
117 | }
|
118 | }
|
119 |
|
120 | localPath = localPath ? this._cleanPath(localPath) : virtualLocalPath
|
121 | const entry = this._getEntry(localPath, zipPath)
|
122 | this.entries.push(entry)
|
123 | return entry
|
124 | }
|
125 |
|
126 | _isExcluded (localPath) {
|
127 | for (const globPattern of this.exclude) {
|
128 | const regex = globrex(globPattern, { globstar: true, extended: true }).regex
|
129 | if (regex.test(localPath)) {
|
130 | return true
|
131 | }
|
132 | }
|
133 |
|
134 | return false
|
135 | }
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 | save (archivePath) {
|
142 | if (this.entries.length === 0) {
|
143 | return null
|
144 | }
|
145 |
|
146 |
|
147 | const jcrRoot = path.join(PACKAGE_CONTENT_PATH, 'jcr_root')
|
148 | const metaInf = path.join(PACKAGE_CONTENT_PATH, 'META-INF')
|
149 | this.zip.add(jcrRoot, 'jcr_root')
|
150 | this.zip.add(metaInf, 'META-INF')
|
151 |
|
152 |
|
153 | const filters = []
|
154 | for (const entry of this.entries) {
|
155 | if (!entry.exists) {
|
156 |
|
157 |
|
158 | filters.push(util.format(FILTER, entry.filterPath))
|
159 | } else {
|
160 |
|
161 |
|
162 | const dirName = path.dirname(entry.filterPath)
|
163 |
|
164 | filters.push(util.format(FILTER_CHILDREN, dirName, dirName, entry.filterPath, entry.filterPath))
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | this.zip.add(entry.localPath, entry.zipPath)
|
170 | }
|
171 | }
|
172 |
|
173 |
|
174 | const filter = util.format(FILTER_WRAPPER, filters.join('\n'))
|
175 | this.zip.add(Buffer.from(filter), FILTER_ZIP_PATH)
|
176 |
|
177 |
|
178 | log.debug('Package details:')
|
179 | log.group()
|
180 | log.debug(JSON.stringify(this.zip.inspect(), null, 2))
|
181 | log.groupEnd()
|
182 |
|
183 | return this.zip.save(archivePath)
|
184 | }
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | _getEntry (localPath, zipPath) {
|
199 | localPath = this._cleanPath(localPath)
|
200 |
|
201 | const entry = {
|
202 | localPath,
|
203 | zipPath,
|
204 | filterPath: this._getFilterPath(zipPath)
|
205 | }
|
206 |
|
207 | try {
|
208 | const stat = fs.statSync(localPath)
|
209 | entry.exists = true
|
210 | entry.isFolder = stat.isDirectory()
|
211 | } catch (err) {
|
212 | entry.exists = false
|
213 | }
|
214 |
|
215 | return entry
|
216 | }
|
217 |
|
218 | _cleanPath (localPath) {
|
219 | return path.resolve(localPath)
|
220 | .replace(/\\/g, '/')
|
221 | .replace(/\/$/, '')
|
222 | }
|
223 |
|
224 | _getZipPath (localPath) {
|
225 | return this._cleanPath(localPath)
|
226 | .replace(/.*\/(jcr_root\/.*)/, '$1')
|
227 | }
|
228 |
|
229 | _getFilterPath (localPath) {
|
230 |
|
231 |
|
232 |
|
233 | return this._cleanPath(localPath)
|
234 | .replace(/(.*jcr_root)|(\.xml$)|(\.dir)/g, '')
|
235 | .replace(/\/_([^/^_]*)_([^/]*)$/g, '/$1:$2')
|
236 | }
|
237 | }
|
238 |
|
239 | module.exports = Package
|