fundus 0.0.4 → 0.2.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 +49 -8
- package/build/client/_app/immutable/assets/0.inkPq3ni.css +1 -0
- package/build/client/_app/immutable/assets/0.inkPq3ni.css.br +0 -0
- package/build/client/_app/immutable/assets/0.inkPq3ni.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css +1 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css.br +0 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css.gz +0 -0
- package/build/client/_app/immutable/assets/{LibraryContextMenu.CMefGNE2.css → LibraryContextMenu.BYvU4SY7.css} +1 -1
- package/build/client/_app/immutable/assets/LibraryContextMenu.BYvU4SY7.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.BYvU4SY7.css.gz +0 -0
- package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css +1 -0
- package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css.br +0 -0
- package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js +1 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DVEQFclp.js → ByVSyIj7.js} +1 -1
- package/build/client/_app/immutable/chunks/ByVSyIj7.js.br +1 -0
- package/build/client/_app/immutable/chunks/ByVSyIj7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js +1 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js.br +0 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js +1 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js.br +0 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CNRz3sSw.js → D65cSBhe.js} +2 -2
- package/build/client/_app/immutable/chunks/D65cSBhe.js.br +0 -0
- package/build/client/_app/immutable/chunks/D65cSBhe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.js +108 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.js.br +0 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js +1 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js.br +0 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js +2 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.br +0 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js +1 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js +2 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js +1 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js +3 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js +1 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js +2 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/favicon.svg +4 -0
- package/build/client/favicon.svg.br +0 -0
- package/build/client/favicon.svg.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-BUZqFfWG.js → context.js-B77ZAVLi.js} +405 -31
- package/build/server/chunks/chunks/context.js-B77ZAVLi.js.map +1 -0
- package/build/server/chunks/chunks/{core.js-xfrkaQpS.js → core.js-EA_ysb1J.js} +130 -3
- package/build/server/chunks/chunks/core.js-EA_ysb1J.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-C0rwMMtk.js → exports.js-DvhE7Ffd.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-C0rwMMtk.js.map → exports.js-DvhE7Ffd.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-DvopzwKh.js → http.js-DyoR9XRI.js} +5 -3
- package/build/server/chunks/chunks/http.js-DyoR9XRI.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-DGo2jPcg.js → internal.js-Dn37LrAW.js} +4 -4
- package/build/server/chunks/chunks/{internal.js-DGo2jPcg.js.map → internal.js-Dn37LrAW.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{library-store.js-ZLudCJlT.js → library-store.js-DJ-JRE-g.js} +388 -20
- package/build/server/chunks/chunks/library-store.js-DJ-JRE-g.js.map +1 -0
- package/build/server/chunks/chunks/{shared.js-B1gC9MiJ.js → shared.js-Dob0TlTu.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-B1gC9MiJ.js.map → shared.js-Dob0TlTu.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-DnEJ3UjO.js → utils.js-B4YimMxv.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-DnEJ3UjO.js.map → utils.js-B4YimMxv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Cq5gXVQT.js → _server.ts.js-H4Pq1KAE.js} +4 -5
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Cq5gXVQT.js.map → _server.ts.js-H4Pq1KAE.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-RXbedi8q.js → _server.ts.js-BI3KHSfE.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-RXbedi8q.js.map → _server.ts.js-BI3KHSfE.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-BlNYUbXX.js +34 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-BlNYUbXX.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-D7Qae9vU.js → _server.ts.js-j-_N540j.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-j-_N540j.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DtO20kgB.js → _server.ts.js-BHKobzAg.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DtO20kgB.js.map → _server.ts.js-BHKobzAg.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BUebPbd5.js → _server.ts.js-BFKjIOCv.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BUebPbd5.js.map → _server.ts.js-BFKjIOCv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BjT2qPMT.js → _server.ts.js-BxZeVw8y.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BjT2qPMT.js.map → _server.ts.js-BxZeVw8y.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DYRLsoT7.js → _server.ts.js-CO36U_CT.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DYRLsoT7.js.map → _server.ts.js-CO36U_CT.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BbN2fGNN.js → _server.ts.js-CBlg1Bkl.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BbN2fGNN.js.map → _server.ts.js-CBlg1Bkl.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-b_MKZ41b.js → _server.ts.js-DcGLCjIs.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-b_MKZ41b.js.map → _server.ts.js-DcGLCjIs.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/_server.ts.js-AqhtjOiL.js +32 -0
- package/build/server/chunks/entries/endpoints/api/raw/adopt/_server.ts.js-AqhtjOiL.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/_server.ts.js-CSa6-DcU.js +30 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/_server.ts.js-CSa6-DcU.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/_server.ts.js-DnzZUoAo.js +30 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/_server.ts.js-DnzZUoAo.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/reconcile/_server.ts.js-B0KMprKq.js +33 -0
- package/build/server/chunks/entries/endpoints/api/reconcile/_server.ts.js-B0KMprKq.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-evEfMpvn.js → _server.ts.js-B1N9o1Ne.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-evEfMpvn.js.map → _server.ts.js-B1N9o1Ne.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-C04XE_1w.js → _server.ts.js-B6HbnRqq.js} +4 -5
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-C04XE_1w.js.map → _server.ts.js-B6HbnRqq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DB8TdoEv.js → _server.ts.js-7wzPyG9v.js} +5 -6
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DB8TdoEv.js.map → _server.ts.js-7wzPyG9v.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BrpaijZO.js → _server.ts.js-BWztxYge.js} +5 -6
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BrpaijZO.js.map → _server.ts.js-BWztxYge.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-BJJwZW_I.js → hooks.server.js-CVKLksJ1.js} +4 -5
- package/build/server/chunks/entries/{hooks.server.js-BJJwZW_I.js.map → hooks.server.js-CVKLksJ1.js.map} +1 -1
- package/build/server/chunks/{handler-DnbS2t1B.js → handler-DZ2Sps4a.js} +5 -5
- package/build/server/chunks/{handler-DnbS2t1B.js.map → handler-DZ2Sps4a.js.map} +1 -1
- package/build/server/chunks/{index.js-g77q3Boj.js → index.js-CdmTWQz8.js} +5 -5
- package/build/server/chunks/{index.js-g77q3Boj.js.map → index.js-CdmTWQz8.js.map} +1 -1
- package/build/server/chunks/{manifest.js-qmEXbstS.js → manifest.js-GhfNr35x.js} +57 -22
- package/build/server/chunks/manifest.js-GhfNr35x.js.map +1 -0
- package/build/server/chunks/nodes/0.js-DGT86_i-.js +11 -0
- package/build/server/chunks/nodes/0.js-DGT86_i-.js.map +1 -0
- package/build/server/chunks/nodes/1.js-DQgYreU0.js +7 -0
- package/build/server/chunks/nodes/{1.js-H-qwo4GJ.js.map → 1.js-DQgYreU0.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-DItreSrX.js +7 -0
- package/build/server/chunks/nodes/2.js-DItreSrX.js.map +1 -0
- package/build/server/chunks/nodes/3.js-bE1dJ_6v.js +7 -0
- package/build/server/chunks/nodes/3.js-bE1dJ_6v.js.map +1 -0
- package/build/server/chunks/nodes/4.js-BebbMVXy.js +7 -0
- package/build/server/chunks/nodes/{4.js-BKL7LSSm.js.map → 4.js-BebbMVXy.js.map} +1 -1
- package/dist/cli.js +32 -9
- package/dist/core/api-types.d.ts +39 -1
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +2 -1
- package/dist/core/ingest.d.ts +2 -0
- package/dist/core/ingest.js +7 -0
- package/dist/core/raw-path.d.ts +8 -0
- package/dist/core/raw-path.js +63 -0
- package/dist/server/build.js +11 -2
- package/dist/server/check.d.ts +5 -1
- package/dist/server/check.js +80 -28
- package/dist/server/cli-commands.d.ts +3 -2
- package/dist/server/cli-commands.js +47 -4
- package/dist/server/cli-init.d.ts +1 -1
- package/dist/server/cli-init.js +38 -5
- package/dist/server/http.js +5 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/library-store.d.ts +23 -0
- package/dist/server/library-store.js +82 -14
- package/dist/server/operations.d.ts +12 -2
- package/dist/server/operations.js +391 -31
- package/dist/server/processing/processor.js +3 -0
- package/dist/server/raw-reconciliation.d.ts +53 -0
- package/dist/server/raw-reconciliation.js +338 -0
- package/dist/server/raw-safety.d.ts +10 -0
- package/dist/server/raw-safety.js +73 -0
- package/dist/server/start.d.ts +4 -2
- package/dist/server/start.js +43 -11
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/0.DLhIsYA_.css +0 -1
- package/build/client/_app/immutable/assets/0.DLhIsYA_.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DLhIsYA_.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.JwgtcTEd.css +0 -1
- package/build/client/_app/immutable/assets/3.JwgtcTEd.css.br +0 -0
- package/build/client/_app/immutable/assets/3.JwgtcTEd.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.gz +0 -0
- package/build/client/_app/immutable/chunks/CNRz3sSw.js.br +0 -0
- package/build/client/_app/immutable/chunks/CNRz3sSw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CO4RBJfa.js +0 -1
- package/build/client/_app/immutable/chunks/CO4RBJfa.js.br +0 -0
- package/build/client/_app/immutable/chunks/CO4RBJfa.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DOpBpjxS.js +0 -108
- package/build/client/_app/immutable/chunks/DOpBpjxS.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOpBpjxS.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DVEQFclp.js.br +0 -1
- package/build/client/_app/immutable/chunks/DVEQFclp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DiFoWL2c.js +0 -1
- package/build/client/_app/immutable/chunks/DiFoWL2c.js.br +0 -0
- package/build/client/_app/immutable/chunks/DiFoWL2c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DkADHpZF.js +0 -1
- package/build/client/_app/immutable/chunks/DkADHpZF.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkADHpZF.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CKgtYXQb.js +0 -2
- package/build/client/_app/immutable/entry/app.CKgtYXQb.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CKgtYXQb.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CYZu5qE1.js +0 -1
- package/build/client/_app/immutable/entry/start.CYZu5qE1.js.br +0 -0
- package/build/client/_app/immutable/entry/start.CYZu5qE1.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.I_1SApYQ.js +0 -2
- package/build/client/_app/immutable/nodes/0.I_1SApYQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.I_1SApYQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.v6eUTnWq.js +0 -1
- package/build/client/_app/immutable/nodes/1.v6eUTnWq.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.v6eUTnWq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js +0 -3
- package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.C-DO70Bs.js +0 -1
- package/build/client/_app/immutable/nodes/3.C-DO70Bs.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.C-DO70Bs.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CALOuSyz.js +0 -2
- package/build/client/_app/immutable/nodes/4.CALOuSyz.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CALOuSyz.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-BUZqFfWG.js.map +0 -1
- package/build/server/chunks/chunks/core.js-xfrkaQpS.js.map +0 -1
- package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js +0 -64
- package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js.map +0 -1
- package/build/server/chunks/chunks/http.js-DvopzwKh.js.map +0 -1
- package/build/server/chunks/chunks/library-store.js-ZLudCJlT.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-D7Qae9vU.js.map +0 -1
- package/build/server/chunks/manifest.js-qmEXbstS.js.map +0 -1
- package/build/server/chunks/nodes/0.js-Bk6IZ-vD.js +0 -11
- package/build/server/chunks/nodes/0.js-Bk6IZ-vD.js.map +0 -1
- package/build/server/chunks/nodes/1.js-H-qwo4GJ.js +0 -7
- package/build/server/chunks/nodes/2.js-DRpXVLwp.js +0 -7
- package/build/server/chunks/nodes/2.js-DRpXVLwp.js.map +0 -1
- package/build/server/chunks/nodes/3.js-CDEzNFeU.js +0 -7
- package/build/server/chunks/nodes/3.js-CDEzNFeU.js.map +0 -1
- package/build/server/chunks/nodes/4.js-BKL7LSSm.js +0 -7
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open, readdir } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { compareCodeUnits, folderPathError, isFundusTransactionName, portableRawPathKey, rawFilePathError } from '../core/index.js';
|
|
6
|
+
function objectIdentity(value) {
|
|
7
|
+
return `${value.dev}:${value.ino}:${value.mode}`;
|
|
8
|
+
}
|
|
9
|
+
function fileIdentity(value) {
|
|
10
|
+
return `${objectIdentity(value)}:${value.size}:${value.mtimeNs}:${value.ctimeNs}`;
|
|
11
|
+
}
|
|
12
|
+
function pathMatchesHandle(pathStat, handleStat) {
|
|
13
|
+
return pathStat.isFile() && objectIdentity(pathStat) === objectIdentity(handleStat);
|
|
14
|
+
}
|
|
15
|
+
/** Hash one regular file without following its final path component. */
|
|
16
|
+
export async function stableRawFileHash(path) {
|
|
17
|
+
let lastProblem;
|
|
18
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
19
|
+
try {
|
|
20
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
21
|
+
try {
|
|
22
|
+
const before = await handle.stat({ bigint: true });
|
|
23
|
+
const pathBefore = await lstat(path, { bigint: true });
|
|
24
|
+
if (!before.isFile() || !pathMatchesHandle(pathBefore, before)) {
|
|
25
|
+
throw new Error('path is not the opened regular file');
|
|
26
|
+
}
|
|
27
|
+
const hash = createHash('sha256');
|
|
28
|
+
for await (const chunk of handle.createReadStream({ autoClose: false }))
|
|
29
|
+
hash.update(chunk);
|
|
30
|
+
const after = await handle.stat({ bigint: true });
|
|
31
|
+
const pathAfter = await lstat(path, { bigint: true });
|
|
32
|
+
if (fileIdentity(before) === fileIdentity(after) && pathMatchesHandle(pathAfter, after)) {
|
|
33
|
+
return hash.digest('hex');
|
|
34
|
+
}
|
|
35
|
+
lastProblem = new Error('changed while it was being hashed');
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await handle.close().catch(() => { });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
lastProblem = cause;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw lastProblem instanceof Error ? lastProblem : new Error(String(lastProblem));
|
|
46
|
+
}
|
|
47
|
+
export async function verifyStableRawFile(path, expectedHash) {
|
|
48
|
+
const actualHash = await stableRawFileHash(path);
|
|
49
|
+
if (actualHash !== expectedHash) {
|
|
50
|
+
throw new Error(`Raw source changed (expected ${expectedHash}, found ${actualHash}).`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Enumerate without following symlinks and retain structural scan completeness. */
|
|
54
|
+
export async function scanRawDirectory(rawDir, includeFile = () => true) {
|
|
55
|
+
const files = [];
|
|
56
|
+
const blockers = [];
|
|
57
|
+
const diagnostics = [];
|
|
58
|
+
const occupiedPaths = new Set();
|
|
59
|
+
const entryKinds = new Map();
|
|
60
|
+
const ignoredRegularPaths = [];
|
|
61
|
+
let complete = true;
|
|
62
|
+
function markIncomplete(path, reason) {
|
|
63
|
+
complete = false;
|
|
64
|
+
diagnostics.push({ path, reason });
|
|
65
|
+
}
|
|
66
|
+
async function visit(folder) {
|
|
67
|
+
const absolutePath = join(rawDir, folder);
|
|
68
|
+
let before;
|
|
69
|
+
try {
|
|
70
|
+
before = await lstat(absolutePath, { bigint: true });
|
|
71
|
+
}
|
|
72
|
+
catch (cause) {
|
|
73
|
+
markIncomplete(folder, `${folder === '' ? 'Raw directory' : `Directory "${folder}"`} cannot be read: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!before.isDirectory() || before.isSymbolicLink()) {
|
|
77
|
+
markIncomplete(folder, `${folder === '' ? 'Raw directory' : `Directory "${folder}"`} is not a regular directory.`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let entries;
|
|
81
|
+
try {
|
|
82
|
+
entries = await readdir(absolutePath, { withFileTypes: true });
|
|
83
|
+
}
|
|
84
|
+
catch (cause) {
|
|
85
|
+
markIncomplete(folder, `${folder === '' ? 'Raw directory' : `Directory "${folder}"`} cannot be traversed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
for (const entry of entries.sort((a, b) => compareCodeUnits(a.name, b.name))) {
|
|
89
|
+
const path = folder === '' ? entry.name : `${folder}/${entry.name}`;
|
|
90
|
+
if (entry.name === '.gitkeep' || isFundusTransactionName(entry.name))
|
|
91
|
+
continue;
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
occupiedPaths.add(path);
|
|
94
|
+
entryKinds.set(path, 'directory');
|
|
95
|
+
const problem = folderPathError(path);
|
|
96
|
+
if (problem)
|
|
97
|
+
blockers.push({ path, reason: problem });
|
|
98
|
+
else
|
|
99
|
+
await visit(path);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (entry.isFile()) {
|
|
103
|
+
// Unsupported regular files do not exist from Fundus's perspective.
|
|
104
|
+
if (!includeFile(path)) {
|
|
105
|
+
ignoredRegularPaths.push(path);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
occupiedPaths.add(path);
|
|
109
|
+
entryKinds.set(path, 'file');
|
|
110
|
+
const problem = rawFilePathError(path);
|
|
111
|
+
if (problem) {
|
|
112
|
+
blockers.push({ path, reason: problem });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
files.push({ path, hash: await stableRawFileHash(join(rawDir, path)) });
|
|
117
|
+
}
|
|
118
|
+
catch (cause) {
|
|
119
|
+
blockers.push({
|
|
120
|
+
path,
|
|
121
|
+
reason: `Could not obtain a stable file hash: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const kind = entry.isSymbolicLink() ? 'symlink' : 'special';
|
|
127
|
+
occupiedPaths.add(path);
|
|
128
|
+
entryKinds.set(path, kind);
|
|
129
|
+
blockers.push({ path, reason: 'Symlinks and special files are not supported.' });
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const after = await lstat(absolutePath, { bigint: true });
|
|
133
|
+
if (!after.isDirectory() || objectIdentity(before) !== objectIdentity(after)) {
|
|
134
|
+
markIncomplete(folder, `${folder === '' ? 'Raw directory' : `Directory "${folder}"`} changed during traversal.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (cause) {
|
|
138
|
+
markIncomplete(folder, `${folder === '' ? 'Raw directory' : `Directory "${folder}"`} changed during traversal: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
await visit('');
|
|
142
|
+
const collisions = new Map();
|
|
143
|
+
for (const path of [...occupiedPaths].sort(compareCodeUnits)) {
|
|
144
|
+
const key = portableRawPathKey(path);
|
|
145
|
+
const previous = collisions.get(key);
|
|
146
|
+
if (previous !== undefined && previous !== path) {
|
|
147
|
+
const reason = `Raw paths "${previous}" and "${path}" collide portably.`;
|
|
148
|
+
blockers.push({ path: previous, reason });
|
|
149
|
+
blockers.push({ path, reason });
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
collisions.set(key, path);
|
|
153
|
+
}
|
|
154
|
+
const blockersByPath = new Map();
|
|
155
|
+
for (const blocker of blockers) {
|
|
156
|
+
if (!blockersByPath.has(blocker.path))
|
|
157
|
+
blockersByPath.set(blocker.path, blocker);
|
|
158
|
+
}
|
|
159
|
+
const sortedBlockers = [...blockersByPath.values()].sort((a, b) => compareCodeUnits(a.path, b.path));
|
|
160
|
+
const blocked = new Set(sortedBlockers.map((entry) => entry.path));
|
|
161
|
+
return {
|
|
162
|
+
complete,
|
|
163
|
+
diagnostics: diagnostics.sort((a, b) => compareCodeUnits(a.path, b.path)),
|
|
164
|
+
files: files
|
|
165
|
+
.filter((entry) => !blocked.has(entry.path))
|
|
166
|
+
.sort((a, b) => compareCodeUnits(a.path, b.path)),
|
|
167
|
+
blockers: sortedBlockers,
|
|
168
|
+
occupiedPaths,
|
|
169
|
+
entryKinds,
|
|
170
|
+
ignoredRegularPaths: ignoredRegularPaths.sort(compareCodeUnits)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Pure deterministic matching: exact path first, then only 1:1 hash groups. */
|
|
174
|
+
export function classifyRawFiles(library, scan) {
|
|
175
|
+
const filesByPath = new Map(scan.files.map((file) => [file.path, file]));
|
|
176
|
+
const remainingFiles = new Map(filesByPath);
|
|
177
|
+
const remainingRecords = new Map();
|
|
178
|
+
const unchangedAssetIds = [];
|
|
179
|
+
const edited = [];
|
|
180
|
+
const blockers = [...scan.blockers];
|
|
181
|
+
const excludedPrefixes = [];
|
|
182
|
+
for (const assetId of Object.keys(library.assets).sort(compareCodeUnits)) {
|
|
183
|
+
const record = library.assets[assetId];
|
|
184
|
+
const kind = scan.entryKinds.get(record.raw);
|
|
185
|
+
if (kind !== undefined && kind !== 'file') {
|
|
186
|
+
const existing = blockers.find((blocker) => blocker.path === record.raw);
|
|
187
|
+
if (existing)
|
|
188
|
+
existing.assetId = assetId;
|
|
189
|
+
else {
|
|
190
|
+
blockers.push({
|
|
191
|
+
assetId,
|
|
192
|
+
path: record.raw,
|
|
193
|
+
reason: `Managed raw path is occupied by a ${kind}.`
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
excludedPrefixes.push(record.raw);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const exact = filesByPath.get(record.raw);
|
|
200
|
+
if (exact) {
|
|
201
|
+
remainingFiles.delete(record.raw);
|
|
202
|
+
if (exact.hash === record.rawHash)
|
|
203
|
+
unchangedAssetIds.push(assetId);
|
|
204
|
+
else
|
|
205
|
+
edited.push({ assetId, ...exact });
|
|
206
|
+
}
|
|
207
|
+
else if (scan.complete &&
|
|
208
|
+
!scan.occupiedPaths.has(record.raw) &&
|
|
209
|
+
!scan.blockers.some((blocker) => record.raw === blocker.path || record.raw.startsWith(`${blocker.path}/`))) {
|
|
210
|
+
remainingRecords.set(assetId, { path: record.raw, hash: record.rawHash });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const path of remainingFiles.keys()) {
|
|
214
|
+
if (excludedPrefixes.some((prefix) => path.startsWith(`${prefix}/`)))
|
|
215
|
+
remainingFiles.delete(path);
|
|
216
|
+
}
|
|
217
|
+
const recordsByHash = new Map();
|
|
218
|
+
for (const [assetId, record] of remainingRecords) {
|
|
219
|
+
recordsByHash.set(record.hash, [...(recordsByHash.get(record.hash) ?? []), assetId]);
|
|
220
|
+
}
|
|
221
|
+
const filesByHash = new Map();
|
|
222
|
+
for (const file of remainingFiles.values()) {
|
|
223
|
+
filesByHash.set(file.hash, [...(filesByHash.get(file.hash) ?? []), file]);
|
|
224
|
+
}
|
|
225
|
+
const moves = [];
|
|
226
|
+
if (scan.complete) {
|
|
227
|
+
for (const [hash, assetIds] of recordsByHash) {
|
|
228
|
+
const candidates = filesByHash.get(hash) ?? [];
|
|
229
|
+
if (assetIds.length !== 1 || candidates.length !== 1)
|
|
230
|
+
continue;
|
|
231
|
+
const assetId = assetIds[0];
|
|
232
|
+
const file = candidates[0];
|
|
233
|
+
moves.push({ assetId, from: remainingRecords.get(assetId)?.path ?? '', ...file });
|
|
234
|
+
remainingRecords.delete(assetId);
|
|
235
|
+
remainingFiles.delete(file.path);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const blockerKeys = new Set();
|
|
239
|
+
const uniqueBlockers = blockers.filter((blocker) => {
|
|
240
|
+
const key = `${blocker.assetId ?? ''}|${blocker.path}`;
|
|
241
|
+
if (blockerKeys.has(key))
|
|
242
|
+
return false;
|
|
243
|
+
blockerKeys.add(key);
|
|
244
|
+
return true;
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
complete: scan.complete,
|
|
248
|
+
diagnostics: scan.diagnostics,
|
|
249
|
+
unchangedAssetIds,
|
|
250
|
+
edited: edited.sort((a, b) => compareCodeUnits(a.path, b.path)),
|
|
251
|
+
moves: scan.complete ? moves.sort((a, b) => compareCodeUnits(a.path, b.path)) : [],
|
|
252
|
+
deletions: scan.complete
|
|
253
|
+
? [...remainingRecords].map(([assetId, record]) => ({ assetId, path: record.path }))
|
|
254
|
+
: [],
|
|
255
|
+
unmanaged: [...remainingFiles.values()].sort((a, b) => compareCodeUnits(a.path, b.path)),
|
|
256
|
+
blockers: uniqueBlockers.sort((a, b) => compareCodeUnits(a.path, b.path))
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export async function classifyRawDirectory(rawDir, library, includeExternalFile = () => true) {
|
|
260
|
+
const managedPaths = new Set(Object.values(library.assets).map((record) => record.raw));
|
|
261
|
+
const scan = await scanRawDirectory(rawDir, (path) => managedPaths.has(path) || includeExternalFile(path));
|
|
262
|
+
const classification = classifyRawFiles(library, scan);
|
|
263
|
+
if (!classification.complete || classification.deletions.length === 0)
|
|
264
|
+
return classification;
|
|
265
|
+
const ignoredByHash = new Map();
|
|
266
|
+
for (const path of scan.ignoredRegularPaths) {
|
|
267
|
+
try {
|
|
268
|
+
const hash = await stableRawFileHash(join(rawDir, path));
|
|
269
|
+
ignoredByHash.set(hash, [...(ignoredByHash.get(hash) ?? []), path]);
|
|
270
|
+
}
|
|
271
|
+
catch (cause) {
|
|
272
|
+
return {
|
|
273
|
+
...classification,
|
|
274
|
+
complete: false,
|
|
275
|
+
diagnostics: [
|
|
276
|
+
...classification.diagnostics,
|
|
277
|
+
{
|
|
278
|
+
path,
|
|
279
|
+
reason: `Could not safely check ignored raw file "${path}" while managed files were missing: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
280
|
+
}
|
|
281
|
+
].sort((a, b) => compareCodeUnits(a.path, b.path)),
|
|
282
|
+
moves: [],
|
|
283
|
+
deletions: []
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const missingByHash = new Map();
|
|
288
|
+
for (const deletion of classification.deletions) {
|
|
289
|
+
const hash = library.assets[deletion.assetId].rawHash;
|
|
290
|
+
missingByHash.set(hash, [...(missingByHash.get(hash) ?? []), deletion]);
|
|
291
|
+
}
|
|
292
|
+
const supportedByHash = new Map();
|
|
293
|
+
for (const file of classification.unmanaged) {
|
|
294
|
+
supportedByHash.set(file.hash, [...(supportedByHash.get(file.hash) ?? []), file.path]);
|
|
295
|
+
}
|
|
296
|
+
const protectedAssetIds = new Set();
|
|
297
|
+
const unsupportedBlockers = [];
|
|
298
|
+
const ambiguityDiagnostics = [];
|
|
299
|
+
for (const [hash, ignoredPaths] of ignoredByHash) {
|
|
300
|
+
const missing = missingByHash.get(hash) ?? [];
|
|
301
|
+
if (missing.length === 0)
|
|
302
|
+
continue;
|
|
303
|
+
const candidatePaths = [...(supportedByHash.get(hash) ?? []), ...ignoredPaths].sort(compareCodeUnits);
|
|
304
|
+
const sortedMissing = [...missing].sort((a, b) => compareCodeUnits(a.assetId, b.assetId));
|
|
305
|
+
if (sortedMissing.length === 1 && ignoredPaths.length === 1 && candidatePaths.length === 1) {
|
|
306
|
+
const [{ assetId, path: oldPath }] = sortedMissing;
|
|
307
|
+
const [destination] = ignoredPaths;
|
|
308
|
+
protectedAssetIds.add(assetId);
|
|
309
|
+
unsupportedBlockers.push({
|
|
310
|
+
assetId,
|
|
311
|
+
path: destination,
|
|
312
|
+
reason: `Asset "${assetId}" was moved from "${oldPath}" to unsupported path "${destination}". ` +
|
|
313
|
+
'Rename it externally to a supported extension.'
|
|
314
|
+
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
ambiguityDiagnostics.push({
|
|
318
|
+
path: candidatePaths[0] ?? '',
|
|
319
|
+
reason: `Ignored raw files ambiguously match missing assets [${sortedMissing.map((entry) => entry.assetId).join(', ')}] ` +
|
|
320
|
+
`and candidate paths [${candidatePaths.join(', ')}]. Restore or reduce the external files until the match is unique.`
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (ambiguityDiagnostics.length > 0) {
|
|
324
|
+
return {
|
|
325
|
+
...classification,
|
|
326
|
+
complete: false,
|
|
327
|
+
diagnostics: [...classification.diagnostics, ...ambiguityDiagnostics].sort((a, b) => compareCodeUnits(`${a.path}|${a.reason}`, `${b.path}|${b.reason}`)),
|
|
328
|
+
moves: [],
|
|
329
|
+
deletions: [],
|
|
330
|
+
blockers: [...classification.blockers, ...unsupportedBlockers].sort((a, b) => compareCodeUnits(a.path, b.path))
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
...classification,
|
|
335
|
+
deletions: classification.deletions.filter((deletion) => !protectedAssetIds.has(deletion.assetId)),
|
|
336
|
+
blockers: [...classification.blockers, ...unsupportedBlockers].sort((a, b) => compareCodeUnits(a.path, b.path))
|
|
337
|
+
};
|
|
338
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Library, ValidationIssueDto } from '../core/index.js';
|
|
2
|
+
import type { RawClassification } from './raw-reconciliation.js';
|
|
3
|
+
export type RawSafetyIssueKind = 'incomplete' | 'blocker' | 'missing' | 'edited' | 'type-incompatible' | 'volatile';
|
|
4
|
+
export interface RawSafetyIssue extends ValidationIssueDto {
|
|
5
|
+
kind: RawSafetyIssueKind;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function rawSafetyIssueKey(issue: RawSafetyIssue): string;
|
|
9
|
+
/** Managed-source safety gate shared by editor operations and `fundus build`. */
|
|
10
|
+
export declare function rawSafetyIssues(library: Library, classification: RawClassification, compatibleTypes: (path: string) => string[]): RawSafetyIssue[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function rawSafetyIssueKey(issue) {
|
|
2
|
+
return `${issue.kind}|${issue.assetId ?? ''}|${issue.path}`;
|
|
3
|
+
}
|
|
4
|
+
/** Managed-source safety gate shared by editor operations and `fundus build`. */
|
|
5
|
+
export function rawSafetyIssues(library, classification, compatibleTypes) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
if (!classification.complete) {
|
|
8
|
+
for (const diagnostic of classification.diagnostics) {
|
|
9
|
+
issues.push({
|
|
10
|
+
kind: 'incomplete',
|
|
11
|
+
path: diagnostic.path,
|
|
12
|
+
message: diagnostic.reason
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const blocker of classification.blockers) {
|
|
17
|
+
const affected = blocker.assetId
|
|
18
|
+
? [blocker.assetId]
|
|
19
|
+
: Object.entries(library.assets)
|
|
20
|
+
.filter(([, record]) => record.raw === blocker.path || record.raw.startsWith(`${blocker.path}/`))
|
|
21
|
+
.map(([assetId]) => assetId);
|
|
22
|
+
for (const assetId of affected) {
|
|
23
|
+
issues.push({
|
|
24
|
+
kind: 'blocker',
|
|
25
|
+
path: blocker.path,
|
|
26
|
+
assetId,
|
|
27
|
+
message: `Raw source "${blocker.path}" is unsafe: ${blocker.reason}`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const missing of classification.deletions) {
|
|
32
|
+
issues.push({
|
|
33
|
+
kind: 'missing',
|
|
34
|
+
path: missing.path,
|
|
35
|
+
assetId: missing.assetId,
|
|
36
|
+
message: `Raw file "${missing.path}" is missing from the raw directory.`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
for (const move of classification.moves) {
|
|
40
|
+
issues.push({
|
|
41
|
+
kind: 'missing',
|
|
42
|
+
path: move.from,
|
|
43
|
+
assetId: move.assetId,
|
|
44
|
+
message: `Raw file "${move.from}" is missing from the raw directory.`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
for (const edited of classification.edited) {
|
|
48
|
+
issues.push({
|
|
49
|
+
kind: 'edited',
|
|
50
|
+
path: edited.path,
|
|
51
|
+
assetId: edited.assetId,
|
|
52
|
+
message: `Raw file "${edited.path}" changed outside Fundus.`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
for (const [assetId, record] of Object.entries(library.assets)) {
|
|
56
|
+
if (!compatibleTypes(record.raw).includes(record.type)) {
|
|
57
|
+
issues.push({
|
|
58
|
+
kind: 'type-incompatible',
|
|
59
|
+
path: record.raw,
|
|
60
|
+
assetId,
|
|
61
|
+
message: `Raw file "${record.raw}" is not compatible with its current ${record.type} type.`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
return issues.filter((issue) => {
|
|
67
|
+
const key = rawSafetyIssueKey(issue);
|
|
68
|
+
if (seen.has(key))
|
|
69
|
+
return false;
|
|
70
|
+
seen.add(key);
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
}
|
package/dist/server/start.d.ts
CHANGED
|
@@ -3,11 +3,13 @@ interface StartBannerOptions {
|
|
|
3
3
|
port: number;
|
|
4
4
|
projectRoot: string;
|
|
5
5
|
color?: boolean;
|
|
6
|
+
terminalWidth?: number;
|
|
6
7
|
}
|
|
7
8
|
export interface RunStartOptions {
|
|
8
9
|
openBrowser?: boolean;
|
|
10
|
+
port?: number;
|
|
9
11
|
}
|
|
10
12
|
/** Format the human startup summary without pulling a terminal UI dependency into the CLI. */
|
|
11
|
-
export declare function formatStartBanner({ port, projectRoot, color }: StartBannerOptions): string;
|
|
12
|
-
export declare function runStart(projectRoot: string, { openBrowser }?: RunStartOptions): Promise<void>;
|
|
13
|
+
export declare function formatStartBanner({ port, projectRoot, color, terminalWidth }: StartBannerOptions): string;
|
|
14
|
+
export declare function runStart(projectRoot: string, { openBrowser, port }?: RunStartOptions): Promise<void>;
|
|
13
15
|
export {};
|
package/dist/server/start.js
CHANGED
|
@@ -8,32 +8,64 @@ export const DEFAULT_PORT = 7583;
|
|
|
8
8
|
function ansi(code, value, enabled) {
|
|
9
9
|
return enabled ? `\u001b[${code}m${value}\u001b[0m` : value;
|
|
10
10
|
}
|
|
11
|
+
function truncateEnd(value, maxLength) {
|
|
12
|
+
if (value.length <= maxLength)
|
|
13
|
+
return value;
|
|
14
|
+
if (maxLength <= 1)
|
|
15
|
+
return '…'.slice(0, maxLength);
|
|
16
|
+
return `${value.slice(0, maxLength - 1)}…`;
|
|
17
|
+
}
|
|
18
|
+
function truncateMiddle(value, maxLength) {
|
|
19
|
+
if (value.length <= maxLength)
|
|
20
|
+
return value;
|
|
21
|
+
if (maxLength <= 1)
|
|
22
|
+
return '…'.slice(0, maxLength);
|
|
23
|
+
const remainingLength = maxLength - 1;
|
|
24
|
+
const startLength = Math.floor(remainingLength / 2);
|
|
25
|
+
const endLength = remainingLength - startLength;
|
|
26
|
+
return `${value.slice(0, startLength)}…${value.slice(-endLength)}`;
|
|
27
|
+
}
|
|
11
28
|
/** Format the human startup summary without pulling a terminal UI dependency into the CLI. */
|
|
12
|
-
export function formatStartBanner({ port, projectRoot, color = false }) {
|
|
29
|
+
export function formatStartBanner({ port, projectRoot, color = false, terminalWidth }) {
|
|
13
30
|
const url = `http://127.0.0.1:${port}`;
|
|
14
31
|
const brand = '◆ Fundus';
|
|
15
|
-
const editor = `Editor ${url}`;
|
|
16
|
-
const project = `Project ${projectRoot}`;
|
|
17
32
|
const ready = 'Ready · press Ctrl+C to stop';
|
|
18
|
-
const
|
|
33
|
+
const editorPrefix = 'Editor ';
|
|
34
|
+
const projectPrefix = 'Project ';
|
|
35
|
+
const preferredContentWidth = Math.max(42, brand.length, editorPrefix.length + url.length, projectPrefix.length + projectRoot.length, ready.length);
|
|
36
|
+
const availableContentWidth = Number.isFinite(terminalWidth) && terminalWidth !== undefined
|
|
37
|
+
? Math.max(1, Math.floor(terminalWidth) - 4)
|
|
38
|
+
: preferredContentWidth;
|
|
39
|
+
const contentWidth = Math.min(preferredContentWidth, availableContentWidth);
|
|
40
|
+
const displayedBrand = truncateEnd(brand, contentWidth);
|
|
41
|
+
const displayedEditorPrefix = truncateEnd(editorPrefix, contentWidth);
|
|
42
|
+
const displayedUrl = truncateEnd(url, Math.max(0, contentWidth - displayedEditorPrefix.length));
|
|
43
|
+
const editor = `${displayedEditorPrefix}${displayedUrl}`;
|
|
44
|
+
const displayedProjectPrefix = truncateEnd(projectPrefix, contentWidth);
|
|
45
|
+
const displayedProjectRoot = truncateMiddle(projectRoot, Math.max(0, contentWidth - displayedProjectPrefix.length));
|
|
46
|
+
const project = `${displayedProjectPrefix}${displayedProjectRoot}`;
|
|
47
|
+
const displayedReady = truncateEnd(ready, contentWidth);
|
|
19
48
|
const border = (value) => ansi('2', value, color);
|
|
49
|
+
const styledPrefix = (displayed, full, label, gap) => displayed === full ? `${ansi('2', label, color)}${gap}` : ansi('2', displayed, color);
|
|
20
50
|
const row = (plain, styled = plain) => `${border('│')} ${styled}${' '.repeat(contentWidth - plain.length)} ${border('│')}`;
|
|
21
51
|
const empty = row('');
|
|
22
52
|
return [
|
|
23
53
|
'',
|
|
24
54
|
border(`╭${'─'.repeat(contentWidth + 2)}╮`),
|
|
25
55
|
empty,
|
|
26
|
-
row(
|
|
56
|
+
row(displayedBrand, displayedBrand === brand
|
|
57
|
+
? `${ansi('38;2;24;152;243', '◆', color)} ${ansi('1', 'Fundus', color)}`
|
|
58
|
+
: ansi('1', displayedBrand, color)),
|
|
27
59
|
empty,
|
|
28
|
-
row(editor, `${
|
|
29
|
-
row(project, `${
|
|
60
|
+
row(editor, `${styledPrefix(displayedEditorPrefix, editorPrefix, 'Editor', ' ')}${ansi('4;36', displayedUrl, color)}`),
|
|
61
|
+
row(project, `${styledPrefix(displayedProjectPrefix, projectPrefix, 'Project', ' ')}${displayedProjectRoot}`),
|
|
30
62
|
empty,
|
|
31
|
-
row(
|
|
63
|
+
row(displayedReady, ansi('32', displayedReady, color)),
|
|
32
64
|
empty,
|
|
33
65
|
border(`╰${'─'.repeat(contentWidth + 2)}╯`)
|
|
34
66
|
].join('\n');
|
|
35
67
|
}
|
|
36
|
-
export async function runStart(projectRoot, { openBrowser = true } = {}) {
|
|
68
|
+
export async function runStart(projectRoot, { openBrowser = true, port = DEFAULT_PORT } = {}) {
|
|
37
69
|
const resolvedProjectRoot = resolve(projectRoot);
|
|
38
70
|
process.env.FUNDUS_PROJECT_ROOT = resolvedProjectRoot;
|
|
39
71
|
process.env.FUNDUS_BODY_SIZE_LIMIT ??= 'Infinity';
|
|
@@ -47,12 +79,12 @@ export async function runStart(projectRoot, { openBrowser = true } = {}) {
|
|
|
47
79
|
if (typeof module.handler !== 'function') {
|
|
48
80
|
throw new Error(`The packaged Fundus handler at ${fileURLToPath(handlerUrl)} is invalid.`);
|
|
49
81
|
}
|
|
50
|
-
const
|
|
51
|
-
const server = await startServer({ handler: module.handler, port: configuredPort });
|
|
82
|
+
const server = await startServer({ handler: module.handler, port });
|
|
52
83
|
const editorUrl = `http://127.0.0.1:${server.port}`;
|
|
53
84
|
console.log(formatStartBanner({
|
|
54
85
|
port: server.port,
|
|
55
86
|
projectRoot: resolvedProjectRoot,
|
|
87
|
+
terminalWidth: process.stdout.columns,
|
|
56
88
|
color: process.stdout.isTTY === true &&
|
|
57
89
|
process.env.NO_COLOR === undefined &&
|
|
58
90
|
process.env.TERM !== 'dumb'
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--panel:#2c2c2c;--panel-raised:#383838;--canvas:#1e1e1e;--border:#444;--border-soft:#383838;--text:#fff;--text-dim:#a0a0a0;--text-faint:#7a7a7a;--accent:#0d99ff;--accent-soft:#0d99ff29;--warning:#ffcd29;--warning-soft:#ffcd2924;--danger:#f24822;--ok:#14ae5c;--radius:6px;--radius-small:4px;--radius-large:8px;--font-ui:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}html,body{height:100%;margin:0;padding:0}body{background:var(--canvas);color:var(--text);font-family:var(--font-ui);-webkit-font-smoothing:antialiased;font-size:12px;line-height:1.45}a{color:inherit;text-decoration:none}button,input,select{font:inherit;color:inherit}button{cursor:pointer}.control{background:var(--panel-raised);border-radius:var(--radius);border:1px solid #0000;height:28px;padding:4px 8px}.control:hover{border-color:var(--border)}.control:focus,.control:focus-visible{border-color:var(--accent);outline:none}.control.primary{background:var(--accent);color:var(--text)}.control.primary:disabled{opacity:.4;cursor:not-allowed}.control.danger{color:var(--danger)}.control.danger:hover{border-color:var(--danger)}.checkerboard{background-color:var(--canvas);background-image:linear-gradient(45deg, var(--panel) 25%, transparent 25%), linear-gradient(-45deg, var(--panel) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--panel) 75%), linear-gradient(-45deg, transparent 75%, var(--panel) 75%);background-position:0 0,0 8px,8px -8px,-8px 0;background-size:16px 16px}.shell.svelte-12qhfyh{flex-direction:column;height:100vh;display:flex}.topbar.svelte-12qhfyh{background:var(--panel);border-bottom:1px solid var(--border);flex:none;align-items:center;gap:24px;height:40px;padding:0 16px;display:flex}.brand.svelte-12qhfyh{align-items:center;gap:8px;font-size:12px;font-weight:600;display:inline-flex}.logo.svelte-12qhfyh{color:var(--accent)}nav.svelte-12qhfyh{gap:4px;display:flex}nav.svelte-12qhfyh a:where(.svelte-12qhfyh){border-radius:var(--radius);color:var(--text-dim);padding:4px 10px}nav.svelte-12qhfyh a:where(.svelte-12qhfyh):hover{color:var(--text)}nav.svelte-12qhfyh a.active:where(.svelte-12qhfyh){background:var(--accent-soft);color:var(--accent)}.status.svelte-12qhfyh{color:var(--text-dim);align-items:center;gap:6px;margin-left:auto;display:inline-flex}.status-text.error.svelte-12qhfyh{color:var(--danger)}.dot.svelte-12qhfyh{background:var(--text-faint);border-radius:50%;width:7px;height:7px}.dot.ok.svelte-12qhfyh{background:var(--ok)}.dot.bad.svelte-12qhfyh{background:var(--danger)}.dot.saving.svelte-12qhfyh{background:var(--accent);animation:.8s ease-in-out infinite svelte-12qhfyh-pulse}@keyframes svelte-12qhfyh-pulse{0%,to{opacity:.35}50%{opacity:1}}.notice.svelte-12qhfyh{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);color:var(--text-dim);margin:24px;padding:12px 16px}.issue-banner.svelte-12qhfyh{background:color-mix(in srgb, var(--danger) 14%, var(--panel));border-bottom:1px solid var(--danger);color:var(--text);flex-direction:column;gap:4px;padding:8px 16px;display:flex}code.svelte-12qhfyh{background:var(--panel-raised);border-radius:4px;padding:1px 5px}
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.manifest-selector.svelte-151zph4{flex-direction:column;gap:4px;width:100%;display:flex}.manifest-option.svelte-151zph4{border-color:var(--border-soft);width:100%;height:28px;color:var(--text-dim);cursor:pointer;border-radius:4px;align-items:center;gap:7px;padding:2px 7px;display:flex}.manifest-option.svelte-151zph4:hover{border-color:var(--manifest-color);background:color-mix(in srgb, var(--manifest-color) 10%, var(--panel-raised));color:var(--text)}.manifest-option.svelte-151zph4:focus-within{border-color:var(--manifest-color)}.manifest-option.selected.svelte-151zph4{border-color:var(--manifest-color);background:color-mix(in srgb, var(--manifest-color) 20%, var(--panel-raised));color:var(--text)}.manifest-option.passive.svelte-151zph4{border-color:var(--manifest-color);border-style:dashed}input.svelte-151zph4{width:14px;height:14px;accent-color:var(--manifest-color);flex:none;margin:0}.marker.svelte-151zph4{border:1px solid color-mix(in srgb, var(--manifest-color) 70%, var(--border));background:color-mix(in srgb, var(--manifest-color) 24%, var(--panel));border-radius:3px;flex:none;justify-content:center;align-items:center;width:18px;height:18px;font-size:12px;display:inline-flex}.color-dot.svelte-151zph4{background:var(--manifest-color);border-radius:50%;width:8px;height:8px}.name.svelte-151zph4,.via.svelte-151zph4{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.name.svelte-151zph4{font-size:11px;font-weight:500}.via.svelte-151zph4{color:var(--text-faint);margin-left:auto;font-size:9px}.detail.svelte-xy8zsr{flex:1;grid-template-columns:224px 1fr 280px;min-height:0;display:grid}.center.svelte-xy8zsr{flex-direction:column;min-width:0;min-height:0;padding:12px 16px;display:flex}.center.empty.svelte-xy8zsr,.empty.svelte-xy8zsr{color:var(--text-dim);flex:1;justify-content:center;align-items:center;display:flex}.inspector.svelte-xy8zsr{background:var(--panel);border-left:1px solid var(--border);overflow-y:auto}.inspector.svelte-xy8zsr section:where(.svelte-xy8zsr){border-bottom:1px solid var(--border-soft);padding:12px}.inspector.svelte-xy8zsr h2:where(.svelte-xy8zsr){word-break:break-all;margin:0;font-size:13px}.raw.svelte-xy8zsr{color:var(--text-dim);word-break:break-all;margin:4px 0 0}.warning.svelte-xy8zsr{color:var(--warning);margin:8px 0 0;font-size:11px}.hint.svelte-xy8zsr{color:var(--text-faint);margin:0;font-size:11px}.inspector.svelte-xy8zsr h4:where(.svelte-xy8zsr){text-transform:uppercase;letter-spacing:.06em;color:var(--text-faint);margin:8px 0 4px;font-size:10px}.reference-row.svelte-xy8zsr{border-radius:var(--radius);color:var(--text-dim);align-items:center;gap:6px;min-width:0;padding:3px 4px;display:flex}.reference-row.svelte-xy8zsr:hover{background:var(--panel-raised);color:var(--text)}.reference-field.svelte-xy8zsr{color:var(--text-faint);font-size:11px}.reference-arrow.svelte-xy8zsr{color:var(--text-faint);flex:none}.reference-asset.svelte-xy8zsr{color:var(--accent);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.rename.svelte-xy8zsr{gap:8px;margin-bottom:8px;display:flex}.rename.svelte-xy8zsr input:where(.svelte-xy8zsr){flex:1;min-width:0}.danger.svelte-xy8zsr{width:100%}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{J as e,P as t,Y as n}from"./CNRz3sSw.js";async function r(e){if(!e.ok){let t=`${e.status} ${e.statusText}`;try{let n=await e.json();n.error?.message&&(t=n.error.message)}catch{}throw Error(t)}return await e.json()}async function i(){return r(await fetch(`/api/state`))}async function a(e,t,n,i){let a=new FormData;return a.set(`file`,e),a.set(`type`,t),a.set(`id`,n),a.set(`folder`,i),r(await fetch(`/api/assets`,{method:`POST`,body:a}))}async function o(e){return r(await fetch(`/api/folders`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:e})}))}async function s(e,t){return r(await fetch(`/api/folders/${encodeURIComponent(e)}`,{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:t})}))}async function c(e,t){return r(await fetch(`/api/folders/${encodeURIComponent(e)}`,{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify({parent:t})}))}async function l(e){return r(await fetch(`/api/folders/${encodeURIComponent(e)}`,{method:`DELETE`}))}async function u(e,t){return r(await fetch(`/api/assets/${encodeURIComponent(e)}`,{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)}))}async function d(e,t){return r(await fetch(`/api/assets/move`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({assetIds:e,folder:t})}))}async function f(e){return r(await fetch(`/api/assets/${encodeURIComponent(e)}`,{method:`DELETE`}))}async function p(e){return r(await fetch(`/api/assets/delete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({assetIds:e})}))}async function m(e){return r(await fetch(`/api/manifests`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({name:e})}))}async function h(e){return r(await fetch(`/api/manifests/${encodeURIComponent(e)}`,{method:`DELETE`}))}async function g(e,t){return r(await fetch(`/api/manifests/${encodeURIComponent(e)}`,{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)}))}var _=new class{#e=Promise.resolve();#t=n(null);get state(){return t(this.#t)}set state(t){e(this.#t,t,!0)}#n=n(!1);get saving(){return t(this.#n)}set saving(t){e(this.#n,t,!0)}#r=n(null);get error(){return t(this.#r)}set error(t){e(this.#r,t,!0)}#i=n(!1);get loaded(){return t(this.#i)}set loaded(t){e(this.#i,t,!0)}async refresh(){try{this.state=await i(),this.error=null}catch(e){this.error=e instanceof Error?e.message:String(e)}finally{this.loaded=!0}}mutate(e){let t=this.#e.then(async()=>{this.saving=!0,this.error=null;try{let t=await e();return this.state=t,t}catch(e){throw this.error=e instanceof Error?e.message:String(e),e}finally{this.saving=!1}});return this.#e=t.then(()=>{},()=>{}),t}get manifestNames(){return Object.keys(this.state?.library.manifests??{}).sort()}manifestAppearance(e){return this.state?.library.manifests[e]??{}}get generalIssues(){return this.state?.issues.filter(e=>e.assetId===void 0).map(e=>e.message)??[]}assetById(e){return this.state?.assets.find(t=>t.id===e)??null}};export{p as a,d as c,g as d,s as f,f as i,c as l,o as n,l as o,a as p,m as r,h as s,_ as t,u};
|
|
Binary file
|
|
Binary file
|