fundus 0.16.0 → 0.17.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 +37 -1
- package/build/client/_app/immutable/assets/{AssetSourceMenu.B5fzYfym.css → AssetSourceMenu.Buj2UxUg.css} +1 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.Buj2UxUg.css.br +0 -0
- package/build/client/_app/immutable/assets/{AssetSourceMenu.B5fzYfym.css.gz → AssetSourceMenu.Buj2UxUg.css.gz} +0 -0
- package/build/client/_app/immutable/chunks/{D6c2mfMB.js → 5fq8ySvR.js} +1 -1
- package/build/client/_app/immutable/chunks/5fq8ySvR.js.br +0 -0
- package/build/client/_app/immutable/chunks/5fq8ySvR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BcVZHIBD.js +1 -0
- package/build/client/_app/immutable/chunks/BcVZHIBD.js.br +0 -0
- package/build/client/_app/immutable/chunks/BcVZHIBD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DXiUbZ9S.js +2 -0
- package/build/client/_app/immutable/chunks/DXiUbZ9S.js.br +0 -0
- package/build/client/_app/immutable/chunks/DXiUbZ9S.js.gz +0 -0
- package/build/client/_app/immutable/chunks/H5KnxQLQ.js +108 -0
- package/build/client/_app/immutable/chunks/H5KnxQLQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/H5KnxQLQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{B97y-eFA.js → jEVVu5G9.js} +1 -1
- package/build/client/_app/immutable/chunks/jEVVu5G9.js.br +0 -0
- package/build/client/_app/immutable/chunks/jEVVu5G9.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.V1MmmICK.js → app.Z9n2k33I.js} +2 -2
- package/build/client/_app/immutable/entry/app.Z9n2k33I.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Z9n2k33I.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DGo1uhI2.js +1 -0
- package/build/client/_app/immutable/entry/start.DGo1uhI2.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DGo1uhI2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DiZpkhxP.js +1 -0
- package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BqoPRvUU.js → 1.rgoSlmPu.js} +1 -1
- package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.br +2 -0
- package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.Dzp3a3be.js +3 -0
- package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.mSsa2TDI.js +1 -0
- package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.4oRGPYr4.js +2 -0
- package/build/client/_app/immutable/nodes/4.4oRGPYr4.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.4oRGPYr4.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/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-BXaBgcZT.js → context.js-Dh0ibRIF.js} +196 -7
- package/build/server/chunks/chunks/context.js-Dh0ibRIF.js.map +1 -0
- package/build/server/chunks/chunks/{contract.js-IY1F-HE_.js → contract.js-_LTMZBo2.js} +68 -6
- package/build/server/chunks/chunks/contract.js-_LTMZBo2.js.map +1 -0
- package/build/server/chunks/chunks/{core.js-D1sYC_78.js → core.js-CTyKR4bu.js} +2 -14
- package/build/server/chunks/chunks/{core.js-D1sYC_78.js.map → core.js-CTyKR4bu.js.map} +1 -1
- package/build/server/chunks/chunks/{exports.js-B5DLeNXr.js → exports.js-gz2aNXzz.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-B5DLeNXr.js.map → exports.js-gz2aNXzz.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-CzB9Igb0.js → http.js-BiqUcAuu.js} +3 -2
- package/build/server/chunks/chunks/http.js-BiqUcAuu.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-HE35Pm1C.js → internal.js-DoH0cN5N.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-HE35Pm1C.js.map → internal.js-DoH0cN5N.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{manifest-membership.js-Cm_2fU-Z.js → manifest-membership.js-at1ixQdW.js} +10 -3
- package/build/server/chunks/chunks/manifest-membership.js-at1ixQdW.js.map +1 -0
- package/build/server/chunks/chunks/{raw-reconciliation.js-1_IwPhKb.js → raw-reconciliation.js-CUH8IIJx.js} +3 -3
- package/build/server/chunks/chunks/raw-reconciliation.js-CUH8IIJx.js.map +1 -0
- package/build/server/chunks/chunks/{raw-reconciliation2.js-CcO9li5n.js → raw-reconciliation2.js-z4xl5zw2.js} +2 -2
- package/build/server/chunks/chunks/{raw-reconciliation2.js-CcO9li5n.js.map → raw-reconciliation2.js-z4xl5zw2.js.map} +1 -1
- package/build/server/chunks/chunks/{shared.js-MR8sPPTu.js → shared.js-CI-ZkPQn.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-MR8sPPTu.js.map → shared.js-CI-ZkPQn.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-C2UFBITa.js → utils.js-gXYLxRG9.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-C2UFBITa.js.map → utils.js-gXYLxRG9.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BnRJ5Qnd.js → _server.ts.js-CdDjuW0r.js} +6 -6
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BnRJ5Qnd.js.map → _server.ts.js-CdDjuW0r.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BZPR6n5n.js → _server.ts.js-OWM1eORG.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BZPR6n5n.js.map → _server.ts.js-OWM1eORG.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-DiBS3A0C.js → _server.ts.js-DoG-7ZpZ.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-DiBS3A0C.js.map → _server.ts.js-DoG-7ZpZ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-LOO6qm2-.js → _server.ts.js-CAWjVeGd.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-LOO6qm2-.js.map → _server.ts.js-CAWjVeGd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js +38 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CRtJJoSs.js → _server.ts.js-Bo_EsYXJ.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CRtJJoSs.js.map → _server.ts.js-Bo_EsYXJ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ds4zCZ9H.js → _server.ts.js-DW4w0XAr.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ds4zCZ9H.js.map → _server.ts.js-DW4w0XAr.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DD4N8pcj.js → _server.ts.js-_MiNPnSy.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DD4N8pcj.js.map → _server.ts.js-_MiNPnSy.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CZev9aYz.js → _server.ts.js-DwbrQnBc.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CZev9aYz.js.map → _server.ts.js-DwbrQnBc.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-MBDVW0q1.js → _server.ts.js-CEnHQf3b.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-MBDVW0q1.js.map → _server.ts.js-CEnHQf3b.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-C7naXnsR.js → _server.ts.js-BZDuJZtF.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-C7naXnsR.js.map → _server.ts.js-BZDuJZtF.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DuGyRdf3.js → _server.ts.js-DRgdpKKd.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DuGyRdf3.js.map → _server.ts.js-DRgdpKKd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-0-ZCt0qH.js → _server.ts.js-CP5QyIhz.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-0-ZCt0qH.js.map → _server.ts.js-CP5QyIhz.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-DFYU_sZL.js → _server.ts.js-BKEvt065.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-DFYU_sZL.js.map → _server.ts.js-BKEvt065.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-BqJpK0sJ.js → _server.ts.js-C_zihkDS.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-BqJpK0sJ.js.map → _server.ts.js-C_zihkDS.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js--LSqADJp.js → _server.ts.js-watkBmy6.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js--LSqADJp.js.map → _server.ts.js-watkBmy6.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DcL8nYDM.js → _server.ts.js-YtumLIou.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DcL8nYDM.js.map → _server.ts.js-YtumLIou.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-72OcyLfP.js → _server.ts.js-Dkouuu-M.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-72OcyLfP.js.map → _server.ts.js-Dkouuu-M.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D0bOxmG0.js → _server.ts.js-DwsEQeuf.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D0bOxmG0.js.map → _server.ts.js-DwsEQeuf.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BjDVCRNV.js → _server.ts.js-CdQxfhV3.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BjDVCRNV.js.map → _server.ts.js-CdQxfhV3.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CndfoXXd.js → _server.ts.js-DDdMocWj.js} +6 -6
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CndfoXXd.js.map → _server.ts.js-DDdMocWj.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Ck1mgbk5.js → _server.ts.js-DbzjbH32.js} +7 -7
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Ck1mgbk5.js.map → _server.ts.js-DbzjbH32.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-lZ9oP8tz.js → _server.ts.js-K_JbXZsU.js} +7 -7
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-lZ9oP8tz.js.map → _server.ts.js-K_JbXZsU.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-DQ6l2RSK.js → hooks.server.js-DgZAVFbQ.js} +6 -6
- package/build/server/chunks/entries/{hooks.server.js-DQ6l2RSK.js.map → hooks.server.js-DgZAVFbQ.js.map} +1 -1
- package/build/server/chunks/{handler-Dfk_4a-r.js → handler-CFkKak8t.js} +5 -5
- package/build/server/chunks/{handler-Dfk_4a-r.js.map → handler-CFkKak8t.js.map} +1 -1
- package/build/server/chunks/{index.js-O3Wl1zok.js → index.js-B1IroJH5.js} +5 -5
- package/build/server/chunks/{index.js-O3Wl1zok.js.map → index.js-B1IroJH5.js.map} +1 -1
- package/build/server/chunks/{manifest.js-51nEZnKQ.js → manifest.js-DNDBsrEU.js} +36 -29
- package/build/server/chunks/manifest.js-DNDBsrEU.js.map +1 -0
- package/build/server/chunks/nodes/{0.js-DjqMfDtB.js → 0.js-CZ3mZUZl.js} +2 -2
- package/build/server/chunks/nodes/{0.js-DjqMfDtB.js.map → 0.js-CZ3mZUZl.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-BmDzLKxV.js +7 -0
- package/build/server/chunks/nodes/{1.js-BwfSLYJH.js.map → 1.js-BmDzLKxV.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-Dirm-bY-.js +7 -0
- package/build/server/chunks/nodes/2.js-Dirm-bY-.js.map +1 -0
- package/build/server/chunks/nodes/3.js-DOqHmSWt.js +7 -0
- package/build/server/chunks/nodes/{3.js-D7_hF0qp.js.map → 3.js-DOqHmSWt.js.map} +1 -1
- package/build/server/chunks/nodes/4.js-C8pmF8IP.js +7 -0
- package/build/server/chunks/nodes/{4.js-D58M2chZ.js.map → 4.js-C8pmF8IP.js.map} +1 -1
- package/dist/core/api-types.d.ts +2 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/ingest.d.ts +1 -6
- package/dist/core/ingest.js +4 -21
- package/dist/core/propose-asset-id.d.ts +10 -0
- package/dist/core/propose-asset-id.js +25 -0
- package/dist/server/cli-commands.d.ts +2 -2
- package/dist/server/cli-commands.js +213 -29
- package/dist/server/config-loader.d.ts +1 -0
- package/dist/server/config-loader.js +5 -0
- package/dist/server/http.js +1 -0
- package/dist/server/importers/figma.d.ts +26 -3
- package/dist/server/importers/figma.js +114 -1
- package/dist/server/library-store.d.ts +9 -0
- package/dist/server/library-store.js +79 -0
- package/dist/server/operations.d.ts +12 -0
- package/dist/server/operations.js +121 -1
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.B5fzYfym.css.br +0 -0
- package/build/client/_app/immutable/chunks/B06eqbax.js +0 -108
- package/build/client/_app/immutable/chunks/B06eqbax.js.br +0 -0
- package/build/client/_app/immutable/chunks/B06eqbax.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B97y-eFA.js.br +0 -0
- package/build/client/_app/immutable/chunks/B97y-eFA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C9MzfQz6.js +0 -2
- package/build/client/_app/immutable/chunks/C9MzfQz6.js.br +0 -0
- package/build/client/_app/immutable/chunks/C9MzfQz6.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CcuyGBqj.js +0 -1
- package/build/client/_app/immutable/chunks/CcuyGBqj.js.br +0 -0
- package/build/client/_app/immutable/chunks/CcuyGBqj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D6c2mfMB.js.br +0 -2
- package/build/client/_app/immutable/chunks/D6c2mfMB.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.V1MmmICK.js.br +0 -0
- package/build/client/_app/immutable/entry/app.V1MmmICK.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BbyflZUw.js +0 -1
- package/build/client/_app/immutable/entry/start.BbyflZUw.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BbyflZUw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BJOUVkow.js +0 -1
- package/build/client/_app/immutable/nodes/0.BJOUVkow.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BJOUVkow.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BqoPRvUU.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BqoPRvUU.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BSUVFDfW.js +0 -3
- package/build/client/_app/immutable/nodes/2.BSUVFDfW.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BSUVFDfW.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.B6beFiTQ.js +0 -1
- package/build/client/_app/immutable/nodes/3.B6beFiTQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.B6beFiTQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.Dt96q8V4.js +0 -2
- package/build/client/_app/immutable/nodes/4.Dt96q8V4.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Dt96q8V4.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-BXaBgcZT.js.map +0 -1
- package/build/server/chunks/chunks/contract.js-IY1F-HE_.js.map +0 -1
- package/build/server/chunks/chunks/http.js-CzB9Igb0.js.map +0 -1
- package/build/server/chunks/chunks/manifest-membership.js-Cm_2fU-Z.js.map +0 -1
- package/build/server/chunks/chunks/raw-reconciliation.js-1_IwPhKb.js.map +0 -1
- package/build/server/chunks/manifest.js-51nEZnKQ.js.map +0 -1
- package/build/server/chunks/nodes/1.js-BwfSLYJH.js +0 -7
- package/build/server/chunks/nodes/2.js-buHbEytf.js +0 -7
- package/build/server/chunks/nodes/2.js-buHbEytf.js.map +0 -1
- package/build/server/chunks/nodes/3.js-D7_hF0qp.js +0 -7
- package/build/server/chunks/nodes/4.js-D58M2chZ.js +0 -7
|
@@ -14,7 +14,7 @@ import { runBuild } from './build.js';
|
|
|
14
14
|
import { formatCheckFinding, runCheck } from './check.js';
|
|
15
15
|
import { loadConfig } from './config-loader.js';
|
|
16
16
|
import { FundusOperations } from './operations.js';
|
|
17
|
-
import { validateFigmaSourceParameters } from './importers/figma.js';
|
|
17
|
+
import { parseFigmaFileKey, validateFigmaSourceParameters } from './importers/figma.js';
|
|
18
18
|
import { pluginsByType, serverPluginForType } from './plugins.js';
|
|
19
19
|
import { CliUsageError, formatBytes, parseArguments, parseJsonFlag, parseManifestList, printJsonDocument, requirePositionals } from './cli-io.js';
|
|
20
20
|
const COMMON_FLAGS = {
|
|
@@ -114,7 +114,9 @@ function printHumanAssetDetail(asset) {
|
|
|
114
114
|
console.log(` rawHash: ${asset.rawHash}`);
|
|
115
115
|
if (asset.importSource?.importer === 'figma') {
|
|
116
116
|
const source = validateFigmaSourceParameters(asset.importSource.parameters);
|
|
117
|
-
console.log(
|
|
117
|
+
console.log(source.mode === 'tag'
|
|
118
|
+
? ` source: Figma tag "${source.fundusTag}" → node ${source.nodeId} at ${source.scale}x`
|
|
119
|
+
: ` source: Figma node ${source.nodeId} at ${source.scale}x`);
|
|
118
120
|
}
|
|
119
121
|
console.log(` manifests: ${asset.manifests.length > 0 ? asset.manifests.join(', ') : '(none)'}`);
|
|
120
122
|
for (const membership of asset.passiveManifests) {
|
|
@@ -374,19 +376,34 @@ Usage: fundus asset ingest <file> [--type <type>] [--id <id>] [--folder <path|ro
|
|
|
374
376
|
[--scale <1|2|3|4>] [--folder <path|root>]
|
|
375
377
|
[--manifests <a,b>] [--parameters <json>]
|
|
376
378
|
[--compression-x <0-100>] [--compression-y <0-100>] [--json]
|
|
379
|
+
fundus asset ingest --from figma --figma-tag <tag> --type <image|slice>
|
|
380
|
+
[--figma-file <key|link>] [--scale <1|2|3|4>] [--folder <path|root>]
|
|
381
|
+
[--manifests <a,b>] [--parameters <json>]
|
|
382
|
+
[--compression-x <0-100>] [--compression-y <0-100>] [--json]
|
|
377
383
|
|
|
378
384
|
File ingest remains the default and behaves like an editor upload. Figma
|
|
379
385
|
ingest materializes one selection as PNG through the same importer-neutral
|
|
380
386
|
operations pipeline. Both run the full autosave chain.
|
|
381
387
|
|
|
388
|
+
Figma has two modes. Node mode takes the selection link as the positional and
|
|
389
|
+
requires --id. Tag mode takes --figma-tag <tag> instead of a link: the tag IS
|
|
390
|
+
the asset id, so a positional link and --id are both forbidden with --figma-tag.
|
|
391
|
+
|
|
382
392
|
Flags:
|
|
383
393
|
--from <file|figma> acquisition source; default: file. URLs are never
|
|
384
394
|
detected heuristically
|
|
385
395
|
--type <type> required for Figma (image or slice) and when a file
|
|
386
396
|
extension is ambiguous; known types:
|
|
387
397
|
${Object.keys(pluginsByType).sort().join(', ')}
|
|
388
|
-
--id <id> required for Figma; for files defaults to a
|
|
389
|
-
from the file name
|
|
398
|
+
--id <id> required for Figma node mode; for files defaults to a
|
|
399
|
+
proposal from the file name; forbidden with --figma-tag
|
|
400
|
+
--figma-tag <tag> Figma tag mode: resolve the node tagged with this Fundus
|
|
401
|
+
asset id; the tag becomes the asset id (mutually
|
|
402
|
+
exclusive with a node selection link and --id)
|
|
403
|
+
--figma-file <key|link>
|
|
404
|
+
fileKey for tag mode as a raw key or a Figma link;
|
|
405
|
+
defaults to import.figma.defaultFileKey from
|
|
406
|
+
fundus.config.ts
|
|
390
407
|
--folder <path|root> destination folder; default: root
|
|
391
408
|
--manifests <a,b> explicit manifest membership (every name must exist)
|
|
392
409
|
--parameters <json> shallow parameter patch merged after ingest seeding
|
|
@@ -408,20 +425,37 @@ Example:
|
|
|
408
425
|
--compression-x 75 --compression-y 25 --json
|
|
409
426
|
fundus asset ingest 'https://www.figma.com/design/abc/UI?node-id=12-34' \\
|
|
410
427
|
--from figma --type slice --id navigationPanel --scale 3 --json
|
|
428
|
+
fundus asset ingest --from figma --figma-tag navigationPanel --type slice \\
|
|
429
|
+
--figma-file abc --scale 3 --json
|
|
411
430
|
`;
|
|
412
431
|
export const ASSET_REIMPORT_HELP = `fundus asset reimport — refresh an asset from its saved source
|
|
413
432
|
|
|
414
433
|
Usage: fundus asset reimport <id> [--json]
|
|
415
434
|
fundus asset reimport <id> --figma-link <selection-link> [--scale <1|2|3|4>] [--json]
|
|
435
|
+
fundus asset reimport <id> --figma-tag <newTag> [--figma-file <key|link>] [--scale <1|2|3|4>] [--json]
|
|
436
|
+
fundus asset reimport <id> --figma-file <key|link> [--scale <1|2|3|4>] [--json]
|
|
416
437
|
fundus asset reimport <id> --scale <1|2|3|4> [--json]
|
|
417
438
|
|
|
418
|
-
Without overrides, re-materializes the persisted importer source
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
439
|
+
Without overrides, re-materializes the persisted importer source (a Figma tag
|
|
440
|
+
asset re-scans its file for the tagged node, self-healing after node moves).
|
|
441
|
+
A Figma override may update the source or scale; omitted values are preserved.
|
|
442
|
+
Asset type, parameters, folder, and manifests remain unchanged. Raw-file drift
|
|
443
|
+
or concurrent changes abort without overwriting local edits.
|
|
444
|
+
|
|
445
|
+
Node vs tag assets accept different overrides. A node asset takes --figma-link
|
|
446
|
+
(and --scale); --figma-tag and --figma-file are rejected. A tag asset takes
|
|
447
|
+
--figma-tag to change its tag, --figma-file to re-point in place to another
|
|
448
|
+
document, and --scale; --figma-link is rejected. Because the tag IS the asset
|
|
449
|
+
id, changing it with --figma-tag renames the asset to the new tag (the new id
|
|
450
|
+
must be valid and unused).
|
|
422
451
|
|
|
423
452
|
Flags:
|
|
424
|
-
--figma-link <link> replace the persisted
|
|
453
|
+
--figma-link <link> node asset only: replace the persisted file/node selection
|
|
454
|
+
--figma-tag <newTag> tag asset only: change the tag, renaming the asset to the
|
|
455
|
+
new tag (the tag is the asset id)
|
|
456
|
+
--figma-file <key|link>
|
|
457
|
+
tag asset only: re-point in place to another file (raw
|
|
458
|
+
key or a Figma link)
|
|
425
459
|
--scale <1|2|3|4> replace the persisted Figma export scale
|
|
426
460
|
--json print {"ok": true, "asset": <AssetStateDto>, "issues": […]}
|
|
427
461
|
--help this help
|
|
@@ -429,6 +463,8 @@ Flags:
|
|
|
429
463
|
Example:
|
|
430
464
|
fundus asset reimport navigationPanel
|
|
431
465
|
fundus asset reimport navigationPanel --figma-link '<new-selection-link>'
|
|
466
|
+
fundus asset reimport navigationPanel --figma-tag mainNavigationPanel
|
|
467
|
+
fundus asset reimport navigationPanel --figma-file def456
|
|
432
468
|
fundus asset reimport navigationPanel --scale 4
|
|
433
469
|
fundus asset reimport navigationPanel --figma-link '<new-selection-link>' --scale 4 --json
|
|
434
470
|
`;
|
|
@@ -436,16 +472,26 @@ export const ASSET_REPLACE_HELP = `fundus asset replace — replace an asset's r
|
|
|
436
472
|
|
|
437
473
|
Usage: fundus asset replace <id> <file|figma-selection-link>
|
|
438
474
|
[--from <file|figma>] [--scale <1|2|3|4>] [--json]
|
|
475
|
+
fundus asset replace <id> --from figma --figma-tag <tag>
|
|
476
|
+
[--figma-file <key|link>] [--scale <1|2|3|4>] [--json]
|
|
439
477
|
|
|
440
478
|
Replaces the stored raw file and content hash while preserving the asset's
|
|
441
479
|
id, type, folder, parameters, references, and manifest memberships. The new
|
|
442
480
|
source must be accepted by the existing asset type. File replacement remains
|
|
443
481
|
the default and disconnects any saved source. Figma replacement stores the new
|
|
444
|
-
selection as a repeatable source.
|
|
445
|
-
|
|
482
|
+
selection (node mode) or tag (tag mode) as a repeatable source. A tag
|
|
483
|
+
replacement must resolve to the asset's own id, so it can only re-point the
|
|
484
|
+
same asset's own tag. Runs the full autosave chain, so delivered assets are
|
|
485
|
+
reprocessed and manifests regenerated.
|
|
446
486
|
|
|
447
487
|
Flags:
|
|
448
488
|
--from <file|figma> replacement source; default: file
|
|
489
|
+
--figma-tag <tag> Figma tag mode: replace from the node tagged with this
|
|
490
|
+
Fundus asset id (takes no positional link)
|
|
491
|
+
--figma-file <key|link>
|
|
492
|
+
fileKey for tag mode as a raw key or a Figma link;
|
|
493
|
+
defaults to import.figma.defaultFileKey from
|
|
494
|
+
fundus.config.ts
|
|
449
495
|
--scale <1|2|3|4> Figma export scale; default:
|
|
450
496
|
import.figma.defaults.scale from fundus.config.ts
|
|
451
497
|
--json print {"ok": true, "asset": <AssetStateDto>, "issues": […]}
|
|
@@ -455,6 +501,8 @@ Example:
|
|
|
455
501
|
fundus asset replace navigationPanel ./exports/navigation_panel_v2.png --json
|
|
456
502
|
fundus asset replace navigationPanel \\
|
|
457
503
|
'https://www.figma.com/design/abc/UI?node-id=12-34' --from figma --scale 3 --json
|
|
504
|
+
fundus asset replace navigationPanel --from figma \\
|
|
505
|
+
--figma-tag navigationPanel --figma-file abc --scale 3 --json
|
|
458
506
|
`;
|
|
459
507
|
export const ASSET_SET_HELP = `fundus asset set — update an asset's parameters and/or memberships
|
|
460
508
|
|
|
@@ -701,9 +749,10 @@ async function runAssetIngest(argumentList) {
|
|
|
701
749
|
'compression-y': { takesValue: true },
|
|
702
750
|
folder: { takesValue: true },
|
|
703
751
|
from: { takesValue: true },
|
|
704
|
-
scale: { takesValue: true }
|
|
752
|
+
scale: { takesValue: true },
|
|
753
|
+
'figma-tag': { takesValue: true },
|
|
754
|
+
'figma-file': { takesValue: true }
|
|
705
755
|
}, 'fundus asset ingest');
|
|
706
|
-
const [source] = requirePositionals(parsed, ['<file|figma-selection-link>'], 'fundus asset ingest');
|
|
707
756
|
const from = parsed.flags.from === undefined ? 'file' : parsed.flags.from;
|
|
708
757
|
if (from !== 'file' && from !== 'figma') {
|
|
709
758
|
throw new CliUsageError('Flag "--from" must be "file" or "figma" — run `fundus asset ingest --help`.');
|
|
@@ -714,6 +763,11 @@ async function runAssetIngest(argumentList) {
|
|
|
714
763
|
if (from === 'file' && scale !== undefined) {
|
|
715
764
|
throw new CliUsageError('Flag "--scale" is only valid with `--from figma` — run `fundus asset ingest --help`.');
|
|
716
765
|
}
|
|
766
|
+
const figmaTag = typeof parsed.flags['figma-tag'] === 'string' ? parsed.flags['figma-tag'] : undefined;
|
|
767
|
+
const figmaFile = typeof parsed.flags['figma-file'] === 'string' ? parsed.flags['figma-file'] : undefined;
|
|
768
|
+
if (from === 'file' && (figmaTag !== undefined || figmaFile !== undefined)) {
|
|
769
|
+
throw new CliUsageError('Flags "--figma-tag" and "--figma-file" are only valid with `--from figma` — run `fundus asset ingest --help`.');
|
|
770
|
+
}
|
|
717
771
|
let parametersPatch = typeof parsed.flags.parameters === 'string'
|
|
718
772
|
? parseJsonFlag('parameters', parsed.flags.parameters)
|
|
719
773
|
: undefined;
|
|
@@ -723,15 +777,56 @@ async function runAssetIngest(argumentList) {
|
|
|
723
777
|
: undefined;
|
|
724
778
|
const folder = typeof parsed.flags.folder === 'string' ? parseFolderToken(parsed.flags.folder) : '';
|
|
725
779
|
if (from === 'figma') {
|
|
780
|
+
const type = parsed.flags.type;
|
|
781
|
+
if (typeof type !== 'string' || (type !== 'image' && type !== 'slice')) {
|
|
782
|
+
throw new CliUsageError('Figma ingest requires --type <image|slice> — run `fundus asset ingest --help`.');
|
|
783
|
+
}
|
|
784
|
+
if (figmaTag !== undefined) {
|
|
785
|
+
if (parsed.positionals.length > 0) {
|
|
786
|
+
throw new CliUsageError('Figma tag ingest takes no positional link — `--figma-tag` and a node selection link are mutually exclusive. Run `fundus asset ingest --help`.');
|
|
787
|
+
}
|
|
788
|
+
if (parsed.flags.id !== undefined) {
|
|
789
|
+
throw new CliUsageError('Figma tag ingest forbids --id: the tag is the asset id. Run `fundus asset ingest --help`.');
|
|
790
|
+
}
|
|
791
|
+
const fileKey = figmaFile === undefined ? undefined : resolveFigmaFileKeyFlag(figmaFile);
|
|
792
|
+
const parametersPatchForTag = applySliceCompressionFlags({
|
|
793
|
+
type,
|
|
794
|
+
flags: compressionFlags,
|
|
795
|
+
parametersPatch,
|
|
796
|
+
commandPath: 'fundus asset ingest'
|
|
797
|
+
});
|
|
798
|
+
const operations = await createOperations();
|
|
799
|
+
const state = await operations.importAsset({
|
|
800
|
+
importer: 'figma',
|
|
801
|
+
input: {
|
|
802
|
+
mode: 'tag',
|
|
803
|
+
tag: figmaTag,
|
|
804
|
+
format: 'png',
|
|
805
|
+
...(fileKey !== undefined ? { fileKey } : {}),
|
|
806
|
+
...(scale !== undefined ? { scale } : {})
|
|
807
|
+
},
|
|
808
|
+
type,
|
|
809
|
+
id: figmaTag,
|
|
810
|
+
folder,
|
|
811
|
+
parametersPatch: parametersPatchForTag,
|
|
812
|
+
manifests
|
|
813
|
+
});
|
|
814
|
+
const asset = assetById(state, figmaTag);
|
|
815
|
+
if (parsed.flags.json) {
|
|
816
|
+
printJsonDocument({ ok: true, asset, issues: state.issues });
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
printMutationResult(`Imported asset "${figmaTag}" from Figma tag as ${type}.`, asset, state);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const [source] = requirePositionals(parsed, ['<figma-selection-link>'], 'fundus asset ingest');
|
|
726
823
|
if (typeof parsed.flags.id !== 'string') {
|
|
727
824
|
throw new CliUsageError('Figma ingest requires --id <id> — run `fundus asset ingest --help`.');
|
|
728
825
|
}
|
|
729
|
-
if (
|
|
730
|
-
|
|
731
|
-
throw new CliUsageError('Figma ingest requires --type <image|slice> — run `fundus asset ingest --help`.');
|
|
826
|
+
if (figmaFile !== undefined) {
|
|
827
|
+
throw new CliUsageError('Flag "--figma-file" is only valid in tag mode (with `--figma-tag`) — run `fundus asset ingest --help`.');
|
|
732
828
|
}
|
|
733
829
|
const id = parsed.flags.id;
|
|
734
|
-
const type = parsed.flags.type;
|
|
735
830
|
parametersPatch = applySliceCompressionFlags({
|
|
736
831
|
type,
|
|
737
832
|
flags: compressionFlags,
|
|
@@ -756,6 +851,7 @@ async function runAssetIngest(argumentList) {
|
|
|
756
851
|
printMutationResult(`Imported asset "${id}" from Figma as ${type}.`, asset, state);
|
|
757
852
|
return;
|
|
758
853
|
}
|
|
854
|
+
const [source] = requirePositionals(parsed, ['<file>'], 'fundus asset ingest');
|
|
759
855
|
const filePath = source;
|
|
760
856
|
const absolutePath = resolve(process.cwd(), filePath);
|
|
761
857
|
let bytes;
|
|
@@ -797,6 +893,20 @@ function parseFigmaScale(value, commandPath) {
|
|
|
797
893
|
}
|
|
798
894
|
throw new CliUsageError(`Flag "--scale" must be 1, 2, 3, or 4 — run \`${commandPath} --help\`.`);
|
|
799
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* `--figma-file` accepts either a raw Figma file key or a full design/file link.
|
|
898
|
+
* A value that parses as a URL is treated as a link (fileKey extracted via
|
|
899
|
+
* `parseFigmaFileKey`); anything else is passed through as a raw key.
|
|
900
|
+
*/
|
|
901
|
+
function resolveFigmaFileKeyFlag(value) {
|
|
902
|
+
try {
|
|
903
|
+
new URL(value);
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
return value;
|
|
907
|
+
}
|
|
908
|
+
return parseFigmaFileKey(value);
|
|
909
|
+
}
|
|
800
910
|
function canonicalFigmaSelectionLink(source) {
|
|
801
911
|
const url = new URL(`https://www.figma.com/design/${encodeURIComponent(source.fileKey)}/fundus`);
|
|
802
912
|
url.searchParams.set('node-id', source.nodeId.replace(':', '-'));
|
|
@@ -806,6 +916,8 @@ async function runAssetReimport(argumentList) {
|
|
|
806
916
|
const parsed = parseArguments(argumentList, {
|
|
807
917
|
...COMMON_FLAGS,
|
|
808
918
|
'figma-link': { takesValue: true },
|
|
919
|
+
'figma-tag': { takesValue: true },
|
|
920
|
+
'figma-file': { takesValue: true },
|
|
809
921
|
scale: { takesValue: true }
|
|
810
922
|
}, 'fundus asset reimport');
|
|
811
923
|
const [id] = requirePositionals(parsed, ['<id>'], 'fundus asset reimport');
|
|
@@ -813,9 +925,20 @@ async function runAssetReimport(argumentList) {
|
|
|
813
925
|
? parseFigmaScale(parsed.flags.scale, 'fundus asset reimport')
|
|
814
926
|
: undefined;
|
|
815
927
|
const figmaLink = typeof parsed.flags['figma-link'] === 'string' ? parsed.flags['figma-link'] : undefined;
|
|
928
|
+
const figmaTag = typeof parsed.flags['figma-tag'] === 'string' ? parsed.flags['figma-tag'] : undefined;
|
|
929
|
+
const figmaFile = typeof parsed.flags['figma-file'] === 'string' ? parsed.flags['figma-file'] : undefined;
|
|
930
|
+
if (figmaTag !== undefined && figmaTag === '') {
|
|
931
|
+
throw new CliUsageError('Flag "--figma-tag" needs a non-empty tag — run `fundus asset reimport --help`.');
|
|
932
|
+
}
|
|
933
|
+
const hasOverride = figmaLink !== undefined ||
|
|
934
|
+
figmaTag !== undefined ||
|
|
935
|
+
figmaFile !== undefined ||
|
|
936
|
+
scale !== undefined;
|
|
816
937
|
const operations = await createOperations();
|
|
817
938
|
let state;
|
|
818
|
-
|
|
939
|
+
// A tag rename changes the id; look the asset up under its effective new id.
|
|
940
|
+
let effectiveId = id;
|
|
941
|
+
if (!hasOverride) {
|
|
819
942
|
state = await operations.reimportAsset(id);
|
|
820
943
|
}
|
|
821
944
|
else {
|
|
@@ -825,28 +948,58 @@ async function runAssetReimport(argumentList) {
|
|
|
825
948
|
throw new Error(`Asset "${id}" does not have a Figma source.`);
|
|
826
949
|
}
|
|
827
950
|
const source = validateFigmaSourceParameters(currentAsset.importSource.parameters);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
951
|
+
if (source.mode === 'tag') {
|
|
952
|
+
if (figmaLink !== undefined) {
|
|
953
|
+
throw new CliUsageError('Flag "--figma-link" is invalid for a Figma tag asset — change the tag with --figma-tag or re-point with --figma-file and/or --scale. Run `fundus asset reimport --help`.');
|
|
954
|
+
}
|
|
955
|
+
const fileKey = figmaFile === undefined ? undefined : resolveFigmaFileKeyFlag(figmaFile);
|
|
956
|
+
state = await operations.retagFigmaAsset(id, {
|
|
957
|
+
...(figmaTag !== undefined ? { tag: figmaTag } : {}),
|
|
958
|
+
...(fileKey !== undefined ? { fileKey } : {}),
|
|
959
|
+
...(scale !== undefined ? { scale } : {})
|
|
960
|
+
});
|
|
961
|
+
// A different tag renames the asset (the tag is the id); else the id is unchanged.
|
|
962
|
+
effectiveId = figmaTag ?? id;
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
if (figmaFile !== undefined) {
|
|
966
|
+
throw new CliUsageError('Flag "--figma-file" is invalid for a Figma node asset — it applies only to tag assets. Run `fundus asset reimport --help`.');
|
|
967
|
+
}
|
|
968
|
+
if (figmaTag !== undefined) {
|
|
969
|
+
throw new CliUsageError('Flag "--figma-tag" is invalid for a Figma node asset — it applies only to tag assets. Run `fundus asset reimport --help`.');
|
|
970
|
+
}
|
|
971
|
+
state = await operations.reimportAsset(id, {
|
|
972
|
+
url: figmaLink ?? canonicalFigmaSelectionLink(source),
|
|
973
|
+
format: 'png',
|
|
974
|
+
scale: scale ?? source.scale
|
|
975
|
+
});
|
|
976
|
+
}
|
|
833
977
|
}
|
|
834
|
-
const asset = assetById(state,
|
|
978
|
+
const asset = assetById(state, effectiveId);
|
|
835
979
|
if (parsed.flags.json) {
|
|
836
980
|
printJsonDocument({ ok: true, asset, issues: state.issues });
|
|
837
981
|
return;
|
|
838
982
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
983
|
+
let headline;
|
|
984
|
+
if (!hasOverride) {
|
|
985
|
+
headline = `Refreshed asset "${id}" from its saved source.`;
|
|
986
|
+
}
|
|
987
|
+
else if (effectiveId !== id) {
|
|
988
|
+
headline = `Reimported asset "${id}" as "${effectiveId}" with updated Figma tag.`;
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
headline = `Reimported asset "${effectiveId}" with updated Figma source.`;
|
|
992
|
+
}
|
|
993
|
+
printMutationResult(headline, asset, state);
|
|
842
994
|
}
|
|
843
995
|
async function runAssetReplace(argumentList) {
|
|
844
996
|
const parsed = parseArguments(argumentList, {
|
|
845
997
|
...COMMON_FLAGS,
|
|
846
998
|
from: { takesValue: true },
|
|
847
|
-
scale: { takesValue: true }
|
|
999
|
+
scale: { takesValue: true },
|
|
1000
|
+
'figma-tag': { takesValue: true },
|
|
1001
|
+
'figma-file': { takesValue: true }
|
|
848
1002
|
}, 'fundus asset replace');
|
|
849
|
-
const [id, source] = requirePositionals(parsed, ['<id>', '<file|figma-selection-link>'], 'fundus asset replace');
|
|
850
1003
|
const from = parsed.flags.from === undefined ? 'file' : parsed.flags.from;
|
|
851
1004
|
if (from !== 'file' && from !== 'figma') {
|
|
852
1005
|
throw new CliUsageError('Flag "--from" must be "file" or "figma" — run `fundus asset replace --help`.');
|
|
@@ -857,8 +1010,39 @@ async function runAssetReplace(argumentList) {
|
|
|
857
1010
|
if (from === 'file' && scale !== undefined) {
|
|
858
1011
|
throw new CliUsageError('Flag "--scale" is only valid with `--from figma` — run `fundus asset replace --help`.');
|
|
859
1012
|
}
|
|
1013
|
+
const figmaTag = typeof parsed.flags['figma-tag'] === 'string' ? parsed.flags['figma-tag'] : undefined;
|
|
1014
|
+
const figmaFile = typeof parsed.flags['figma-file'] === 'string' ? parsed.flags['figma-file'] : undefined;
|
|
1015
|
+
if (from === 'file' && (figmaTag !== undefined || figmaFile !== undefined)) {
|
|
1016
|
+
throw new CliUsageError('Flags "--figma-tag" and "--figma-file" are only valid with `--from figma` — run `fundus asset replace --help`.');
|
|
1017
|
+
}
|
|
1018
|
+
if (from === 'figma' && figmaTag !== undefined) {
|
|
1019
|
+
const [id] = requirePositionals(parsed, ['<id>'], 'fundus asset replace');
|
|
1020
|
+
const fileKey = figmaFile === undefined ? undefined : resolveFigmaFileKeyFlag(figmaFile);
|
|
1021
|
+
const operations = await createOperations();
|
|
1022
|
+
const state = await operations.replaceRawFromSource(id, {
|
|
1023
|
+
importer: 'figma',
|
|
1024
|
+
input: {
|
|
1025
|
+
mode: 'tag',
|
|
1026
|
+
tag: figmaTag,
|
|
1027
|
+
format: 'png',
|
|
1028
|
+
...(fileKey !== undefined ? { fileKey } : {}),
|
|
1029
|
+
...(scale !== undefined ? { scale } : {})
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
const asset = assetById(state, id);
|
|
1033
|
+
if (parsed.flags.json) {
|
|
1034
|
+
printJsonDocument({ ok: true, asset, issues: state.issues });
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
printMutationResult(`Replaced raw file for asset "${id}" from Figma tag.`, asset, state);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const [id, source] = requirePositionals(parsed, ['<id>', from === 'figma' ? '<figma-selection-link>' : '<file>'], 'fundus asset replace');
|
|
860
1041
|
const operations = await createOperations();
|
|
861
1042
|
if (from === 'figma') {
|
|
1043
|
+
if (figmaFile !== undefined) {
|
|
1044
|
+
throw new CliUsageError('Flag "--figma-file" is only valid in tag mode (with `--figma-tag`) — run `fundus asset replace --help`.');
|
|
1045
|
+
}
|
|
862
1046
|
const state = await operations.replaceRawFromSource(id, {
|
|
863
1047
|
importer: 'figma',
|
|
864
1048
|
input: { url: source, format: 'png', ...(scale !== undefined ? { scale } : {}) }
|
|
@@ -149,6 +149,10 @@ export function resolveConfig(projectRoot, value, registry = serverPluginsByType
|
|
|
149
149
|
if (figma.token !== undefined && (typeof figma.token !== 'string' || figma.token.length === 0)) {
|
|
150
150
|
throw new Error('fundus.config: "import.figma.token" must be a non-empty string.');
|
|
151
151
|
}
|
|
152
|
+
if (figma.defaultFileKey !== undefined &&
|
|
153
|
+
(typeof figma.defaultFileKey !== 'string' || figma.defaultFileKey.length === 0)) {
|
|
154
|
+
throw new Error('fundus.config: "import.figma.defaultFileKey" must be a non-empty string.');
|
|
155
|
+
}
|
|
152
156
|
const defaults = figma.defaults === undefined ? {} : figma.defaults;
|
|
153
157
|
if (!isRecord(defaults)) {
|
|
154
158
|
throw new Error('fundus.config: "import.figma.defaults" must be an object.');
|
|
@@ -178,6 +182,7 @@ export function resolveConfig(projectRoot, value, registry = serverPluginsByType
|
|
|
178
182
|
import: {
|
|
179
183
|
figma: {
|
|
180
184
|
...(figma.token !== undefined ? { token: figma.token } : {}),
|
|
185
|
+
...(figma.defaultFileKey !== undefined ? { defaultFileKey: figma.defaultFileKey } : {}),
|
|
181
186
|
defaults: { scale }
|
|
182
187
|
}
|
|
183
188
|
},
|
package/dist/server/http.js
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
|
-
import { type ServerSourceImporter } from './contract.js';
|
|
1
|
+
import { type ImporterContext, type ServerSourceImporter } from './contract.js';
|
|
2
2
|
import { type PinnedHttpsTarget } from './pinned-https.js';
|
|
3
|
-
export interface
|
|
3
|
+
export interface FigmaNodeSourceParameters {
|
|
4
|
+
mode?: 'node';
|
|
4
5
|
fileKey: string;
|
|
5
6
|
nodeId: string;
|
|
6
7
|
format: 'png';
|
|
7
8
|
scale: 1 | 2 | 3 | 4;
|
|
8
9
|
}
|
|
9
|
-
export interface
|
|
10
|
+
export interface FigmaTagSourceParameters {
|
|
11
|
+
mode: 'tag';
|
|
12
|
+
fileKey: string;
|
|
13
|
+
/** The Fundus asset id carried in Figma as `sharedPluginData.fundus.assetId`. */
|
|
14
|
+
fundusTag: string;
|
|
15
|
+
/** Resolved node id (colon-form), cached from the last tag resolution. */
|
|
16
|
+
nodeId: string;
|
|
17
|
+
format: 'png';
|
|
18
|
+
scale: 1 | 2 | 3 | 4;
|
|
19
|
+
}
|
|
20
|
+
export type FigmaSourceParameters = FigmaNodeSourceParameters | FigmaTagSourceParameters;
|
|
21
|
+
export interface FigmaNodeImportInput {
|
|
22
|
+
mode?: 'node';
|
|
10
23
|
url: string;
|
|
11
24
|
format?: 'png';
|
|
12
25
|
scale?: 1 | 2 | 3 | 4;
|
|
13
26
|
}
|
|
27
|
+
export interface FigmaTagImportInput {
|
|
28
|
+
mode: 'tag';
|
|
29
|
+
tag: string;
|
|
30
|
+
fileKey?: string;
|
|
31
|
+
format?: 'png';
|
|
32
|
+
scale?: 1 | 2 | 3 | 4;
|
|
33
|
+
}
|
|
34
|
+
export type FigmaImportInput = FigmaNodeImportInput | FigmaTagImportInput;
|
|
14
35
|
export declare function parseFigmaSelectionUrl(value: string): {
|
|
15
36
|
fileKey: string;
|
|
16
37
|
nodeId: string;
|
|
17
38
|
};
|
|
39
|
+
export declare function parseFigmaFileKey(value: string): string;
|
|
18
40
|
export declare function validateFigmaSourceParameters(value: unknown): FigmaSourceParameters;
|
|
19
41
|
export declare function resolveSafeDownloadTarget(value: string | URL): Promise<PinnedHttpsTarget>;
|
|
20
42
|
export declare function assertSafeDownloadUrl(value: string | URL): Promise<URL>;
|
|
43
|
+
export declare function resolveTagToNode(fileKey: string, tag: string, context: ImporterContext): Promise<string>;
|
|
21
44
|
export declare const figmaSourceImporter: ServerSourceImporter<FigmaImportInput, FigmaSourceParameters>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { lookup } from 'node:dns/promises';
|
|
2
2
|
import { isIP } from 'node:net';
|
|
3
3
|
import sharp from 'sharp';
|
|
4
|
+
import { isValidAssetId } from '../../core/index.js';
|
|
4
5
|
import { SourceImportError, invalidInput } from './contract.js';
|
|
5
6
|
import { requestPinnedHttps } from './pinned-https.js';
|
|
6
7
|
const MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
|
|
@@ -36,6 +37,24 @@ export function parseFigmaSelectionUrl(value) {
|
|
|
36
37
|
const [left, right] = nodeIds[0].split(/[-:]/);
|
|
37
38
|
return { fileKey: segments[1], nodeId: `${left}:${right}` };
|
|
38
39
|
}
|
|
40
|
+
export function parseFigmaFileKey(value) {
|
|
41
|
+
let url;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(value);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return invalidInput('Enter a valid Figma file link.');
|
|
47
|
+
}
|
|
48
|
+
if (url.protocol !== 'https:' ||
|
|
49
|
+
(url.hostname !== 'figma.com' && url.hostname !== 'www.figma.com')) {
|
|
50
|
+
return invalidInput('Figma links must use HTTPS on figma.com.');
|
|
51
|
+
}
|
|
52
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
53
|
+
if (!['design', 'file', 'proto'].includes(segments[0] ?? '') || !segments[1]) {
|
|
54
|
+
return invalidInput('The Figma link must point to a design, file, or prototype.');
|
|
55
|
+
}
|
|
56
|
+
return segments[1];
|
|
57
|
+
}
|
|
39
58
|
function validateScale(value) {
|
|
40
59
|
if (value !== 1 && value !== 2 && value !== 3 && value !== 4) {
|
|
41
60
|
return invalidInput('Figma export scale must be 1, 2, 3, or 4.');
|
|
@@ -44,12 +63,31 @@ function validateScale(value) {
|
|
|
44
63
|
}
|
|
45
64
|
function validateInput(value) {
|
|
46
65
|
const input = record(value);
|
|
47
|
-
if (!input
|
|
66
|
+
if (!input)
|
|
48
67
|
return invalidInput('Figma import requires a URL.');
|
|
49
68
|
if (input.format !== undefined && input.format !== 'png') {
|
|
50
69
|
return invalidInput('Figma import currently supports PNG only.');
|
|
51
70
|
}
|
|
71
|
+
if (input.mode === 'tag') {
|
|
72
|
+
if (typeof input.tag !== 'string' || !isValidAssetId(input.tag)) {
|
|
73
|
+
return invalidInput('Figma tag import requires a valid Fundus asset id as the tag.');
|
|
74
|
+
}
|
|
75
|
+
if (input.fileKey !== undefined &&
|
|
76
|
+
(typeof input.fileKey !== 'string' || input.fileKey.length === 0)) {
|
|
77
|
+
return invalidInput('Figma tag import fileKey must be a non-empty string.');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
mode: 'tag',
|
|
81
|
+
tag: input.tag,
|
|
82
|
+
format: 'png',
|
|
83
|
+
...(input.fileKey !== undefined ? { fileKey: input.fileKey } : {}),
|
|
84
|
+
...(input.scale !== undefined ? { scale: validateScale(input.scale) } : {})
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (typeof input.url !== 'string')
|
|
88
|
+
return invalidInput('Figma import requires a URL.');
|
|
52
89
|
return {
|
|
90
|
+
mode: 'node',
|
|
53
91
|
url: input.url,
|
|
54
92
|
format: 'png',
|
|
55
93
|
...(input.scale !== undefined ? { scale: validateScale(input.scale) } : {})
|
|
@@ -65,6 +103,19 @@ export function validateFigmaSourceParameters(value) {
|
|
|
65
103
|
source.format !== 'png') {
|
|
66
104
|
return invalidInput('Stored Figma source parameters are invalid.');
|
|
67
105
|
}
|
|
106
|
+
if (source.mode === 'tag') {
|
|
107
|
+
if (typeof source.fundusTag !== 'string' || !isValidAssetId(source.fundusTag)) {
|
|
108
|
+
return invalidInput('Stored Figma source parameters are invalid.');
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
mode: 'tag',
|
|
112
|
+
fileKey: source.fileKey,
|
|
113
|
+
fundusTag: source.fundusTag,
|
|
114
|
+
nodeId: source.nodeId,
|
|
115
|
+
format: 'png',
|
|
116
|
+
scale: validateScale(source.scale)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
68
119
|
return {
|
|
69
120
|
fileKey: source.fileKey,
|
|
70
121
|
nodeId: source.nodeId,
|
|
@@ -332,6 +383,49 @@ async function validatePng(bytes) {
|
|
|
332
383
|
throw new SourceImportError('image-too-large', 'Figma export exceeds the 32 megapixel limit.');
|
|
333
384
|
}
|
|
334
385
|
}
|
|
386
|
+
function collectTaggedNodeIds(node, tag, matches) {
|
|
387
|
+
const current = record(node);
|
|
388
|
+
if (!current)
|
|
389
|
+
return;
|
|
390
|
+
const shared = record(current.sharedPluginData);
|
|
391
|
+
const fundus = shared ? record(shared.fundus) : null;
|
|
392
|
+
if (fundus && fundus.assetId === tag && typeof current.id === 'string') {
|
|
393
|
+
matches.push(current.id);
|
|
394
|
+
}
|
|
395
|
+
if (Array.isArray(current.children)) {
|
|
396
|
+
for (const child of current.children)
|
|
397
|
+
collectTaggedNodeIds(child, tag, matches);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
export async function resolveTagToNode(fileKey, tag, context) {
|
|
401
|
+
const token = context.config.import.figma.token;
|
|
402
|
+
if (!token) {
|
|
403
|
+
throw new SourceImportError('not-configured', 'Figma access is not configured.');
|
|
404
|
+
}
|
|
405
|
+
const endpoint = new URL(`https://api.figma.com/v1/files/${encodeURIComponent(fileKey)}`);
|
|
406
|
+
endpoint.searchParams.set('plugin_data', 'shared');
|
|
407
|
+
const jsonBytes = await fetchWithDeadline(context.fetch, endpoint, { redirect: 'error', headers: { 'X-Figma-Token': token } }, async (response) => new Uint8Array(await response.arrayBuffer()));
|
|
408
|
+
let payload;
|
|
409
|
+
try {
|
|
410
|
+
payload = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
throw new SourceImportError('invalid-response', 'Figma returned an invalid response.');
|
|
414
|
+
}
|
|
415
|
+
const body = record(payload);
|
|
416
|
+
if (!body || body.err || (typeof body.status === 'number' && body.status >= 400)) {
|
|
417
|
+
throw new SourceImportError('invalid-response', 'Figma returned an invalid response.');
|
|
418
|
+
}
|
|
419
|
+
const matches = [];
|
|
420
|
+
collectTaggedNodeIds(body.document, tag, matches);
|
|
421
|
+
if (matches.length === 0) {
|
|
422
|
+
throw new SourceImportError('not-found', `No Figma node is tagged "${tag}" in this file.`);
|
|
423
|
+
}
|
|
424
|
+
if (matches.length > 1) {
|
|
425
|
+
throw new SourceImportError('ambiguous-tag', `More than one Figma node is tagged "${tag}" in this file.`);
|
|
426
|
+
}
|
|
427
|
+
return matches[0];
|
|
428
|
+
}
|
|
335
429
|
async function materialize(source, context) {
|
|
336
430
|
const token = context.config.import.figma.token;
|
|
337
431
|
if (!token) {
|
|
@@ -371,6 +465,21 @@ export const figmaSourceImporter = {
|
|
|
371
465
|
label: 'Figma',
|
|
372
466
|
validateInput,
|
|
373
467
|
async importRaw(input, context) {
|
|
468
|
+
if (input.mode === 'tag') {
|
|
469
|
+
const fileKey = input.fileKey ?? context.config.import.figma.defaultFileKey;
|
|
470
|
+
if (!fileKey) {
|
|
471
|
+
throw new SourceImportError('not-configured', 'Figma tag import needs a fileKey or import.figma.defaultFileKey in fundus.config.');
|
|
472
|
+
}
|
|
473
|
+
const nodeId = await resolveTagToNode(fileKey, input.tag, context);
|
|
474
|
+
return materialize({
|
|
475
|
+
mode: 'tag',
|
|
476
|
+
fileKey,
|
|
477
|
+
fundusTag: input.tag,
|
|
478
|
+
nodeId,
|
|
479
|
+
format: 'png',
|
|
480
|
+
scale: input.scale ?? context.config.import.figma.defaults.scale
|
|
481
|
+
}, context);
|
|
482
|
+
}
|
|
374
483
|
const parsed = parseFigmaSelectionUrl(input.url);
|
|
375
484
|
return materialize({
|
|
376
485
|
fileKey: parsed.fileKey,
|
|
@@ -381,6 +490,10 @@ export const figmaSourceImporter = {
|
|
|
381
490
|
},
|
|
382
491
|
validateSourceParameters: validateFigmaSourceParameters,
|
|
383
492
|
async reimportRaw(source, context) {
|
|
493
|
+
if (source.mode === 'tag') {
|
|
494
|
+
const nodeId = await resolveTagToNode(source.fileKey, source.fundusTag, context);
|
|
495
|
+
return materialize({ ...source, nodeId }, context);
|
|
496
|
+
}
|
|
384
497
|
return materialize(source, context);
|
|
385
498
|
}
|
|
386
499
|
};
|