@haptiq/kit 0.6.0 → 0.7.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.
- package/README.md +26 -59
- package/bin/haptiq.kit.js +9 -0
- package/lib/ship.js +112 -5
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -52,71 +52,38 @@ kit js --skip <name> # skip a named config (multi-config mode)
|
|
|
52
52
|
|
|
53
53
|
**Defaults:** reads `src/**/*.js`, writes to `js/bundle.js`.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
---
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```js
|
|
60
|
-
module.exports = {
|
|
61
|
-
css: {
|
|
62
|
-
src: 'assets/**/*.{scss,sass,css}', // default: 'src/**/*.{scss,sass,css}'
|
|
63
|
-
dest: 'public/css', // default: 'css'
|
|
64
|
-
sass: {
|
|
65
|
-
style: 'expanded',
|
|
66
|
-
includePaths: ['node_modules'],
|
|
67
|
-
},
|
|
68
|
-
lightning: {
|
|
69
|
-
targets: { chrome: 90, firefox: 88, safari: 14 },
|
|
70
|
-
minify: true,
|
|
71
|
-
sourceMap: true,
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
// Combine mode — all files into one output
|
|
76
|
-
js: {
|
|
77
|
-
src: 'assets/**/*.js', // default: 'src/**/*.js'
|
|
78
|
-
dest: 'public/js/bundle.js', // default: 'js/bundle.js'
|
|
79
|
-
terser: {
|
|
80
|
-
compress: {
|
|
81
|
-
drop_console: true,
|
|
82
|
-
drop_debugger: true,
|
|
83
|
-
},
|
|
84
|
-
format: {
|
|
85
|
-
comments: false,
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
```
|
|
57
|
+
### `kit ship [target]`
|
|
91
58
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Use `js.configs` to define several named configurations:
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
module.exports = {
|
|
98
|
-
js: {
|
|
99
|
-
configs: {
|
|
100
|
-
'app': {
|
|
101
|
-
src: 'src/app/**/*.js',
|
|
102
|
-
dest: 'public/js/app.bundle.js',
|
|
103
|
-
},
|
|
104
|
-
'components': {
|
|
105
|
-
src: 'src/components/**/*.js',
|
|
106
|
-
dest: 'public/js/components/',
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Then use `--only` / `--skip` to target specific configs:
|
|
59
|
+
Builds CSS and JS assets, then syncs them to a destination via rsync or packages them as a zip archive.
|
|
114
60
|
|
|
115
61
|
```sh
|
|
116
|
-
kit
|
|
117
|
-
kit
|
|
62
|
+
kit ship # prompts you to choose a target
|
|
63
|
+
kit ship staging # ship to a specific named target
|
|
64
|
+
kit ship dist # built-in local target (always available, no config needed)
|
|
65
|
+
kit ship --dev # build without minification before shipping
|
|
66
|
+
kit ship --verbose # show detailed rsync/zip output
|
|
118
67
|
```
|
|
119
68
|
|
|
69
|
+
When called without a target name, the command lists all configured targets and asks you to choose one before proceeding. To skip the prompt, pass the target name directly.
|
|
70
|
+
|
|
71
|
+
`kit ship dist` syncs the project to a sibling directory (`../project-name-dist/`) and is always available without any configuration.
|
|
72
|
+
|
|
73
|
+
**Target types**
|
|
74
|
+
|
|
75
|
+
| Type | Config | Behaviour |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| Remote | `host` + `dest` | rsyncs `src` to `host:dest` |
|
|
78
|
+
| Local | neither | rsyncs `src` to `../project-name-dist/` (same as `kit ship dist`) |
|
|
79
|
+
| Zip | `zip` path | packages `src` into a zip archive |
|
|
80
|
+
|
|
81
|
+
`zip` is mutually exclusive with `host` and `dest`. The zip destination directory must exist; the command aborts if the archive already exists (remove it manually to re-ship).
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Create a `haptiq.config.js` in your project root. All options are optional. See [`examples/haptiq.config.js`](examples/haptiq.config.js) for a full annotated reference.
|
|
86
|
+
|
|
120
87
|
## License
|
|
121
88
|
|
|
122
89
|
GPL-2.0-or-later
|
package/bin/haptiq.kit.js
CHANGED
|
@@ -110,6 +110,15 @@ async function loadConfig(verbose = false) {
|
|
|
110
110
|
if (targetConfig.dev !== undefined && typeof targetConfig.dev !== 'boolean') {
|
|
111
111
|
throw new Error(`ship.targets.${targetName}.dev must be a boolean (true or false, not a string)`);
|
|
112
112
|
}
|
|
113
|
+
if (targetConfig.zip !== undefined && typeof targetConfig.zip !== 'string') {
|
|
114
|
+
throw new Error(`ship.targets.${targetName}.zip must be a string`);
|
|
115
|
+
}
|
|
116
|
+
if (targetConfig.zip && targetConfig.host) {
|
|
117
|
+
throw new Error(`ship.targets.${targetName}: "zip" and "host" are mutually exclusive`);
|
|
118
|
+
}
|
|
119
|
+
if (targetConfig.zip && targetConfig.dest) {
|
|
120
|
+
throw new Error(`ship.targets.${targetName}: "zip" and "dest" are mutually exclusive`);
|
|
121
|
+
}
|
|
113
122
|
}
|
|
114
123
|
}
|
|
115
124
|
}
|
package/lib/ship.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* under the `ship` key.
|
|
7
7
|
*/
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
|
-
import { existsSync } from 'fs';
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
10
|
import readline from 'readline';
|
|
11
11
|
import path from 'path';
|
|
12
|
+
import { glob } from 'glob';
|
|
13
|
+
import { zipSync } from 'fflate';
|
|
12
14
|
import { buildCSS } from './css.js';
|
|
13
15
|
import { buildJS } from './js.js';
|
|
14
16
|
|
|
@@ -45,7 +47,8 @@ async function ship(config, verbose, options = {}) {
|
|
|
45
47
|
if (configuredTargetNames.length === 0) {
|
|
46
48
|
throw new Error('No ship targets configured. Add a ship.targets object to haptiq.config.js, or run `kit ship dist` for a local build.');
|
|
47
49
|
}
|
|
48
|
-
|
|
50
|
+
const selected = await promptTarget(configuredTargetNames);
|
|
51
|
+
targetsToProcess = [selected];
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
for (const name of targetsToProcess) {
|
|
@@ -54,13 +57,86 @@ async function ship(config, verbose, options = {}) {
|
|
|
54
57
|
|
|
55
58
|
await buildCSS(config, verbose, dev);
|
|
56
59
|
await buildJS(config, verbose, { dev });
|
|
57
|
-
|
|
60
|
+
|
|
61
|
+
if (targetConfig.zip) {
|
|
62
|
+
await zipSingleTarget(name, targetConfig, { src, exclude }, verbose);
|
|
63
|
+
} else {
|
|
64
|
+
await shipSingleTarget(name, targetConfig, { src, exclude, deleteRemoved }, verbose);
|
|
65
|
+
}
|
|
58
66
|
|
|
59
67
|
console.log(`✅ Shipped to [${name}].`);
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Zip a single target — builds a zip archive at the configured path.
|
|
74
|
+
* Aborts if the destination directory does not exist or the zip file already exists.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} name - Target name (for log messages)
|
|
77
|
+
* @param {{ zip: string }} targetConfig - Target settings
|
|
78
|
+
* @param {{ src: string, exclude: string[] }} shipConfig - Shared settings
|
|
79
|
+
* @param {boolean} verbose - List each file added to the archive
|
|
80
|
+
* @returns {Promise<void>}
|
|
81
|
+
*/
|
|
82
|
+
async function zipSingleTarget(name, targetConfig, shipConfig, verbose) {
|
|
83
|
+
const { zip: zipPathTemplate } = targetConfig;
|
|
84
|
+
const { src = './', exclude = [] } = shipConfig;
|
|
85
|
+
|
|
86
|
+
const projectRoot = process.cwd();
|
|
87
|
+
const projectName = path.basename(projectRoot);
|
|
88
|
+
|
|
89
|
+
let version = '0.0.0';
|
|
90
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
91
|
+
if (existsSync(pkgPath)) {
|
|
92
|
+
try {
|
|
93
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
94
|
+
version = pkg.version ?? version;
|
|
95
|
+
} catch { /* use default */ }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const resolvedZipPath = zipPathTemplate
|
|
99
|
+
.replace(/\{name\}/g, projectName)
|
|
100
|
+
.replace(/\{version\}/g, version);
|
|
101
|
+
|
|
102
|
+
const zipDir = path.dirname(resolvedZipPath);
|
|
103
|
+
if (!existsSync(zipDir)) {
|
|
104
|
+
throw new Error(`Destination directory does not exist: ${zipDir}\nCreate it manually before shipping.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (existsSync(resolvedZipPath)) {
|
|
108
|
+
throw new Error(`Zip archive already exists: ${resolvedZipPath}\nRemove it manually before shipping to avoid accidental overwrites.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const resolvedSrc = path.resolve(projectRoot, src);
|
|
112
|
+
|
|
113
|
+
const globIgnore = exclude.flatMap(e => {
|
|
114
|
+
const stripped = e.replace(/\/$/, '');
|
|
115
|
+
return [stripped, `${stripped}/**`];
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
console.log(`📦 Packing [${name}] → ${resolvedZipPath}`);
|
|
119
|
+
|
|
120
|
+
const files = await glob('**', {
|
|
121
|
+
cwd: resolvedSrc,
|
|
122
|
+
dot: true,
|
|
123
|
+
nodir: true,
|
|
124
|
+
ignore: globIgnore,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const zipData = {};
|
|
128
|
+
for (const file of files) {
|
|
129
|
+
if (verbose) {
|
|
130
|
+
process.stdout.write(` ${file}\n`);
|
|
131
|
+
}
|
|
132
|
+
zipData[`${projectName}/${file}`] = readFileSync(path.join(resolvedSrc, file));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const zipped = zipSync(zipData);
|
|
136
|
+
writeFileSync(resolvedZipPath, zipped);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
64
140
|
/**
|
|
65
141
|
* Rsync a single target
|
|
66
142
|
*
|
|
@@ -206,15 +282,46 @@ async function confirmDeletion(deletions, destination) {
|
|
|
206
282
|
}
|
|
207
283
|
|
|
208
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Prompt the user to choose a ship target from the available options.
|
|
287
|
+
* Accepts either the target name or its list number.
|
|
288
|
+
*
|
|
289
|
+
* @param {string[]} configuredTargetNames - Target names from haptiq.config.js
|
|
290
|
+
* @returns {Promise<string>} The chosen target name
|
|
291
|
+
*/
|
|
292
|
+
async function promptTarget(configuredTargetNames) {
|
|
293
|
+
const options = [...configuredTargetNames, 'dist'];
|
|
294
|
+
|
|
295
|
+
console.log('\nNo target specified. Choose one of the available targets to ship to:\n');
|
|
296
|
+
options.forEach((name, i) => {
|
|
297
|
+
console.log(` ${i + 1}) ${name}`);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const answer = (await readLine('\nTarget: ')).trim();
|
|
301
|
+
|
|
302
|
+
const num = parseInt(answer, 10);
|
|
303
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) {
|
|
304
|
+
return options[num - 1];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (options.includes(answer)) {
|
|
308
|
+
return answer;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
throw new Error(`"${answer}" is not a valid target. Run \`kit ship <target>\` to skip this prompt.`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
209
315
|
/**
|
|
210
316
|
* Read a single line from stdin.
|
|
211
317
|
*
|
|
318
|
+
* @param {string} prompt - Text to display before waiting for input
|
|
212
319
|
* @returns {Promise<string>}
|
|
213
320
|
*/
|
|
214
|
-
function readLine() {
|
|
321
|
+
function readLine(prompt = '') {
|
|
215
322
|
return new Promise((resolve) => {
|
|
216
323
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
217
|
-
rl.question(
|
|
324
|
+
rl.question(prompt, (answer) => {
|
|
218
325
|
rl.close();
|
|
219
326
|
resolve(answer);
|
|
220
327
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haptiq/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Build tools for Haptiq projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"build",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@haptiq/browserslist-config": "^0.1.0",
|
|
25
25
|
"browserslist": "^4.28.2",
|
|
26
26
|
"commander": "^15.0.0",
|
|
27
|
+
"fflate": "^0.8.0",
|
|
27
28
|
"glob": "^13.0.6",
|
|
28
29
|
"lightningcss": "^1.32.0",
|
|
29
30
|
"sass": "^1.101.0",
|