mikser-io 10.0.2 → 10.0.4
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/docs/configuration.md +1 -1
- package/package.json +1 -1
- package/src/config.js +12 -0
- package/src/engine.js +16 -0
- package/src/manifest.js +6 -3
- package/src/plugins/assets.js +11 -0
- package/src/plugins/files.js +32 -25
package/docs/configuration.md
CHANGED
|
@@ -47,7 +47,7 @@ These options are part of `runtime.options` and apply to the engine itself.
|
|
|
47
47
|
| `outputFolder` | `-o, --output-folder` | string | `out` | Folder where rendered output is written. |
|
|
48
48
|
| `runtimeFolder` | `-e, --runtime-folder` | string | `runtime` | Folder for temporary files. The engine's sqlite substrate lives at `runtime/mikser.sqlite` (entities, refs, snapshots, journal, schema-version meta all in one file). |
|
|
49
49
|
| `plugins` | — | factory-call[] | `[]` | Array of factory returns. Import the factory by name and call it: `plugins: [documents(), layouts({ cleanUrls: true })]`. Config-only — there's no `--plugins` CLI flag under v9 because identifiers can't be passed via the command line (ADR-0010). |
|
|
50
|
-
| `config` | `-c, --config` | string | `./mikser.config.js` | Path to the config file. |
|
|
50
|
+
| `config` | `-c, --config` | string | `./mikser.config.js` | Path to the config file, relative to `workingFolder` like every other path — so `-c prod.config.js` means the one in the folder you pointed at, and repeating the working folder in the path doubles it. A project with no config file at the default location runs on defaults; a path given here that does not exist is an error, because the alternative is a green build with an empty output folder. |
|
|
51
51
|
| `mode` | `-m, --mode` | string | `development` | Runtime mode, accessible as `runtime.options.mode`. |
|
|
52
52
|
| `clear` | `-r, --clear` | boolean | `false` | Delete `outputFolder` and `runtimeFolder` before each run. |
|
|
53
53
|
| `watch` | `-w, --watch` | boolean | `false` | Watch source folders for changes and rebuild incrementally. |
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -103,6 +103,18 @@ onLoad(async () => {
|
|
|
103
103
|
// during evaluation both exit 1. Module resolution is the only one that
|
|
104
104
|
// needs this to stay in line with them.
|
|
105
105
|
if (!existsSync(configFile)) {
|
|
106
|
+
// Absent by DEFAULT is a project without a config, which is allowed.
|
|
107
|
+
// Absent after the caller named it is a typo, and continuing on
|
|
108
|
+
// defaults answers a question nobody asked: the build prints the path
|
|
109
|
+
// it did not find, reports "No plugins loaded", writes nothing and
|
|
110
|
+
// exits 0. That is the same green-build-empty-output failure the
|
|
111
|
+
// module-resolution note above exists to prevent, reached by the
|
|
112
|
+
// shorter route of getting the path wrong.
|
|
113
|
+
if (runtime.options.configExplicit) {
|
|
114
|
+
throw new Error(`No config file at ${configFile} — it was named with --config, so this is a wrong `
|
|
115
|
+
+ 'path rather than a project without a config. A relative --config resolves against the '
|
|
116
|
+
+ `working folder (${runtime.options.workingFolder}), so it does not repeat it.`)
|
|
117
|
+
}
|
|
106
118
|
logger.debug('No config file at %s — using defaults', configFile)
|
|
107
119
|
} else {
|
|
108
120
|
// No catch: any failure loading a config that EXISTS is fatal.
|
package/src/engine.js
CHANGED
|
@@ -683,6 +683,22 @@ The full version, with what each code means: docs/diagnostics.md`)
|
|
|
683
683
|
// deliberate: engine's onInitialize does setup that the rest of
|
|
684
684
|
// the engine infrastructure depends on; onInitialized does
|
|
685
685
|
// things plugins may need.
|
|
686
|
+
// Whether --config was TYPED, for config.js.
|
|
687
|
+
//
|
|
688
|
+
// Relative config paths resolve against the working folder, not the
|
|
689
|
+
// folder the command was run in — that is what makes the default
|
|
690
|
+
// './mikser.config.js' follow --working-folder, and `-c prod.js`
|
|
691
|
+
// reads as "the prod config of the site I am pointing at", which is
|
|
692
|
+
// how it is used. Deliberately unchanged.
|
|
693
|
+
//
|
|
694
|
+
// What was missing is what happens when the path is wrong. Nothing
|
|
695
|
+
// distinguished "this project has no config" from "the config you
|
|
696
|
+
// named is not there", so a mistyped path printed the path, reported
|
|
697
|
+
// no plugins, and exited 0 over an empty output folder. config.js
|
|
698
|
+
// needs to know which case it is in, and only commander can say.
|
|
699
|
+
runtime.options.configExplicit =
|
|
700
|
+
runtime.engine?.commander?.getOptionValueSource?.('config') === 'cli'
|
|
701
|
+
|
|
686
702
|
runtime.options.workingFolder = path.resolve(runtime.options.workingFolder)
|
|
687
703
|
process.chdir(runtime.options.workingFolder)
|
|
688
704
|
|
package/src/manifest.js
CHANGED
|
@@ -1122,9 +1122,12 @@ export function createManifest(db) {
|
|
|
1122
1122
|
// One stat per recorded snapshot, once per cycle, hoisted out of the
|
|
1123
1123
|
// per-file path exactly like checksumsByCollection. The syscalls are
|
|
1124
1124
|
// ~2.2ms per 1842 paths and cost the same whether the files are there
|
|
1125
|
-
// or not
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1125
|
+
// or not. At the build level it does not register: warm rebuilds of
|
|
1126
|
+
// the 10k perf corpus ran 3.36s median with this against 3.56s
|
|
1127
|
+
// without, i.e. nominally faster, which is only to say the difference
|
|
1128
|
+
// is well inside the run-to-run spread (3.0-4.1 vs 3.5-4.7 over five
|
|
1129
|
+
// runs each). Re-measure with `npm run test:perf` before trusting a
|
|
1130
|
+
// claim that it got slower.
|
|
1128
1131
|
//
|
|
1129
1132
|
// Resolves destinations through resolveOutputPath, which is what
|
|
1130
1133
|
// auditOutput uses. The two must agree — `--audit-output` reporting
|
package/src/plugins/assets.js
CHANGED
|
@@ -348,6 +348,17 @@ export function assets(options = {}) {
|
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
async function isPresetRendered(entity) {
|
|
351
|
+
// The marker says a render HAPPENED. It does not say the file it
|
|
352
|
+
// produced is still there, and the two come apart the moment anyone
|
|
353
|
+
// deletes a derivative — which is the obvious thing to do when you
|
|
354
|
+
// want one rebuilt. The marker survives, this returned true, and the
|
|
355
|
+
// image never came back on any number of ordinary builds; only
|
|
356
|
+
// --force or a config change brought it round, and --audit-output was
|
|
357
|
+
// the only thing that ever said a file was missing.
|
|
358
|
+
//
|
|
359
|
+
// Checked first and cheaply: one stat, and if the derivative is gone
|
|
360
|
+
// no amount of marker archaeology changes the answer.
|
|
361
|
+
if (!existsSync(entity.destination)) return false
|
|
351
362
|
let result = false
|
|
352
363
|
let revisions = []
|
|
353
364
|
const assetChecksum = `${entity.destination}.${entity.preset.checksum}.md5`
|
package/src/plugins/files.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdir, symlink, unlink, lstat, realpath } from 'fs/promises'
|
|
|
4
4
|
import { globby } from 'globby'
|
|
5
5
|
import pMap from 'p-map'
|
|
6
6
|
import { checksumsByCollection } from '../catalog.js'
|
|
7
|
-
import { sweepDeleted } from '../source.js'
|
|
7
|
+
import { gateChecksum, sweepDeleted } from '../source.js'
|
|
8
8
|
|
|
9
9
|
export function files(options = {}) {
|
|
10
10
|
return ({
|
|
@@ -187,23 +187,32 @@ export function files(options = {}) {
|
|
|
187
187
|
|
|
188
188
|
const paths = await globby('**/*', { cwd: runtime.options.filesFolder })
|
|
189
189
|
trackProgress('Files import', paths.length)
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
190
|
+
// The SHARED gate, not a copy of it.
|
|
191
|
+
//
|
|
192
|
+
// Without a gate the plugin re-emitted createEntity on every cycle
|
|
193
|
+
// for every file regardless of changes, inflating the journal with
|
|
194
|
+
// phantom mutations and triggering downstream re-dispatch of
|
|
195
|
+
// aggregate layouts whose recorded query deps matched the
|
|
196
|
+
// collection. So it grew one — and then it was a SECOND,
|
|
197
|
+
// independent implementation of the decision source.js already
|
|
198
|
+
// owns, which is how it silently missed the missing-output bypass
|
|
199
|
+
// that was added to the original: an image derivative deleted from
|
|
200
|
+
// the assets folder was never regenerated, because the file it
|
|
201
|
+
// derives from was gated here as unchanged and never reached the
|
|
202
|
+
// assets plugin. Green build, missing image, and only
|
|
203
|
+
// --audit-output said otherwise.
|
|
204
|
+
//
|
|
205
|
+
// Every reason to defeat the gate — --force, a wiped catalog, a
|
|
206
|
+
// recorded output that is gone — now lives in exactly one place,
|
|
207
|
+
// so the next one cannot be added to that place and missed here.
|
|
208
|
+
//
|
|
209
|
+
// priorChecksums is still bulk-prefetched per scan so the gate
|
|
210
|
+
// reads a map instead of doing per-file SQL, and missingOutputs
|
|
211
|
+
// alongside it for the same reason; the manifest memoizes the
|
|
212
|
+
// latter for the cycle, so this shares the walk with useSource's
|
|
213
|
+
// own scans rather than paying for a second one.
|
|
206
214
|
const priorChecksums = checksumsByCollection(collection)
|
|
215
|
+
const missingOutputs = runtime.manifest?.missingOutputIds() ?? new Set()
|
|
207
216
|
const scanned = new Set()
|
|
208
217
|
await pMap(paths, async relativePath => {
|
|
209
218
|
const { source } = await ensureLink(relativePath)
|
|
@@ -217,15 +226,13 @@ export function files(options = {}) {
|
|
|
217
226
|
// to re-emit; the sweep asks what still EXISTS, and a file
|
|
218
227
|
// skipped for being unchanged very much exists.
|
|
219
228
|
scanned.add(id)
|
|
220
|
-
|
|
229
|
+
// Returns the checksum to stamp on the entity, or null when
|
|
230
|
+
// the catalog already has this file unchanged and there is
|
|
231
|
+
// nothing to emit. Progress ticks either way — a gated file
|
|
232
|
+
// was still looked at.
|
|
233
|
+
const newChecksum = await gateChecksum(source, id, { priorChecksums, missingOutputs })
|
|
221
234
|
updateProgress()
|
|
222
|
-
|
|
223
|
-
// checksum, the file hasn't changed since the last cycle.
|
|
224
|
-
// Skip emitting a CREATE — the catalog row stays correct,
|
|
225
|
-
// the journal stays accurate (mutations = actual changes),
|
|
226
|
-
// and downstream aggregate-layout invalidation isn't fired
|
|
227
|
-
// spuriously.
|
|
228
|
-
if (!forced && priorChecksums.get(id) === newChecksum) return
|
|
235
|
+
if (newChecksum === null) return
|
|
229
236
|
await createEntity({
|
|
230
237
|
id,
|
|
231
238
|
uri,
|