Skip to content

Commit 064d3c7

Browse files
Adding Requested Featured: External Build Tools
Eclipse uses `*.launch` files stored in `.externalToolBuilders` folders inside cartridges in order to handle automagically building for things like Sass and JS Minification. This Commit adds support during project setup to look for those setup scripts and add them to your project config. Then during the watch process, if a file is being uploaded that was configured to trigger a build, it will do so. If you have already setup a project, you will need to do so again with the same setting as last time to update the project config to use this new build setting.
1 parent 7eb6b57 commit 064d3c7

File tree

9 files changed

+206
-39
lines changed

9 files changed

+206
-39
lines changed

commands/setup.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const prompt = require('prompt')
55
const slug = require('slug')
66

77
const config = require('../lib/config')()
8+
const builds = require('../lib/builds')
89

910
module.exports = async () => {
1011
const setDefaults =
@@ -129,14 +130,18 @@ module.exports = async () => {
129130
newConfig[client] = {}
130131
}
131132

133+
// Fetch Build Scripts from Project Directory
134+
const builders = builds(result.d)
135+
132136
// Create / Overwrite SFCC Instance for Client
133137
newConfig[client][alias] = {
134138
h: result.h,
135139
v: result.v,
136140
a: result.a,
137141
d: result.d,
138142
u: result.u,
139-
p: result.p
143+
p: result.p,
144+
b: builders
140145
}
141146

142147
// Write Config File

commands/watch.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const chalk = require('chalk')
33
const chokidar = require('chokidar')
44
const ora = require('ora')
55
const path = require('path')
6+
const {exec} = require('child_process')
67

78
const logger = require('../lib/logger')()
89
const notify = require('../lib/notify')()
@@ -34,28 +35,56 @@ module.exports = options => {
3435

3536
if (selected) {
3637
let spinner
37-
const watcher = chokidar.watch('dir', {
38+
const watcher = chokidar.watch(selected.d, {
3839
ignored: [/[/\\]\./, '**/node_modules/**'],
3940
ignoreInitial: true,
4041
persistent: true,
41-
awaitWriteFinish: true,
42-
atomic: true
42+
awaitWriteFinish: true
4343
})
4444

45-
// Add Instance Directory to Watch List
46-
watcher.add(selected.d)
45+
const buildCheck = file => {
46+
if (Object.keys(selected.b).length > 0) {
47+
const checkPath = path.dirname(file).replace(path.normalize(selected.d), '')
48+
Object.keys(selected.b).map(build => {
49+
const builder = selected.b[build]
50+
if (
51+
builder.enabled &&
52+
new RegExp(builder.watch.join('|')).test(checkPath) &&
53+
typeof builder.cmd.exec !== 'undefined' &&
54+
builder.cmd.exec.length > 0
55+
) {
56+
const cmd = builder.cmd.exec
57+
const building = build.split('_')
58+
console.log(
59+
`\n${chalk.bgGreen.white.bold(' BUILDING ')} ${chalk.cyan.bold(
60+
building[1]
61+
)} for cartridge ${chalk.magenta.bold(building[0])} ...\n\n`
62+
)
63+
64+
exec(cmd, (err, data, stderr) => {
65+
if (err || stderr) {
66+
console.error(err, stderr)
67+
}
68+
})
69+
}
70+
})
71+
}
72+
}
4773

4874
// Watch for File Changes
4975
watcher.on('change', file => {
76+
// Check if we need to start a build
77+
buildCheck(file)
5078
upload({file, spinner, selected, client, instance, options})
5179
})
5280
watcher.on('add', file => {
81+
buildCheck(file)
5382
upload({file, spinner, selected, client, instance, options})
5483
})
5584

5685
// @TODO: Watch for Removing Files
5786
watcher.on('unlink', file => {
58-
console.log('UNLINK', file)
87+
console.log(`${chalk.red('✗ REMOVING')} ${file.replace(selected.d, '.')}`)
5988
})
6089

6190
// Watch for Errors
@@ -91,7 +120,7 @@ module.exports = options => {
91120
if (useLog) {
92121
logger.log(logMessage, true)
93122
} else {
94-
spinner = ora(`${logMessage} [Ctrl-C to Cancel]`).start()
123+
spinner = ora(`${chalk.bold(logMessage)} [Ctrl-C to Cancel]\n`).start()
95124
}
96125
})
97126
} else if (client && instance) {

lib/builds.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
const convert = require('xml-js')
2+
const fs = require('fs')
3+
const os = require('os').type()
4+
const path = require('path')
5+
const slug = require('slug')
6+
7+
module.exports = project => {
8+
let externalToolBuilders = []
9+
let build = {}
10+
11+
// Look for *.launch files in .externalToolBuilders folder
12+
fs.readdirSync(project).map(fileName => {
13+
const filePath = path.join(project, fileName)
14+
if (fs.statSync(filePath).isDirectory()) {
15+
const buildPath = path.join(filePath, '.externalToolBuilders')
16+
if (fs.existsSync(buildPath) && fs.statSync(buildPath).isDirectory()) {
17+
fs.readdirSync(buildPath).map(buildFile => {
18+
if (path.extname(buildFile) === '.launch') {
19+
externalToolBuilders.push(path.join(buildPath, buildFile))
20+
}
21+
})
22+
}
23+
}
24+
})
25+
26+
// Loop through found .externalToolBuilders files and parse build instructions
27+
if (externalToolBuilders.length > 0) {
28+
externalToolBuilders.map(launch => {
29+
const xml = fs.readFileSync(launch)
30+
const json = convert.xml2json(xml)
31+
const builder = json ? JSON.parse(json) : null
32+
const name = slug(
33+
path.basename(path.dirname(path.dirname(launch))).replace('_', '-') +
34+
'_' +
35+
path.basename(launch).replace('.launch', ''),
36+
{lower: true, replacement: '-'}
37+
)
38+
39+
// setup placeholder for this config file
40+
build[name] = {
41+
enabled: false,
42+
watch: [],
43+
cmd: {
44+
basedir: null,
45+
exec: null
46+
}
47+
}
48+
49+
if (
50+
builder &&
51+
typeof builder.elements !== 'undefined' &&
52+
builder.elements.length === 1 &&
53+
builder.elements[0].name === 'launchConfiguration' &&
54+
builder.elements[0].elements
55+
) {
56+
builder.elements[0].elements.map(elm => {
57+
// Get Watch Directories
58+
if (elm.attributes.key === 'org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE') {
59+
const buildScopeJson = convert.xml2json(
60+
elm.attributes.value.replace('${working_set:', '').replace(/}$/, '')
61+
)
62+
const buildScope = buildScopeJson ? JSON.parse(buildScopeJson) : null
63+
64+
if (
65+
buildScope &&
66+
typeof buildScope.elements !== 'undefined' &&
67+
buildScope.elements.length === 1 &&
68+
buildScope.elements[0].name === 'resources' &&
69+
builder.elements[0].elements
70+
) {
71+
buildScope.elements[0].elements.map(buildSrc => {
72+
build[name].watch.push(buildSrc.attributes.path)
73+
})
74+
}
75+
}
76+
77+
// Check if we should enable this build
78+
if (elm.attributes.key === 'org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS') {
79+
build[name].enabled =
80+
elm.attributes.value.includes('full') ||
81+
elm.attributes.value.includes('incremental') ||
82+
elm.attributes.value.includes('auto')
83+
}
84+
85+
// Get Build Instructuctions
86+
if (elm.attributes.key === 'org.eclipse.ui.externaltools.ATTR_LOCATION') {
87+
const buildFilePath = elm.attributes.value.replace('${workspace_loc:', '').replace(/}$/, '')
88+
const buildFileXml = fs.readFileSync(path.join(project, buildFilePath))
89+
const buildFileJson = convert.xml2json(buildFileXml)
90+
const buildInstructions = buildFileJson ? JSON.parse(buildFileJson) : null
91+
92+
if (
93+
buildInstructions &&
94+
typeof buildInstructions.elements !== 'undefined' &&
95+
buildInstructions.elements.length === 1 &&
96+
buildInstructions.elements[0].name === 'project' &&
97+
buildInstructions.elements[0].elements
98+
) {
99+
buildInstructions.elements[0].elements.map(buildInstruction => {
100+
build[name].cmd.basedir =
101+
typeof buildInstructions.elements[0].attributes.basedir !== 'undefined'
102+
? path.join(
103+
project,
104+
path.basename(path.dirname(path.dirname(launch))),
105+
buildInstructions.elements[0].attributes.basedir
106+
)
107+
: null
108+
buildInstruction.elements.map(instruction => {
109+
if (instruction.name === 'exec') {
110+
let exec = instruction.attributes.executable
111+
if (
112+
(exec === 'cmd' && os === 'Windows_NT') ||
113+
(exec !== 'cmd' && (os === 'Darwin' || os === 'Linux'))
114+
) {
115+
instruction.elements.map(arg => {
116+
if (arg.name === 'arg') {
117+
exec = exec.concat(' ' + arg.attributes.value)
118+
}
119+
})
120+
121+
// Replace commands that are not needed outside Eclipse
122+
exec = exec.replace('/bin/bash -l -c ', '')
123+
exec = exec.replace(/\${basedir}/g, build[name].cmd.basedir)
124+
125+
build[name].cmd.exec = exec
126+
}
127+
}
128+
})
129+
})
130+
}
131+
}
132+
})
133+
}
134+
})
135+
}
136+
137+
return build
138+
}

lib/search.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = async (selected, groups, options) => {
1111
const excluding = options.exclude && options.exclude.length > 0 ? ` '${options.exclude.join(', ')}'` : ''
1212
const filters = options.filter && options.filter.length > 0 ? ` containing '${options.filter}'` : ''
1313

14-
const text = `Searching${including}${excluding} Logs${filters} [Ctrl-C to Cancel]`
14+
const text = chalk.bold(`Searching${including}${excluding} Logs${filters}`).concat(' [Ctrl-C to Cancel]\n')
1515
const spinner = ora(text)
1616
const output = fn => {
1717
spinner.stop()

lib/tail.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = async (selected, logs, groups, options) => {
1111
const excluding = options.exclude && options.exclude.length > 0 ? ` '${options.exclude.join(', ')}'` : ''
1212
const filters = options.filter && options.filter.length > 0 ? ` containing '${options.filter}'` : ''
1313

14-
const text = `Streaming${including}${excluding} Logs${filters} [Ctrl-C to Cancel]`
14+
const text = chalk.bold(`Streaming${including}${excluding} Logs${filters}`).concat(' [Ctrl-C to Cancel]\n')
1515
const spinner = ora(text)
1616
const output = fn => {
1717
spinner.stop()

lib/upload.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const path = require('path')
22
const pRetry = require('p-retry')
3+
const chalk = require('chalk')
34

45
const logger = require('../lib/logger')()
56
const notify = require('../lib/notify')()
@@ -9,14 +10,13 @@ const mkdirp = require('./mkdirp')
910
module.exports = async ({file, spinner, selected, client, instance, options}) => {
1011
const src = path.relative(process.cwd(), file)
1112
const uploading = new Set()
12-
1313
const useLog = options.log
1414
const errorsOnly = options.errorsOnly
1515

1616
if (!uploading.has(src)) {
1717
const dir = path.dirname(file).replace(path.normalize(selected.d), '')
1818
const dest = path.join('/', 'Cartridges', selected.v, dir)
19-
const text = `Watching ${client} ${instance} [Ctrl-C to Cancel]`
19+
const text = chalk.bold(`Watching ${client} ${instance}`).concat(' [Ctrl-C to Cancel]\n')
2020

2121
uploading.add(src)
2222

@@ -25,18 +25,17 @@ module.exports = async ({file, spinner, selected, client, instance, options}) =>
2525
title: `${client} ${instance}`,
2626
icon: path.join(__dirname, '../icons/', 'sfcc-uploading.png'),
2727
subtitle: 'UPLOADING ...',
28-
message: `${path.basename(src)} => ${dest}`
28+
message: `${path.basename(src)}`
2929
})
3030
}
3131

32-
let logMessage = `Uploading ${file.replace(selected.d, '.')} ...`
32+
let logMessage = `${chalk.cyan('▲ UPLOADING')} ${file.replace(selected.d, '.')}...`
3333

3434
if (useLog) {
3535
logger.log(logMessage)
3636
} else {
37-
spinner.stopAndPersist({text: logMessage})
38-
spinner.text = text
39-
spinner.start()
37+
spinner.stop()
38+
console.log(logMessage)
4039
}
4140

4241
try {
@@ -49,7 +48,7 @@ module.exports = async ({file, spinner, selected, client, instance, options}) =>
4948
}
5049

5150
const tryToMkdir = () => mkdirp(dest, request)
52-
const tryToWrite = () => write(src, dest, request)
51+
const tryToWrite = () => write(file, dest, request)
5352

5453
await pRetry(tryToMkdir, {retries: 5})
5554
await pRetry(tryToWrite, {retries: 5})
@@ -59,11 +58,11 @@ module.exports = async ({file, spinner, selected, client, instance, options}) =>
5958
title: `${client} ${instance}`,
6059
icon: path.join(__dirname, '../icons/', 'sfcc-success.png'),
6160
subtitle: 'UPLOAD COMPLETE',
62-
message: `${path.basename(src)} => ${dest}`
61+
message: `${path.basename(src)}`
6362
})
6463
}
6564

66-
logMessage = `${path.basename(src)} pushed to ${client} ${instance}`
65+
logMessage = `${chalk.green('COMPLETE')} ${file.replace(selected.d, '.')}`
6766

6867
if (useLog) {
6968
logger.log(logMessage)

lib/write.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
const fs = require('fs-extra')
22
const path = require('path')
3-
const debug = require('debug')('write')
43
const axios = require('axios')
54
const followRedirects = require('follow-redirects')
65

76
followRedirects.maxBodyLength = 100 * 1024 * 1024
87

98
module.exports = (src, dest, options) => {
109
try {
11-
debug(`Uploading ${src}`)
12-
1310
const url = path.join('/', dest, path.basename(src))
1411
const stream = fs.createReadStream(src)
15-
1612
const config = Object.assign(
1713
{
1814
url,
@@ -24,6 +20,12 @@ module.exports = (src, dest, options) => {
2420
options
2521
)
2622

27-
return axios(config).then(() => url)
23+
const request = axios(config)
24+
.then(() => url)
25+
.catch(error => {
26+
console.log('error', error)
27+
})
28+
29+
return request
2830
} catch (err) {}
2931
}

0 commit comments

Comments
 (0)