@changke/staticnext-build 0.13.0 → 0.27.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/AGENTS.md +195 -0
- package/CHANGELOG.md +19 -0
- package/build.mjs +49 -17
- package/lib/config.mjs +8 -7
- package/lib/is-prod.mjs +5 -3
- package/lib/tasks/clean.mjs +9 -4
- package/lib/tasks/copy.mjs +51 -5
- package/lib/tasks/markdown.mjs +54 -18
- package/lib/tasks/prototype.mjs +30 -6
- package/lib/tasks/scripts.mjs +3 -3
- package/lib/tasks/set-env-dev.mjs +2 -5
- package/lib/tasks/set-env-prod.mjs +2 -5
- package/lib/tasks/styles.mjs +5 -6
- package/lib/typedefs.mjs +9 -1
- package/lib/utils.mjs +96 -3
- package/package.json +12 -15
- package/eslint.config.mjs +0 -30
package/AGENTS.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidelines for AI agents working in the `@changke/staticnext-build` repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Node.js CLI build tool (`sn-build`) that orchestrates static asset builds: cleaning,
|
|
8
|
+
copying, Markdown-to-HTML, HTML prototyping (Nunjucks or jstmpl), JS/TS bundling,
|
|
9
|
+
and CSS bundling via esbuild. Pure JavaScript, ESM-only, no TypeScript compiler,
|
|
10
|
+
no React/Next.js framework.
|
|
11
|
+
|
|
12
|
+
## Build / Lint / Test Commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Lint (ESLint v9 flat config with @stylistic)
|
|
16
|
+
npm run lint
|
|
17
|
+
|
|
18
|
+
# Run all tests (Node.js built-in test runner, requires Node >= 22.14.0)
|
|
19
|
+
npm test
|
|
20
|
+
|
|
21
|
+
# Run a single test file
|
|
22
|
+
node --test test/util.test.mjs
|
|
23
|
+
|
|
24
|
+
# Run tests matching a name pattern
|
|
25
|
+
node --test --test-name-pattern="getTargetPathString" test/util.test.mjs
|
|
26
|
+
|
|
27
|
+
# Lint + test (runs automatically before npm publish)
|
|
28
|
+
npm run prepublishOnly
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
There is no build/compile step -- the source is plain `.mjs` files executed directly.
|
|
32
|
+
|
|
33
|
+
## Module System
|
|
34
|
+
|
|
35
|
+
- **ESM only.** Package has `"type": "module"`. All files use `.mjs` extension.
|
|
36
|
+
- No CommonJS (`require`, `module.exports`) anywhere.
|
|
37
|
+
|
|
38
|
+
## Code Style
|
|
39
|
+
|
|
40
|
+
Formatting is enforced entirely by ESLint with `@stylistic/eslint-plugin` -- there
|
|
41
|
+
is no Prettier. Run `npm run lint` to check.
|
|
42
|
+
|
|
43
|
+
### Formatting Rules
|
|
44
|
+
|
|
45
|
+
| Rule | Setting |
|
|
46
|
+
|------|---------|
|
|
47
|
+
| Indentation | 2 spaces |
|
|
48
|
+
| Quotes | Single quotes |
|
|
49
|
+
| Semicolons | Always required |
|
|
50
|
+
| Trailing commas | Never (`comma-dangle: never`) |
|
|
51
|
+
| Object braces | No spaces (`{foo}` not `{ foo }`) |
|
|
52
|
+
| Block spacing | No spaces (`{return x}` not `{ return x }`) |
|
|
53
|
+
| Brace style | 1TBS (opening brace on same line) |
|
|
54
|
+
| Arrow parens | As-needed (omit for single param) |
|
|
55
|
+
| Named function parens | No space before: `function foo()` |
|
|
56
|
+
| Async arrow | Space required: `async () =>` |
|
|
57
|
+
| Line endings | LF only |
|
|
58
|
+
| Trailing whitespace | Trimmed (except in `.md` files) |
|
|
59
|
+
| Final newline | Always insert |
|
|
60
|
+
|
|
61
|
+
### Import Conventions
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// Node built-ins: always use `node:` prefix
|
|
65
|
+
import {readFile} from 'node:fs/promises';
|
|
66
|
+
import path from 'node:path';
|
|
67
|
+
import {env} from 'node:process';
|
|
68
|
+
|
|
69
|
+
// Third-party: bare specifiers
|
|
70
|
+
import {build} from 'esbuild';
|
|
71
|
+
import {marked} from 'marked';
|
|
72
|
+
|
|
73
|
+
// Local: relative paths with explicit .mjs extension
|
|
74
|
+
import {consoleLogColored} from '../utils.mjs';
|
|
75
|
+
import Config from './config.mjs';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- Prefer named imports over namespace imports.
|
|
79
|
+
- Default imports are used for classes/singletons (e.g., `Config`, `path`).
|
|
80
|
+
- Group imports: Node built-ins first, then third-party, then local (separated by blank lines).
|
|
81
|
+
|
|
82
|
+
### Export Conventions
|
|
83
|
+
|
|
84
|
+
Modules typically provide both named and default exports:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const clean = async dirs => { /* ... */ };
|
|
88
|
+
export {clean};
|
|
89
|
+
export default clean;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Naming Conventions
|
|
93
|
+
|
|
94
|
+
| Entity | Convention | Examples |
|
|
95
|
+
|--------|-----------|----------|
|
|
96
|
+
| Variables, functions, params | camelCase | `getTargetPathString`, `srcPaths` |
|
|
97
|
+
| Classes | PascalCase | `Config` |
|
|
98
|
+
| Constants | camelCase (not SCREAMING_SNAKE) | `taskName`, `defaultConfig` |
|
|
99
|
+
| Files | kebab-case with `.mjs` | `set-env-dev.mjs`, `is-prod.mjs` |
|
|
100
|
+
| Test files | `*.test.mjs` | `util.test.mjs`, `config.test.mjs` |
|
|
101
|
+
|
|
102
|
+
### Types
|
|
103
|
+
|
|
104
|
+
No TypeScript compiler is used. Types are defined via **JSDoc** in `lib/typedefs.mjs`:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
/**
|
|
108
|
+
* @typedef {Object} PathPart
|
|
109
|
+
* @property {string} assets Path for general static assets
|
|
110
|
+
* @property {string=} css Path for CSS files
|
|
111
|
+
*/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use `@param`, `@returns`, `@typedef`, and `@type` annotations for documenting
|
|
115
|
+
function signatures and inline type hints:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
/** @type string[] */
|
|
119
|
+
const negativePatterns = [];
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Error Handling
|
|
123
|
+
|
|
124
|
+
- Use `try/catch` in async functions.
|
|
125
|
+
- Log errors with `console.error()`.
|
|
126
|
+
- Top-level entry point (`build.mjs`) catches with `.catch()` and calls `process.exit(1)`.
|
|
127
|
+
- Inner functions log errors and allow graceful continuation where appropriate.
|
|
128
|
+
|
|
129
|
+
### Async Patterns
|
|
130
|
+
|
|
131
|
+
- `async/await` is the primary pattern.
|
|
132
|
+
- `Promise.all()` for parallel operations (e.g., running styles + scripts + markdown concurrently).
|
|
133
|
+
- `.then()` chaining for sequential task pipelines:
|
|
134
|
+
```js
|
|
135
|
+
setEnvDev().then(tasks.clean).then(tasks.copy).then(paraDev);
|
|
136
|
+
```
|
|
137
|
+
- Arrow functions are the dominant function style.
|
|
138
|
+
|
|
139
|
+
## Testing
|
|
140
|
+
|
|
141
|
+
Tests use the **Node.js built-in test runner** (`node:test` module), not Jest or Vitest.
|
|
142
|
+
|
|
143
|
+
### Test Structure
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
import {suite, test} from 'node:test';
|
|
147
|
+
import assert from 'node:assert/strict';
|
|
148
|
+
|
|
149
|
+
void suite('SuiteName', () => {
|
|
150
|
+
test('descriptive test name', () => {
|
|
151
|
+
assert.strictEqual(actual, expected);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('async test', async () => {
|
|
155
|
+
const result = await someAsyncFunc();
|
|
156
|
+
assert.strictEqual(result, expected);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Key patterns:
|
|
162
|
+
- Suites are wrapped with `void suite(...)` (the `void` suppresses the return value).
|
|
163
|
+
- Use `node:assert/strict` -- always strict assertions.
|
|
164
|
+
- Tests clean up after themselves (create/remove temp directories in `before`/`after`).
|
|
165
|
+
- Test fixtures live in `test/fixtures/`.
|
|
166
|
+
|
|
167
|
+
## Project Structure
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
build.mjs CLI entry point (bin: sn-build)
|
|
171
|
+
sn-config.mjs Default project configuration
|
|
172
|
+
lib/
|
|
173
|
+
config.mjs Config loading, merging, path helpers
|
|
174
|
+
is-prod.mjs NODE_ENV check helper
|
|
175
|
+
typedefs.mjs JSDoc type definitions
|
|
176
|
+
utils.mjs File I/O, glob helpers, console coloring
|
|
177
|
+
vars.mjs Default configuration values
|
|
178
|
+
tasks/
|
|
179
|
+
clean.mjs Remove target directories
|
|
180
|
+
copy.mjs Copy static assets via glob
|
|
181
|
+
markdown.mjs Convert .md -> .html (marked + nunjucks/jstmpl)
|
|
182
|
+
prototype.mjs Generate static HTML pages from templates
|
|
183
|
+
scripts.mjs Bundle JS/TS via esbuild
|
|
184
|
+
set-env-dev.mjs Set NODE_ENV=development
|
|
185
|
+
set-env-prod.mjs Set NODE_ENV=production
|
|
186
|
+
styles.mjs Bundle CSS via esbuild
|
|
187
|
+
test/
|
|
188
|
+
*.test.mjs Test files
|
|
189
|
+
fixtures/ Test fixture data
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Changelog & Versioning
|
|
193
|
+
|
|
194
|
+
The project follows [Semantic Versioning](https://semver.org/) and maintains a
|
|
195
|
+
`CHANGELOG.md` in [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.27.0
|
|
8
|
+
2026-02-12
|
|
9
|
+
### Changed
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
- Various code changes after AI (Opus 4.6) code review
|
|
12
|
+
|
|
13
|
+
## 0.26.0
|
|
14
|
+
2025-12-21
|
|
15
|
+
### Removed
|
|
16
|
+
- `del`, `cpy` and `globby` to prefer node's native features (`rm`, `cp` and `glob`)
|
|
17
|
+
- `mocha` to prefer node's built-in test runner
|
|
18
|
+
### Added
|
|
19
|
+
- New experimental templating system `JSTmpl` as default
|
|
20
|
+
- Parameter `--njk` to specify Nunjucks as template engine
|
|
21
|
+
- Parameter `--debug` to show verbose information for "copy" and "markdown" tasks
|
|
22
|
+
### Changed
|
|
23
|
+
- Console outputs now with colors
|
|
24
|
+
- Updated various dependencies
|
|
25
|
+
|
|
7
26
|
## 0.13.0
|
|
8
27
|
2025-09-20
|
|
9
28
|
### Changed
|
package/build.mjs
CHANGED
|
@@ -1,52 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
import {argv} from 'node:process';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import './lib/typedefs.mjs';
|
|
6
|
+
import config from './lib/config.mjs';
|
|
6
7
|
|
|
7
8
|
import clean from './lib/tasks/clean.mjs';
|
|
8
9
|
import copy from './lib/tasks/copy.mjs';
|
|
9
|
-
import markdown from './lib/tasks/markdown.mjs';
|
|
10
|
-
import prototype from './lib/tasks/prototype.mjs';
|
|
10
|
+
import {markdown, markdown2} from './lib/tasks/markdown.mjs';
|
|
11
|
+
import {prototype, prototype2} from './lib/tasks/prototype.mjs';
|
|
11
12
|
import scripts from './lib/tasks/scripts.mjs';
|
|
12
13
|
import setEnvDev from './lib/tasks/set-env-dev.mjs';
|
|
13
14
|
import setEnvProd from './lib/tasks/set-env-prod.mjs';
|
|
14
15
|
import styles from './lib/tasks/styles.mjs';
|
|
16
|
+
import {consoleLogColored} from './lib/utils.mjs';
|
|
15
17
|
|
|
16
18
|
const taskName = argv[2]?.toLowerCase() || 'dev';
|
|
19
|
+
const useNjk = argv.includes('--njk');
|
|
20
|
+
const debug = argv.includes('--debug');
|
|
21
|
+
|
|
22
|
+
if (debug) {
|
|
23
|
+
process.env.SN_BUILD_MODE = 'debug';
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
// load config data
|
|
19
|
-
const conf = await
|
|
27
|
+
const conf = await config.loadConfig();
|
|
20
28
|
|
|
21
29
|
const taskEnded = taskName => {
|
|
22
30
|
console.timeEnd(taskName);
|
|
23
31
|
};
|
|
24
32
|
|
|
25
|
-
const srcPaths =
|
|
26
|
-
const tgtPaths =
|
|
33
|
+
const srcPaths = config.getSourcePaths(conf.sourcePath, path.normalize(conf.sourceRoot));
|
|
34
|
+
const tgtPaths = config.getTargetPaths(conf.targetPath, path.normalize(conf.targetRoot), conf.targetAssetsRoot);
|
|
27
35
|
|
|
28
36
|
// Set parameters of each task
|
|
29
37
|
const tasks = {
|
|
30
|
-
clean: () => clean([
|
|
38
|
+
clean: () => clean([conf.targetRoot]),
|
|
31
39
|
copy: () => copy([
|
|
32
|
-
|
|
33
|
-
{sources: `${
|
|
34
|
-
|
|
35
|
-
{sources: `${srcPaths.
|
|
36
|
-
|
|
40
|
+
// Root2root: src/main/webapp/index.html -> tgt/static/index.html
|
|
41
|
+
{sources: [`${conf.sourceRoot}/*.*`, `!${conf.sourceRoot}/*.d.ts`], target: conf.targetRoot, opts: {flat: true}},
|
|
42
|
+
// Assets: src/main/webapp/assets/dummy.txt -> tgt/static/assets/_sn_/dummy.txt
|
|
43
|
+
{sources: `${srcPaths.assets}/**/*.*`, target: tgtPaths.assets, opts: {levelRemoval: 4}},
|
|
44
|
+
// Module assets:
|
|
45
|
+
// src/main/webapp/modules/mod-weather/assets/weather/sample.json -> tgt/static/assets/_sn_/modules/mod-weather/assets/weather/sample.json
|
|
46
|
+
{sources: `${srcPaths.modules}/**/assets/**/*.*`, target: tgtPaths.moduleAssets, opts: {levelRemoval: 4}},
|
|
47
|
+
// Markdown file assets
|
|
48
|
+
// src/main/webapp/md/google-pixel-9/assets/cover.webp -> public/posts/google-pixel-9/assets/cover.webp
|
|
49
|
+
{sources: `${srcPaths.markdown}/**/assets/**/*.*`, target: tgtPaths.markdown, opts: {levelRemoval: 4}}
|
|
50
|
+
].concat(config.getCopyPairs(conf.copyPairs, conf.sourceRoot, tgtPaths.assets))),
|
|
37
51
|
markdown: () => markdown(
|
|
38
52
|
srcPaths.markdown,
|
|
39
53
|
[srcPaths.prototype, srcPaths.modules],
|
|
40
54
|
conf.njkGlobals,
|
|
41
55
|
tgtPaths.markdown
|
|
42
56
|
),
|
|
57
|
+
markdown2: () => markdown2(
|
|
58
|
+
srcPaths.markdown,
|
|
59
|
+
conf.njkGlobals,
|
|
60
|
+
tgtPaths.markdown
|
|
61
|
+
),
|
|
43
62
|
prototype: () => prototype(
|
|
44
63
|
srcPaths.prototype,
|
|
45
64
|
[srcPaths.modules],
|
|
46
65
|
conf.njkGlobals,
|
|
47
66
|
tgtPaths.prototype
|
|
48
67
|
),
|
|
49
|
-
|
|
68
|
+
prototype2: () => prototype2(
|
|
69
|
+
srcPaths.prototype,
|
|
70
|
+
conf.njkGlobals,
|
|
71
|
+
tgtPaths.prototype
|
|
72
|
+
),
|
|
73
|
+
scripts: () => scripts(config.getScriptEntries(conf.moduleEntries, conf.mainEntryFile, srcPaths), tgtPaths.assets),
|
|
50
74
|
styles: () => styles([
|
|
51
75
|
`${srcPaths.css}/brands/*.css`,
|
|
52
76
|
`${srcPaths.css}/index.css`,
|
|
@@ -57,7 +81,7 @@ const tasks = {
|
|
|
57
81
|
|
|
58
82
|
const paraDev = () => {
|
|
59
83
|
return Promise.all([
|
|
60
|
-
tasks.markdown().then(tasks.prototype),
|
|
84
|
+
useNjk ? tasks.markdown().then(tasks.prototype) : tasks.markdown2().then(tasks.prototype2),
|
|
61
85
|
tasks.styles(),
|
|
62
86
|
tasks.scripts()
|
|
63
87
|
]);
|
|
@@ -65,7 +89,7 @@ const paraDev = () => {
|
|
|
65
89
|
|
|
66
90
|
const paraProd = () => {
|
|
67
91
|
return Promise.all([
|
|
68
|
-
tasks.markdown(),
|
|
92
|
+
useNjk ? tasks.markdown() : tasks.markdown2(),
|
|
69
93
|
tasks.styles(),
|
|
70
94
|
tasks.scripts()
|
|
71
95
|
]);
|
|
@@ -83,7 +107,9 @@ const taskMap = {
|
|
|
83
107
|
'clean': tasks.clean,
|
|
84
108
|
'copy': tasks.copy,
|
|
85
109
|
'markdown': tasks.markdown,
|
|
110
|
+
'markdown2': tasks.markdown2,
|
|
86
111
|
'prototype': tasks.prototype,
|
|
112
|
+
'prototype2': tasks.prototype2,
|
|
87
113
|
'scripts': tasks.scripts,
|
|
88
114
|
'setenvdev': setEnvDev,
|
|
89
115
|
'setenvprod': setEnvProd,
|
|
@@ -94,10 +120,16 @@ const taskMap = {
|
|
|
94
120
|
|
|
95
121
|
const task = taskMap[taskName];
|
|
96
122
|
|
|
97
|
-
|
|
123
|
+
if (typeof task !== 'function') {
|
|
124
|
+
console.warn(`Task "${taskName}" not found or is not a function!`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
consoleLogColored('+++ SN Build +++', ['bold', 'blue']);
|
|
98
129
|
console.time(taskName);
|
|
99
130
|
console.log(`Task "${taskName}" started...`);
|
|
100
131
|
task().then(() => {
|
|
132
|
+
console.log('Task ended.');
|
|
101
133
|
taskEnded(taskName);
|
|
102
134
|
}).catch(err => {
|
|
103
135
|
console.error(err);
|
package/lib/config.mjs
CHANGED
|
@@ -14,7 +14,7 @@ class Config {
|
|
|
14
14
|
const cf = configFile || `${cwd()}${this.customConfigFile}`;
|
|
15
15
|
const mod = await import(cf);
|
|
16
16
|
const customConf = mod.default;
|
|
17
|
-
return this.
|
|
17
|
+
return this.mergeConfObj(defaultConf, customConf);
|
|
18
18
|
} catch (e) {
|
|
19
19
|
// file not exist
|
|
20
20
|
console.error('Error loading file: ', e);
|
|
@@ -22,9 +22,9 @@ class Config {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// keep nested obj values while merging
|
|
25
|
+
// keep nested obj values while merging (only merges one level deep)
|
|
26
26
|
// e.g. {foo:{a:1,b:2}} merged with {foo:{b:3,c:4}} will be {foo:{a:1,b:3,c:4}}
|
|
27
|
-
|
|
27
|
+
mergeConfObj(defaultConf, customConf) {
|
|
28
28
|
const conf = Object.assign({}, defaultConf);
|
|
29
29
|
for (const [k, v] of Object.entries(customConf)) {
|
|
30
30
|
if (this.objsToMerge.includes(k)) {
|
|
@@ -89,12 +89,13 @@ class Config {
|
|
|
89
89
|
*/
|
|
90
90
|
getCopyPairs = (copyPairs, sourceBase, targetBase, appendSourcePath = false) => {
|
|
91
91
|
if (copyPairs && Array.isArray(copyPairs) && copyPairs.length > 0) {
|
|
92
|
-
return copyPairs.map(
|
|
92
|
+
return copyPairs.map(pair => {
|
|
93
|
+
const pc = {...pair};
|
|
93
94
|
if (appendSourcePath) {
|
|
94
|
-
|
|
95
|
+
pc.sources = `${sourceBase}${pc.sources}`;
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
-
return
|
|
97
|
+
pc.target = `${targetBase}${pc.target}`;
|
|
98
|
+
return pc;
|
|
98
99
|
});
|
|
99
100
|
} else {
|
|
100
101
|
return [];
|
package/lib/is-prod.mjs
CHANGED
package/lib/tasks/clean.mjs
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {rm} from 'node:fs/promises';
|
|
2
|
+
import {consoleLogColored} from '../utils.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Clean the target directory
|
|
5
6
|
*
|
|
7
|
+
* @example
|
|
8
|
+
* await clean('target');
|
|
9
|
+
*
|
|
6
10
|
* @param {(string|string[])} targetPath
|
|
7
|
-
* @returns {Promise<
|
|
11
|
+
* @returns {Promise<Awaited<void>[]>}
|
|
8
12
|
*/
|
|
9
13
|
const clean = targetPath => {
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
consoleLogColored('=> clean');
|
|
15
|
+
let pa = (Array.isArray(targetPath)) ? targetPath : [targetPath];
|
|
16
|
+
return Promise.all(pa.map(path => rm(path, {force: true, recursive: true})));
|
|
12
17
|
};
|
|
13
18
|
|
|
14
19
|
export {clean};
|
package/lib/tasks/copy.mjs
CHANGED
|
@@ -1,15 +1,61 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {cp} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {glob2Array, consoleLogColored, isDebugMode} from '../utils.mjs';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Copy (different) assets
|
|
5
7
|
*
|
|
6
8
|
* @param {STPair[]} sourceTargetPairs
|
|
7
|
-
* @return {Promise<Awaited<
|
|
9
|
+
* @return {Promise<Awaited<Awaited<void>[]>[]>}
|
|
8
10
|
*/
|
|
9
11
|
const copy = sourceTargetPairs => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
consoleLogColored('=> copy');
|
|
13
|
+
const debug = isDebugMode();
|
|
14
|
+
return Promise.all(sourceTargetPairs.map(pair => globCopy(pair.sources, pair.target, pair.opts, debug)));
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Copy files specified as glob pattern(s)
|
|
19
|
+
*
|
|
20
|
+
* @param {string|string[]} globPatterns
|
|
21
|
+
* @param {string} targetRoot
|
|
22
|
+
* @param {CopyTaskOption} opts
|
|
23
|
+
* @param {boolean} verbose
|
|
24
|
+
* @returns {Promise<Awaited<void>[]>}
|
|
25
|
+
*/
|
|
26
|
+
const globCopy = async (globPatterns, targetRoot, opts, verbose = false) => {
|
|
27
|
+
/** @type string[] */
|
|
28
|
+
const sources = await glob2Array(globPatterns);
|
|
29
|
+
|
|
30
|
+
return Promise.all(sources.map(async src => {
|
|
31
|
+
const targetPath = opts.flat
|
|
32
|
+
? path.join(targetRoot, path.basename(src))
|
|
33
|
+
: path.join(targetRoot, genTargetPath(src, opts.levelRemoval));
|
|
34
|
+
if (verbose) {
|
|
35
|
+
consoleLogColored(`copying ${src} -> ${targetPath}`, 'dim');
|
|
36
|
+
}
|
|
37
|
+
return cp(src, targetPath, {recursive: true});
|
|
38
|
+
}));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate the target path (destination) of the file to be copied
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* genTargetPath('src/foo/bar/a.txt', 2)
|
|
46
|
+
* // returns 'bar/a.txt', 2 levels of path (src/foo) are removed
|
|
47
|
+
*
|
|
48
|
+
* @param {string} sourcePath
|
|
49
|
+
* @param {number=} levelRemoval How many levels of path to be removed
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
const genTargetPath = (sourcePath, levelRemoval = 0) => {
|
|
53
|
+
const pathParts = sourcePath.split(path.sep);
|
|
54
|
+
const pathPartsCount = pathParts.length - 1;
|
|
55
|
+
const removal = levelRemoval ? Math.min(levelRemoval, pathPartsCount) : pathPartsCount;
|
|
56
|
+
const newPathParts = pathParts.slice(removal);
|
|
57
|
+
return newPathParts.join(path.sep);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export {copy, genTargetPath};
|
|
15
61
|
export default copy;
|
package/lib/tasks/markdown.mjs
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
import {globby} from 'globby';
|
|
2
1
|
import {readFile} from 'node:fs/promises';
|
|
3
2
|
import {marked} from 'marked';
|
|
4
3
|
import {markedHighlight} from 'marked-highlight';
|
|
5
4
|
import {markedXhtml} from 'marked-xhtml';
|
|
6
5
|
import hljs from 'highlight.js';
|
|
7
6
|
import njk from 'nunjucks';
|
|
7
|
+
import {renderPageToString} from '@changke/jstmpl';
|
|
8
|
+
import {createAndWriteToFile, getTargetPathString, glob2Array, consoleLogColored, isDebugMode} from '../utils.mjs';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
20
|
-
return hljs.highlight(code, {language}).value;
|
|
21
|
-
}
|
|
22
|
-
}));
|
|
10
|
+
marked.use(
|
|
11
|
+
markedXhtml(),
|
|
12
|
+
markedHighlight({
|
|
13
|
+
langPrefix: 'hljs language-',
|
|
14
|
+
highlight: (code, lang) => {
|
|
15
|
+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
16
|
+
return hljs.highlight(code, {language}).value;
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
);
|
|
23
20
|
|
|
24
21
|
const {Environment, FileSystemLoader} = njk;
|
|
25
22
|
|
|
@@ -39,6 +36,8 @@ const getPageTitle = content => {
|
|
|
39
36
|
return t;
|
|
40
37
|
};
|
|
41
38
|
|
|
39
|
+
const mdArticleTemplateFile = 'md.tmpl.mjs'; // TODO: configurable?
|
|
40
|
+
|
|
42
41
|
/**
|
|
43
42
|
* Generate docs using markdown and Nunjucks
|
|
44
43
|
*
|
|
@@ -49,8 +48,8 @@ const getPageTitle = content => {
|
|
|
49
48
|
* @return {Promise<string[]>}
|
|
50
49
|
*/
|
|
51
50
|
const markdown = (mdSourcePathRoot, njkPaths, njkGlobals, mdTargetPath) => {
|
|
52
|
-
|
|
53
|
-
return
|
|
51
|
+
consoleLogColored('=> markdown (njk)');
|
|
52
|
+
return glob2Array(`${mdSourcePathRoot}/**/*.md`).then(docs => {
|
|
54
53
|
const njkEnv = new Environment(new FileSystemLoader(njkPaths, {}));
|
|
55
54
|
for (const [k, v] of Object.entries(njkGlobals)) {
|
|
56
55
|
njkEnv.addGlobal(k, v);
|
|
@@ -85,5 +84,42 @@ const markdown = (mdSourcePathRoot, njkPaths, njkGlobals, mdTargetPath) => {
|
|
|
85
84
|
});
|
|
86
85
|
};
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
const markdown2 = (mdSourcePathRoot, data, mdTargetPath) => {
|
|
88
|
+
const debug = isDebugMode();
|
|
89
|
+
consoleLogColored('=> markdown2 (jstmpl)');
|
|
90
|
+
return glob2Array(`${mdSourcePathRoot}/**/*.md`).then(docs => {
|
|
91
|
+
return Promise.all(docs.map(async doc => {
|
|
92
|
+
const mdContent = await readFile(doc, {encoding: 'utf8'});
|
|
93
|
+
const mdHtml = marked.parse(mdContent);
|
|
94
|
+
const pageTitle = getPageTitle(mdContent);
|
|
95
|
+
const res = await renderPageToString(`${mdSourcePathRoot}/${mdArticleTemplateFile}`, {
|
|
96
|
+
...data,
|
|
97
|
+
title: pageTitle,
|
|
98
|
+
content: mdHtml
|
|
99
|
+
});
|
|
100
|
+
const target = getTargetPathString(
|
|
101
|
+
doc,
|
|
102
|
+
mdSourcePathRoot,
|
|
103
|
+
mdTargetPath,
|
|
104
|
+
'md',
|
|
105
|
+
'html'
|
|
106
|
+
);
|
|
107
|
+
if (debug) {
|
|
108
|
+
consoleLogColored(`md: ${doc} -> ${target}`, 'dim');
|
|
109
|
+
}
|
|
110
|
+
await createAndWriteToFile(target, res);
|
|
111
|
+
// create a content-only file for RSS generating
|
|
112
|
+
const feedTarget = getTargetPathString(
|
|
113
|
+
doc,
|
|
114
|
+
mdSourcePathRoot,
|
|
115
|
+
mdTargetPath,
|
|
116
|
+
'md',
|
|
117
|
+
'txt'
|
|
118
|
+
);
|
|
119
|
+
await createAndWriteToFile(feedTarget, mdHtml);
|
|
120
|
+
}));
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export {markdown, markdown2};
|
|
89
125
|
export default markdown;
|
package/lib/tasks/prototype.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import njk from 'nunjucks';
|
|
2
|
-
import {globby} from 'globby';
|
|
3
2
|
import {readFile} from 'node:fs/promises';
|
|
4
|
-
|
|
5
|
-
import {getTargetPathString, createAndWriteToFile} from '../utils.mjs';
|
|
3
|
+
import {renderPageToString} from '@changke/jstmpl';
|
|
4
|
+
import {getTargetPathString, createAndWriteToFile, glob2Array, consoleLogColored} from '../utils.mjs';
|
|
6
5
|
|
|
7
6
|
const {Environment, FileSystemLoader} = njk;
|
|
8
7
|
|
|
@@ -16,8 +15,8 @@ const {Environment, FileSystemLoader} = njk;
|
|
|
16
15
|
* @return {Promise<string[]>}
|
|
17
16
|
*/
|
|
18
17
|
const prototype = (prototypeSourcePathRoot, njkPaths, njkGlobals, prototypeTargetPath) => {
|
|
19
|
-
|
|
20
|
-
return
|
|
18
|
+
consoleLogColored('=> prototype (njk)');
|
|
19
|
+
return glob2Array(`${prototypeSourcePathRoot}/pages/**/*.njk`).then(pages => {
|
|
21
20
|
const njkEnv = new Environment(new FileSystemLoader([prototypeSourcePathRoot].concat(njkPaths), {}));
|
|
22
21
|
for (const [k, v] of Object.entries(njkGlobals)) {
|
|
23
22
|
njkEnv.addGlobal(k, v);
|
|
@@ -37,5 +36,30 @@ const prototype = (prototypeSourcePathRoot, njkPaths, njkGlobals, prototypeTarge
|
|
|
37
36
|
});
|
|
38
37
|
};
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Generate static pages with JSTmpl
|
|
41
|
+
* @param {string} sourceRoot - Glob pattern for source files e.g. 'src/pages/*.mjs'
|
|
42
|
+
* @param {Object.<string, string>} data
|
|
43
|
+
* @param {string} targetPath
|
|
44
|
+
* @returns {Promise<void>}
|
|
45
|
+
*/
|
|
46
|
+
const prototype2 = (sourceRoot, data, targetPath) => {
|
|
47
|
+
consoleLogColored('=> prototype (jstmpl)');
|
|
48
|
+
// src/main/webapp/prototype/pages/index.mjs -> ${targetPath.prototype}/index.html
|
|
49
|
+
return glob2Array(`${sourceRoot}/pages/**/*.mjs`).then(pages => {
|
|
50
|
+
return Promise.all(pages.map(async page => {
|
|
51
|
+
const res = await renderPageToString(page, data);
|
|
52
|
+
const targetFile = getTargetPathString(
|
|
53
|
+
page,
|
|
54
|
+
`${sourceRoot}/pages`,
|
|
55
|
+
targetPath,
|
|
56
|
+
'mjs',
|
|
57
|
+
page.endsWith('rss/index.mjs') ? 'xml' : 'html'
|
|
58
|
+
);
|
|
59
|
+
await createAndWriteToFile(targetFile, res);
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export {prototype, prototype2};
|
|
41
65
|
export default prototype;
|
package/lib/tasks/scripts.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {build} from 'esbuild';
|
|
2
|
-
|
|
3
2
|
import isEnvProd from '../is-prod.mjs';
|
|
3
|
+
import {consoleLogColored} from '../utils.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Transpile / bundle JS files using esbuild
|
|
@@ -10,12 +10,12 @@ import isEnvProd from '../is-prod.mjs';
|
|
|
10
10
|
* @return {Promise}
|
|
11
11
|
*/
|
|
12
12
|
const esm = (entries, targetPath) => {
|
|
13
|
-
|
|
13
|
+
consoleLogColored('=> scripts');
|
|
14
14
|
return build({
|
|
15
15
|
entryPoints: entries,
|
|
16
16
|
bundle: true,
|
|
17
17
|
format: 'esm',
|
|
18
|
-
target: '
|
|
18
|
+
target: 'es2024',
|
|
19
19
|
splitting: true,
|
|
20
20
|
outdir: targetPath,
|
|
21
21
|
minify: isEnvProd(), // only minify in prod build
|
package/lib/tasks/styles.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {build} from 'esbuild';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {glob2Array, consoleLogColored} from '../utils.mjs';
|
|
4
3
|
import isEnvProd from '../is-prod.mjs';
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -12,16 +11,16 @@ import isEnvProd from '../is-prod.mjs';
|
|
|
12
11
|
* @return {Promise}
|
|
13
12
|
*/
|
|
14
13
|
const styles = (sources, targetPath, external = []) => {
|
|
15
|
-
|
|
16
|
-
return
|
|
14
|
+
consoleLogColored('=> styles');
|
|
15
|
+
return glob2Array(sources).then(entries => {
|
|
17
16
|
return build({
|
|
18
17
|
entryPoints: entries,
|
|
19
18
|
bundle: true,
|
|
20
|
-
target: '
|
|
19
|
+
target: 'es2024',
|
|
21
20
|
outdir: targetPath,
|
|
22
21
|
sourcemap: !isEnvProd(),
|
|
23
22
|
minify: isEnvProd(),
|
|
24
|
-
external
|
|
23
|
+
external
|
|
25
24
|
});
|
|
26
25
|
});
|
|
27
26
|
};
|
package/lib/typedefs.mjs
CHANGED
|
@@ -14,13 +14,21 @@
|
|
|
14
14
|
* @property {string=} markdown Path for markdown files
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Options for "copy" task
|
|
19
|
+
*
|
|
20
|
+
* @typedef {Object} CopyTaskOption
|
|
21
|
+
* @property {number=} levelRemoval
|
|
22
|
+
* @property {boolean=} flat
|
|
23
|
+
*/
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Source/Target pairs for "copy" task
|
|
19
27
|
*
|
|
20
28
|
* @typedef {Object} STPair
|
|
21
29
|
* @property {(string|string[])} sources
|
|
22
30
|
* @property {string} target
|
|
23
|
-
* @property {
|
|
31
|
+
* @property {CopyTaskOption=} opts
|
|
24
32
|
*/
|
|
25
33
|
|
|
26
34
|
/**
|
package/lib/utils.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {access, mkdir, writeFile} from 'node:fs/promises';
|
|
2
|
-
import
|
|
1
|
+
import {access, mkdir, writeFile, glob, rm, stat} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {styleText} from 'node:util';
|
|
4
|
+
import {env} from 'node:process';
|
|
3
5
|
|
|
4
6
|
const getTargetPathString = (sourcePath, sourceRoot, targetRoot, srcExt, tgtExt) => {
|
|
5
7
|
const baseName = path.basename(sourcePath, `.${srcExt}`);
|
|
@@ -17,7 +19,98 @@ const createAndWriteToFile = async (filePath, fileContent) => {
|
|
|
17
19
|
await writeFile(filePath, fileContent, {flag: 'w+'});
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Extracts negative patterns into another array to feed Node's glob function
|
|
24
|
+
* @param {(string|string[])} globPatterns
|
|
25
|
+
* @returns {Object.<string, string[]>}
|
|
26
|
+
*/
|
|
27
|
+
const processGlobPatterns = globPatterns => {
|
|
28
|
+
// extract negative patterns
|
|
29
|
+
let patterns = globPatterns;
|
|
30
|
+
if (!Array.isArray(globPatterns)) {
|
|
31
|
+
patterns = [globPatterns];
|
|
32
|
+
}
|
|
33
|
+
/** @type string[] */
|
|
34
|
+
const negativePatterns = [];
|
|
35
|
+
/** @type string[] */
|
|
36
|
+
const normalPatterns = patterns.filter(p => {
|
|
37
|
+
if (p.startsWith('!')) {
|
|
38
|
+
negativePatterns.push(p.substring(1));
|
|
39
|
+
return false;
|
|
40
|
+
} else {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return {normalPatterns, negativePatterns};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convert `glob()` result to an array
|
|
49
|
+
* @param {(string|string[])} globPatterns
|
|
50
|
+
* @param {boolean} fileOnly
|
|
51
|
+
* @returns {Promise<string[]>}
|
|
52
|
+
*/
|
|
53
|
+
const glob2Array = async (globPatterns, fileOnly = false) => {
|
|
54
|
+
const p = processGlobPatterns(globPatterns);
|
|
55
|
+
const m = glob(p.normalPatterns, {exclude: p.negativePatterns});
|
|
56
|
+
if (fileOnly) {
|
|
57
|
+
const files = [];
|
|
58
|
+
return Array.fromAsync(m).then(async entries => {
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
const st = await stat(entry);
|
|
61
|
+
if (st.isFile()) {
|
|
62
|
+
files.push(entry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return files;
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
return Array.fromAsync(m);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const createDirectory = async (dir, log = false) => {
|
|
73
|
+
try {
|
|
74
|
+
const dirUrl = new URL(dir, import.meta.url);
|
|
75
|
+
const createdDir = await mkdir(dirUrl, {recursive: true});
|
|
76
|
+
if (log) {
|
|
77
|
+
console.log(`Directory created: ${createdDir}`);
|
|
78
|
+
}
|
|
79
|
+
return createdDir;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(err.message);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const removeDirectory = async (dir, log = false) => {
|
|
86
|
+
try {
|
|
87
|
+
await rm(dir, {recursive: true, force: true});
|
|
88
|
+
if (log) {
|
|
89
|
+
console.log(`Directory removed: ${dir}`);
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(err.message);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Print colored console messages
|
|
98
|
+
* @param {string} msg
|
|
99
|
+
* @param {(string|string[])} color
|
|
100
|
+
*/
|
|
101
|
+
const consoleLogColored = (msg, color = 'cyan') => {
|
|
102
|
+
console.log(styleText(color, msg));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const isDebugMode = () => (env.SN_BUILD_MODE === 'debug');
|
|
106
|
+
|
|
20
107
|
export {
|
|
21
108
|
getTargetPathString,
|
|
22
|
-
createAndWriteToFile
|
|
109
|
+
createAndWriteToFile,
|
|
110
|
+
processGlobPatterns,
|
|
111
|
+
glob2Array,
|
|
112
|
+
createDirectory,
|
|
113
|
+
removeDirectory,
|
|
114
|
+
consoleLogColored,
|
|
115
|
+
isDebugMode
|
|
23
116
|
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@changke/staticnext-build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Build scripts extracted from StaticNext seed project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint . lib test",
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "node --test 'test/*.test.mjs'",
|
|
9
9
|
"prepublishOnly": "npm run lint && npm run test"
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=
|
|
12
|
+
"node": ">=22.14.0"
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
15
|
"sn-build": "build.mjs"
|
|
@@ -19,21 +19,18 @@
|
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"type": "module",
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@eslint/js": "^9.
|
|
23
|
-
"@stylistic/eslint-plugin": "^5.
|
|
24
|
-
"eslint": "^9.
|
|
25
|
-
"globals": "^16.
|
|
26
|
-
"mocha": "^11.7.2"
|
|
22
|
+
"@eslint/js": "^9.39.2",
|
|
23
|
+
"@stylistic/eslint-plugin": "^5.8.0",
|
|
24
|
+
"eslint": "^9.39.2",
|
|
25
|
+
"globals": "^16.5.0"
|
|
27
26
|
},
|
|
28
27
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"esbuild": "^0.25.10",
|
|
32
|
-
"globby": "^14.1.0",
|
|
28
|
+
"@changke/jstmpl": "^0.0.1",
|
|
29
|
+
"esbuild": "^0.27.3",
|
|
33
30
|
"highlight.js": "^11.11.1",
|
|
34
|
-
"marked": "^
|
|
35
|
-
"marked-highlight": "^2.2.
|
|
36
|
-
"marked-xhtml": "^1.0.
|
|
31
|
+
"marked": "^17.0.2",
|
|
32
|
+
"marked-highlight": "^2.2.3",
|
|
33
|
+
"marked-xhtml": "^1.0.14",
|
|
37
34
|
"nunjucks": "^3.2.4"
|
|
38
35
|
}
|
|
39
36
|
}
|
package/eslint.config.mjs
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import globals from 'globals';
|
|
2
|
-
import js from '@eslint/js';
|
|
3
|
-
import stylistic from '@stylistic/eslint-plugin';
|
|
4
|
-
|
|
5
|
-
export default [
|
|
6
|
-
js.configs.recommended,
|
|
7
|
-
stylistic.configs.customize({
|
|
8
|
-
blockSpacing: false,
|
|
9
|
-
braceStyle: '1tbs',
|
|
10
|
-
semi: true
|
|
11
|
-
}),
|
|
12
|
-
{
|
|
13
|
-
files: ['**/*.mjs'],
|
|
14
|
-
languageOptions: {
|
|
15
|
-
globals: {
|
|
16
|
-
...globals.node,
|
|
17
|
-
...globals.mocha
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
rules: {
|
|
21
|
-
'@stylistic/arrow-parens': ['error', 'as-needed', {requireForBlockBody: false}],
|
|
22
|
-
'@stylistic/comma-dangle': ['error', 'never'],
|
|
23
|
-
'@stylistic/object-curly-spacing': ['error', 'never'],
|
|
24
|
-
'@stylistic/quotes': ['warn', 'single'],
|
|
25
|
-
'@stylistic/quote-props': 0,
|
|
26
|
-
'@stylistic/semi': ['error', 'always'],
|
|
27
|
-
'@stylistic/space-before-function-paren': ['error', {anonymous: 'ignore', asyncArrow: 'always', named: 'never'}]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
];
|