@smallpen/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/bin/command-contract.mjs +7 -8
- package/bin/design-output.mjs +65 -165
- package/bin/help-topics.mjs +2 -2
- package/bin/help.mjs +24 -15
- package/bin/inspection-output.mjs +825 -0
- package/bin/result-files.mjs +1 -1
- package/bin/smallpen.mjs +82 -251
- package/bin/workflow.mjs +4 -4
- package/package.json +3 -3
package/bin/result-files.mjs
CHANGED
|
@@ -17,7 +17,7 @@ export const OUTPUT_LIMIT_BYTES = 8192;
|
|
|
17
17
|
export const RESULT_RETENTION_MS = 30 * 60 * 1000;
|
|
18
18
|
const uuid = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}";
|
|
19
19
|
const role =
|
|
20
|
-
"(?:result|render(?:-matrix-\\d{2})?|outline|wireframe|text|undo|changes|evidence|matrix|validation|flow-connections|flow-verification)";
|
|
20
|
+
"(?:result|render(?:-matrix-\\d{2})?|outline|spec|wireframe|text|undo|changes|evidence|matrix|validation|flow-connections|flow-verification)";
|
|
21
21
|
const managedName = new RegExp(
|
|
22
22
|
`^(smallpen-\\d{13}-${uuid})-(${role}|owner-(\\d+))\\.(?:png|json|txt)(?:\\.smallpen-(\\d+)-${uuid}\\.tmp)?$`,
|
|
23
23
|
);
|
package/bin/smallpen.mjs
CHANGED
|
@@ -100,7 +100,6 @@ import {
|
|
|
100
100
|
} from "@smallpen/core";
|
|
101
101
|
import {
|
|
102
102
|
applyOperationBatch,
|
|
103
|
-
createEvidence,
|
|
104
103
|
defaultLibraryCacheRoot,
|
|
105
104
|
fontVariantName,
|
|
106
105
|
importDraft,
|
|
@@ -115,7 +114,6 @@ import {
|
|
|
115
114
|
readBatchHistory,
|
|
116
115
|
openRemoteLibrary,
|
|
117
116
|
measureProjectionText,
|
|
118
|
-
renderProjection,
|
|
119
117
|
replayRecordedBatch,
|
|
120
118
|
resolveWorkspace,
|
|
121
119
|
sfntToWoff,
|
|
@@ -155,12 +153,13 @@ import {
|
|
|
155
153
|
componentSummaryText,
|
|
156
154
|
platformHints,
|
|
157
155
|
localWireframe,
|
|
158
|
-
|
|
156
|
+
resolveDesignLayout,
|
|
159
157
|
outlineTree,
|
|
160
158
|
selectedDesign,
|
|
161
159
|
themeSettings,
|
|
162
160
|
validateDesign,
|
|
163
161
|
} from "./design-output.mjs";
|
|
162
|
+
import { inspectionCoverage, inspectionReport, inspectionText } from "./inspection-output.mjs";
|
|
164
163
|
import { definitionReuseAdvice } from "./reuse-advice.mjs";
|
|
165
164
|
import { warningOutput } from "./warning-output.mjs";
|
|
166
165
|
import {
|
|
@@ -471,6 +470,9 @@ function printJson(value) {
|
|
|
471
470
|
"mimeType",
|
|
472
471
|
"output",
|
|
473
472
|
"imagePath",
|
|
473
|
+
"specPath",
|
|
474
|
+
"wireframePath",
|
|
475
|
+
"layout",
|
|
474
476
|
"evidencePath",
|
|
475
477
|
"matrixPath",
|
|
476
478
|
"status",
|
|
@@ -626,6 +628,9 @@ function printJson(value) {
|
|
|
626
628
|
"answersTemplate",
|
|
627
629
|
"output",
|
|
628
630
|
"imagePath",
|
|
631
|
+
"specPath",
|
|
632
|
+
"wireframePath",
|
|
633
|
+
"layout",
|
|
629
634
|
"evidencePath",
|
|
630
635
|
]) {
|
|
631
636
|
if (Buffer.byteLength(json) + 1 <= limitBytes) break;
|
|
@@ -1641,7 +1646,7 @@ function nameDiagnostics(diagnostics = [], projection) {
|
|
|
1641
1646
|
const rest = [];
|
|
1642
1647
|
for (const diagnostic of diagnostics) {
|
|
1643
1648
|
if (diagnostic.code === "layout_projection_partial") continue;
|
|
1644
|
-
if (diagnostic.code === "font_render_fallback" && !diagnostic.
|
|
1649
|
+
if (diagnostic.code === "font_render_fallback" && !Array.isArray(diagnostic.nodeIds)) {
|
|
1645
1650
|
const family = diagnostic.details?.requestedFont ?? diagnostic.message;
|
|
1646
1651
|
const entry = fallbacks.get(family);
|
|
1647
1652
|
if (entry) {
|
|
@@ -1675,120 +1680,6 @@ function nameDiagnostics(diagnostics = [], projection) {
|
|
|
1675
1680
|
});
|
|
1676
1681
|
}
|
|
1677
1682
|
|
|
1678
|
-
// The renderer reports one font_render_fallback per text node. Collapse them
|
|
1679
|
-
// to one diagnostic per missing family that names the fonts it can draw and
|
|
1680
|
-
// the import-font command that adds the missing one.
|
|
1681
|
-
async function renderEvidence(product, options) {
|
|
1682
|
-
const bundle = await createEvidence(product, options);
|
|
1683
|
-
const fallbacks = new Map();
|
|
1684
|
-
const diagnostics = [];
|
|
1685
|
-
for (const diagnostic of bundle.render.diagnostics) {
|
|
1686
|
-
if (diagnostic.code !== "font_render_fallback") {
|
|
1687
|
-
diagnostics.push(diagnostic);
|
|
1688
|
-
continue;
|
|
1689
|
-
}
|
|
1690
|
-
const requestedFont =
|
|
1691
|
-
diagnostic.details?.requestedFont ??
|
|
1692
|
-
/substituted .+ for (.+)$/.exec(diagnostic.message)?.[1] ??
|
|
1693
|
-
"<family>";
|
|
1694
|
-
let collapsed = fallbacks.get(requestedFont);
|
|
1695
|
-
if (!collapsed) {
|
|
1696
|
-
collapsed = { diagnostic, nodeIds: [], paths: [], requestedFont };
|
|
1697
|
-
fallbacks.set(requestedFont, collapsed);
|
|
1698
|
-
diagnostics.push(collapsed);
|
|
1699
|
-
}
|
|
1700
|
-
if (diagnostic.nodeId !== undefined)
|
|
1701
|
-
collapsed.nodeIds.push(diagnostic.nodeId);
|
|
1702
|
-
if (diagnostic.path !== undefined) collapsed.paths.push(diagnostic.path);
|
|
1703
|
-
}
|
|
1704
|
-
if (fallbacks.size === 0) return bundle;
|
|
1705
|
-
const imported = [
|
|
1706
|
-
...new Set(
|
|
1707
|
-
[options.foundation, ...(options.libraries ?? []), product]
|
|
1708
|
-
.filter(Boolean)
|
|
1709
|
-
.flatMap((snapshot) =>
|
|
1710
|
-
snapshot.manifest.entries.assets.flatMap((entry) =>
|
|
1711
|
-
(snapshot.entries[entry].fonts ?? []).map(({ family }) => family),
|
|
1712
|
-
),
|
|
1713
|
-
),
|
|
1714
|
-
),
|
|
1715
|
-
].sort(compareText);
|
|
1716
|
-
const availableFonts = [
|
|
1717
|
-
"Source Sans Pro (bundled)",
|
|
1718
|
-
...imported.map((family) => `${family} (imported)`),
|
|
1719
|
-
];
|
|
1720
|
-
const collapsedEntries = new Set(fallbacks.values());
|
|
1721
|
-
const explained = diagnostics.map((entry) => {
|
|
1722
|
-
if (!collapsedEntries.has(entry)) return entry;
|
|
1723
|
-
const { requestedFont } = entry;
|
|
1724
|
-
return {
|
|
1725
|
-
availableFonts,
|
|
1726
|
-
code: entry.diagnostic.code,
|
|
1727
|
-
nodeIds: entry.nodeIds,
|
|
1728
|
-
details: {
|
|
1729
|
-
...entry.diagnostic.details,
|
|
1730
|
-
requestedFont,
|
|
1731
|
-
substituteFont:
|
|
1732
|
-
entry.diagnostic.details?.substituteFont ?? "Source Sans Pro",
|
|
1733
|
-
},
|
|
1734
|
-
message:
|
|
1735
|
-
`${entry.diagnostic.message} on ${entry.nodeIds.length} text node(s): ` +
|
|
1736
|
-
`${requestedFont} is neither bundled nor imported. Available fonts: ` +
|
|
1737
|
-
`${availableFonts.join(", ")}. Import ${requestedFont} with smallpen asset font import ` +
|
|
1738
|
-
"(once per weight/style; see nextOperations), or use an available family.",
|
|
1739
|
-
nextOperations: [
|
|
1740
|
-
{
|
|
1741
|
-
argv: [
|
|
1742
|
-
"import-font",
|
|
1743
|
-
product.locator,
|
|
1744
|
-
"--file",
|
|
1745
|
-
`<${requestedFont} .ttf|.otf|.woff file>`,
|
|
1746
|
-
"--family",
|
|
1747
|
-
requestedFont,
|
|
1748
|
-
"--weight",
|
|
1749
|
-
"<400|700|...>",
|
|
1750
|
-
"--json",
|
|
1751
|
-
],
|
|
1752
|
-
operation: "smallpen.import-font",
|
|
1753
|
-
},
|
|
1754
|
-
],
|
|
1755
|
-
nodeIds: entry.nodeIds,
|
|
1756
|
-
paths: entry.paths,
|
|
1757
|
-
requestedFont,
|
|
1758
|
-
};
|
|
1759
|
-
});
|
|
1760
|
-
bundle.render.diagnostics = explained;
|
|
1761
|
-
bundle.evidence.diagnostics = explained;
|
|
1762
|
-
return bundle;
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
async function deliverImage(
|
|
1766
|
-
bundle,
|
|
1767
|
-
{ product, foundation, libraries },
|
|
1768
|
-
output,
|
|
1769
|
-
includeBase64 = false,
|
|
1770
|
-
) {
|
|
1771
|
-
return {
|
|
1772
|
-
diagnostics: bundle.render.diagnostics,
|
|
1773
|
-
height: bundle.render.height,
|
|
1774
|
-
mimeType: bundle.render.mimeType,
|
|
1775
|
-
packageId: product.manifest.packageId,
|
|
1776
|
-
renderHash: bundle.render.renderHash,
|
|
1777
|
-
...workspaceRevisions(product, foundation, libraries),
|
|
1778
|
-
selection: bundle.evidence.selector,
|
|
1779
|
-
width: bundle.render.width,
|
|
1780
|
-
...(output === undefined
|
|
1781
|
-
? {}
|
|
1782
|
-
: { output: await writeAtomic(output, bundle.render.bytes) }),
|
|
1783
|
-
...(includeBase64
|
|
1784
|
-
? { base64: Buffer.from(bundle.render.bytes).toString("base64") }
|
|
1785
|
-
: {}),
|
|
1786
|
-
};
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
// The generated Design System page, the same node tree the editor draws:
|
|
1790
|
-
// every Token Cell and representative samples of each Component Set.
|
|
1791
|
-
// part narrows the page: {only, tokenPrefix, componentSetIds, allVariants}.
|
|
1792
1683
|
async function designSystemProjection(workspace, args, part) {
|
|
1793
1684
|
const { foundation, libraries, product } = workspace;
|
|
1794
1685
|
const runtime = await createWorkspaceRuntime(product, { foundation });
|
|
@@ -1830,25 +1721,6 @@ async function designSystemProjection(workspace, args, part) {
|
|
|
1830
1721
|
return tree;
|
|
1831
1722
|
}
|
|
1832
1723
|
|
|
1833
|
-
async function renderDesignSystem(workspace, args) {
|
|
1834
|
-
const { product, foundation, libraries } = workspace;
|
|
1835
|
-
const tree = await designSystemProjection(workspace, args);
|
|
1836
|
-
const render = await renderProjection(product, tree, {
|
|
1837
|
-
foundation,
|
|
1838
|
-
libraries,
|
|
1839
|
-
scale: numberOption(args, "--scale"),
|
|
1840
|
-
});
|
|
1841
|
-
return {
|
|
1842
|
-
evidence: {
|
|
1843
|
-
selector: {
|
|
1844
|
-
designSystem: true,
|
|
1845
|
-
locale: option(args, "--locale") ?? "en",
|
|
1846
|
-
},
|
|
1847
|
-
},
|
|
1848
|
-
render,
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
1724
|
async function designForCommand(workspace, args) {
|
|
1853
1725
|
if (flag(args, "--design-system")) {
|
|
1854
1726
|
const projection = await designSystemProjection(workspace, args);
|
|
@@ -1872,9 +1744,22 @@ async function designForCommand(workspace, args) {
|
|
|
1872
1744
|
|
|
1873
1745
|
async function designCommand(packagePath, args, override) {
|
|
1874
1746
|
const workspace = await tokenWorkspace(packagePath, args);
|
|
1875
|
-
|
|
1747
|
+
let design = override ?? (await designForCommand(workspace, args));
|
|
1876
1748
|
const format =
|
|
1877
1749
|
option(args, "--format") ?? (args[0] === "view" ? "outline" : "wireframe");
|
|
1750
|
+
if (["spec", "wireframe", "png"].includes(format))
|
|
1751
|
+
design = await resolveDesignLayout(workspace, design, {
|
|
1752
|
+
raster: format === "png", scale: numberOption(args, "--scale"),
|
|
1753
|
+
forExport: flag(args, "--evidence"),
|
|
1754
|
+
});
|
|
1755
|
+
let report;
|
|
1756
|
+
if (design.layoutSnapshot) {
|
|
1757
|
+
design.validation = await validateDesign(workspace, design);
|
|
1758
|
+
report = inspectionReport(workspace, design, {
|
|
1759
|
+
components: componentLookup(workspace), stored: storedNodesOf(workspace, design.selection),
|
|
1760
|
+
full: flag(args, "--full"),
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1878
1763
|
const receipt = {
|
|
1879
1764
|
packageId: workspace.product.manifest.packageId,
|
|
1880
1765
|
...workspaceRevisions(
|
|
@@ -1891,7 +1776,58 @@ async function designCommand(packagePath, args, override) {
|
|
|
1891
1776
|
: design.tokenSources?.map(({ path, value }) => ({ path, value })),
|
|
1892
1777
|
}),
|
|
1893
1778
|
diagnostics: nameDiagnostics(design.projection.diagnostics ?? [], design.projection),
|
|
1779
|
+
...(report ? {
|
|
1780
|
+
layout: { ...report.snapshot, diagnostics: undefined, diagnosticCount: report.snapshot.diagnostics.length },
|
|
1781
|
+
coverage: flag(args, "--full") ? report.coverage : inspectionCoverage(report.coverage),
|
|
1782
|
+
issueCount: report.issues.length,
|
|
1783
|
+
...(flag(args, "--full") ? { inspection: report } : {}),
|
|
1784
|
+
} : {}),
|
|
1894
1785
|
};
|
|
1786
|
+
if (flag(args, "--evidence")) {
|
|
1787
|
+
const prefix = option(args, "--output");
|
|
1788
|
+
if (flag(args, "--base64") && !prefix) return {
|
|
1789
|
+
...receipt, renderHash: design.render.renderHash, semanticTree: design.tree,
|
|
1790
|
+
specification: inspectionText(report), wireframe: localWireframe(design),
|
|
1791
|
+
image: { imageBase64: Buffer.from(design.render.bytes).toString("base64"),
|
|
1792
|
+
...workspaceRevisions(workspace.product, workspace.foundation, workspace.libraries),
|
|
1793
|
+
width: design.render.width, height: design.render.height,
|
|
1794
|
+
mimeType: "image/png", renderHash: design.render.renderHash },
|
|
1795
|
+
};
|
|
1796
|
+
const path = (kind, extension) => prefix ? `${prefix}${kind ? `.${kind}` : ""}.${extension}` : artifactPath(kind || "render", extension);
|
|
1797
|
+
const imagePath = await writeAtomic(path("", "png"), design.render.bytes);
|
|
1798
|
+
const specPath = await writeAtomic(path("spec", "txt"), inspectionText(report));
|
|
1799
|
+
const wireframePath = await writeAtomic(path("wireframe", "txt"), localWireframe(design));
|
|
1800
|
+
const regions = [];
|
|
1801
|
+
const viewport = design.layoutSnapshot.viewport;
|
|
1802
|
+
const scale = numberOption(args, "--scale") ?? 1;
|
|
1803
|
+
const regionsOf = node => {
|
|
1804
|
+
if (node.bounds) {
|
|
1805
|
+
const left = Math.max(0, Math.min(design.render.width, (node.bounds.x - viewport.x) * scale));
|
|
1806
|
+
const top = Math.max(0, Math.min(design.render.height, (node.bounds.y - viewport.y) * scale));
|
|
1807
|
+
const right = Math.max(0, Math.min(design.render.width, (node.bounds.x + node.bounds.width - viewport.x) * scale));
|
|
1808
|
+
const bottom = Math.max(0, Math.min(design.render.height, (node.bounds.y + node.bounds.height - viewport.y) * scale));
|
|
1809
|
+
regions.push({ nodeId: node.id, x: left, y: top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) });
|
|
1810
|
+
}
|
|
1811
|
+
node.children.forEach(regionsOf);
|
|
1812
|
+
};
|
|
1813
|
+
regionsOf(design.tree.root);
|
|
1814
|
+
const evidencePath = await writeAtomic(prefix ? `${prefix}.json` : artifactPath("evidence", "json"), canonicalJSON({
|
|
1815
|
+
...report, semanticTree: { ...design.tree, selection: design.selection }, renderHash: design.render.renderHash,
|
|
1816
|
+
packageId: workspace.product.manifest.packageId,
|
|
1817
|
+
packageHash: workspace.foundation
|
|
1818
|
+
? createHash("sha256").update(`${workspace.product.revision}\0${workspace.foundation.revision}`).digest("hex")
|
|
1819
|
+
: workspace.product.revision,
|
|
1820
|
+
...workspaceRevisions(workspace.product, workspace.foundation, workspace.libraries),
|
|
1821
|
+
renderer: design.layoutSnapshot.renderer, kernelHash: design.layoutSnapshot.kernelHash,
|
|
1822
|
+
selector: design.selection,
|
|
1823
|
+
snapshotId: design.layoutSnapshot.id, nodeRegions: regions,
|
|
1824
|
+
viewport: { width: design.render.width, height: design.render.height, scale },
|
|
1825
|
+
}));
|
|
1826
|
+
return { ...receipt, imagePath, specPath, wireframePath, evidencePath,
|
|
1827
|
+
renderHash: design.render.renderHash, width: design.render.width, height: design.render.height,
|
|
1828
|
+
...(flag(args, "--base64") ? { imageBase64: Buffer.from(design.render.bytes).toString("base64") } : {}),
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1895
1831
|
const hints = platformHints(workspace, design);
|
|
1896
1832
|
if (hints.length) receipt.hints = hints;
|
|
1897
1833
|
const textLayout = {
|
|
@@ -1951,12 +1887,7 @@ async function designCommand(packagePath, args, override) {
|
|
|
1951
1887
|
format === "wireframe"
|
|
1952
1888
|
? localWireframe(design, { full: flag(args, "--full") })
|
|
1953
1889
|
: format === "spec"
|
|
1954
|
-
?
|
|
1955
|
-
components,
|
|
1956
|
-
tokenName: tokenNamer(workspace),
|
|
1957
|
-
stored: storedNodesOf(workspace, design.selection),
|
|
1958
|
-
heading: `Spec · px, x/y from the top-left of ${design.tree.root.name} · themes ${themeSettings(workspace).selection.themes.join(", ") || "defaults"} · {path} = Token`,
|
|
1959
|
-
})
|
|
1890
|
+
? inspectionText(report, { details: flag(args, "--full") || Boolean(design.selection.nodeId) })
|
|
1960
1891
|
: outline;
|
|
1961
1892
|
const lines = text.split("\n");
|
|
1962
1893
|
// A page outline reads whole; long ones page at 80 lines.
|
|
@@ -1996,11 +1927,7 @@ async function designCommand(packagePath, args, override) {
|
|
|
1996
1927
|
};
|
|
1997
1928
|
}
|
|
1998
1929
|
if (format === "png") {
|
|
1999
|
-
const render =
|
|
2000
|
-
workspace.product,
|
|
2001
|
-
design.renderProjection ?? design.projection,
|
|
2002
|
-
{ ...workspace, crop: design.crop, scale: numberOption(args, "--scale") },
|
|
2003
|
-
);
|
|
1930
|
+
const render = design.render;
|
|
2004
1931
|
const output = await writeAtomic(
|
|
2005
1932
|
option(args, "--output") ?? artifactPath("render", "png"),
|
|
2006
1933
|
render.bytes,
|
|
@@ -2021,7 +1948,7 @@ async function designCommand(packagePath, args, override) {
|
|
|
2021
1948
|
? { imageBase64: Buffer.from(render.bytes).toString("base64") }
|
|
2022
1949
|
: {}),
|
|
2023
1950
|
diagnostics: nameDiagnostics(
|
|
2024
|
-
|
|
1951
|
+
design.layoutSnapshot.diagnostics.filter(
|
|
2025
1952
|
(diagnostic) =>
|
|
2026
1953
|
diagnostic.code !== "layout_projection_partial" ||
|
|
2027
1954
|
relevant.has(diagnostic.nodeId),
|
|
@@ -2030,7 +1957,8 @@ async function designCommand(packagePath, args, override) {
|
|
|
2030
1957
|
),
|
|
2031
1958
|
};
|
|
2032
1959
|
}
|
|
2033
|
-
const text = `${
|
|
1960
|
+
const text = format === "spec" ? `${inspectionText(report, { details: flag(args, "--full") || Boolean(design.selection.nodeId) })}\n`
|
|
1961
|
+
: `${JSON.stringify(receipt)}\n\n${format === "wireframe" ? localWireframe(design) : outline}\n`;
|
|
2034
1962
|
const output = await writeAtomic(
|
|
2035
1963
|
option(args, "--output") ?? artifactPath(format, "txt"),
|
|
2036
1964
|
text,
|
|
@@ -2105,88 +2033,6 @@ function designArgs(args) {
|
|
|
2105
2033
|
return [...result, "--json"];
|
|
2106
2034
|
}
|
|
2107
2035
|
|
|
2108
|
-
async function renderCommand(packagePath, args, evidenceOnly) {
|
|
2109
|
-
const workspace = await tokenWorkspace(packagePath, args);
|
|
2110
|
-
const { foundation, libraries, product } = workspace;
|
|
2111
|
-
if (!evidenceOnly && flag(args, "--design-system")) {
|
|
2112
|
-
const bundle = await renderDesignSystem(workspace, args);
|
|
2113
|
-
const inline = flag(args, "--base64");
|
|
2114
|
-
const output =
|
|
2115
|
-
option(args, "--output") ??
|
|
2116
|
-
(inline ? undefined : artifactPath("render", "png"));
|
|
2117
|
-
return deliverImage(
|
|
2118
|
-
bundle,
|
|
2119
|
-
workspace,
|
|
2120
|
-
output,
|
|
2121
|
-
(inline && output === undefined) || flag(args, "--base64"),
|
|
2122
|
-
);
|
|
2123
|
-
}
|
|
2124
|
-
const bundle = await renderEvidence(product, {
|
|
2125
|
-
foundation,
|
|
2126
|
-
libraries,
|
|
2127
|
-
scale: numberOption(args, "--scale"),
|
|
2128
|
-
selector: designSelector(args, "screenshot"),
|
|
2129
|
-
forExport: true,
|
|
2130
|
-
});
|
|
2131
|
-
const full = flag(args, "--full");
|
|
2132
|
-
const inline = flag(args, "--base64");
|
|
2133
|
-
const output =
|
|
2134
|
-
option(args, "--output") ??
|
|
2135
|
-
(inline ? undefined : artifactPath("render", "png"));
|
|
2136
|
-
const evidenceJson = evidenceOnly
|
|
2137
|
-
? canonicalJSON({
|
|
2138
|
-
...bundle.evidence,
|
|
2139
|
-
...workspaceRevisions(product, foundation, libraries),
|
|
2140
|
-
})
|
|
2141
|
-
: undefined;
|
|
2142
|
-
if (evidenceOnly && !inline) {
|
|
2143
|
-
const prefix = option(args, "--output");
|
|
2144
|
-
const imagePath = await writeAtomic(
|
|
2145
|
-
prefix === undefined ? output : `${prefix}.png`,
|
|
2146
|
-
bundle.render.bytes,
|
|
2147
|
-
);
|
|
2148
|
-
const evidencePath = await writeAtomic(
|
|
2149
|
-
prefix === undefined
|
|
2150
|
-
? artifactPath("evidence", "json")
|
|
2151
|
-
: `${prefix}.json`,
|
|
2152
|
-
evidenceJson,
|
|
2153
|
-
);
|
|
2154
|
-
return {
|
|
2155
|
-
...(full ? bundle.evidence : {}),
|
|
2156
|
-
diagnostics: nameDiagnostics(bundle.render.diagnostics, bundle.projection),
|
|
2157
|
-
renderHash: bundle.render.renderHash,
|
|
2158
|
-
selection: bundle.evidence.selector,
|
|
2159
|
-
packageId: product.manifest.packageId,
|
|
2160
|
-
...workspaceRevisions(product, foundation, libraries),
|
|
2161
|
-
evidencePath,
|
|
2162
|
-
imagePath,
|
|
2163
|
-
};
|
|
2164
|
-
}
|
|
2165
|
-
if (evidenceOnly && output !== undefined) {
|
|
2166
|
-
const imagePath = await writeAtomic(`${output}.png`, bundle.render.bytes);
|
|
2167
|
-
const evidencePath = await writeAtomic(`${output}.json`, evidenceJson);
|
|
2168
|
-
return {
|
|
2169
|
-
...bundle.evidence,
|
|
2170
|
-
...workspaceRevisions(product, foundation, libraries),
|
|
2171
|
-
imagePath,
|
|
2172
|
-
evidencePath,
|
|
2173
|
-
};
|
|
2174
|
-
}
|
|
2175
|
-
const image = await deliverImage(
|
|
2176
|
-
bundle,
|
|
2177
|
-
workspace,
|
|
2178
|
-
output,
|
|
2179
|
-
(output === undefined && inline) || flag(args, "--base64"),
|
|
2180
|
-
);
|
|
2181
|
-
return evidenceOnly
|
|
2182
|
-
? {
|
|
2183
|
-
...bundle.evidence,
|
|
2184
|
-
...workspaceRevisions(product, foundation, libraries),
|
|
2185
|
-
image,
|
|
2186
|
-
}
|
|
2187
|
-
: image;
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
2036
|
function tokenImpact(snapshot, tokenId, packageId) {
|
|
2191
2037
|
const locations = [];
|
|
2192
2038
|
const visit = (value, location) => {
|
|
@@ -3104,6 +2950,8 @@ async function lookCommand(packagePath, args) {
|
|
|
3104
2950
|
"too_many_view_targets",
|
|
3105
2951
|
"Look at one thing per call: --page, --component, --token, --tokens, --components or --pages",
|
|
3106
2952
|
);
|
|
2953
|
+
if (as === "spec" && page === undefined && component === undefined)
|
|
2954
|
+
throw new SmallPenError("missing_spec_target", "Choose one --page or --component for its specification; use --as text to navigate first");
|
|
3107
2955
|
const needs = (name, parents, text) => {
|
|
3108
2956
|
if (option(args, name) !== undefined && !parents.some((parent) => args.includes(parent)))
|
|
3109
2957
|
throw new SmallPenError("incomplete_view_target", `${name} ${text}`);
|
|
@@ -3881,14 +3729,6 @@ function storedNodesOf(workspace, selection = {}) {
|
|
|
3881
3729
|
return {};
|
|
3882
3730
|
}
|
|
3883
3731
|
|
|
3884
|
-
// Token id -> name across the workspace, for specs.
|
|
3885
|
-
function tokenNamer(workspace) {
|
|
3886
|
-
const names = new Map();
|
|
3887
|
-
for (const snapshot of [workspace.product, workspace.foundation, ...(workspace.libraries ?? [])].filter(Boolean))
|
|
3888
|
-
for (const token of snapshot.domain.tokens.values()) if (!names.has(token.id)) names.set(token.id, token.path);
|
|
3889
|
-
return (reference) => names.get(reference?.assetId ?? reference);
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
3732
|
// --token: one Token by name, or every Token under a name ("color").
|
|
3893
3733
|
function tokenNamed(path, wanted) {
|
|
3894
3734
|
if (wanted === undefined) return true;
|
|
@@ -4364,16 +4204,7 @@ async function main(args) {
|
|
|
4364
4204
|
if (command === "export" && flag(args, "--evidence")) {
|
|
4365
4205
|
if (option(args, "--format") !== "png")
|
|
4366
4206
|
throw new SmallPenError("missing_png_format", "--evidence requires --format png");
|
|
4367
|
-
|
|
4368
|
-
for (let i = 2; i < args.length; i++) {
|
|
4369
|
-
if (args[i] === "--evidence") continue;
|
|
4370
|
-
if (args[i] === "--format") {
|
|
4371
|
-
i++;
|
|
4372
|
-
continue;
|
|
4373
|
-
}
|
|
4374
|
-
translated.push(args[i]);
|
|
4375
|
-
}
|
|
4376
|
-
printJson(await renderCommand(packagePath, translated, true));
|
|
4207
|
+
printJson(await designCommand(packagePath, args));
|
|
4377
4208
|
return;
|
|
4378
4209
|
}
|
|
4379
4210
|
|
package/bin/workflow.mjs
CHANGED
|
@@ -19,11 +19,11 @@ export const CLI_RULES = Object.freeze({
|
|
|
19
19
|
"token theme list returns groups/options/defaults; token list --definitions [--group GROUP] reads stored option values; token list/show/explain resolve values and sources.",
|
|
20
20
|
flows:
|
|
21
21
|
"flow list shows starts and links by page and element name; flow link adds a link, flow unlink removes an element's links, flow start --page makes a start and --remove undoes it.",
|
|
22
|
-
view: "view looks at anything by name: nothing (whole package), --tokens/--token, --components/--component [--variant --element], --pages/--page [--platform --element]; --as text|wireframe|png|issues. Outlines collapse component internals and group repeated instances. changes --since REVISION lists, by name, what changed after a revision, including edits made in the App.",
|
|
22
|
+
view: "view looks at anything by name: nothing (whole package), --tokens/--token, --components/--component [--variant --element], --pages/--page [--platform --element]; --as text|spec|wireframe|png|issues. Text navigates; spec implements and traces sources; wireframe shows structure; PNG reviews appearance. Outlines collapse component internals and group repeated instances. changes --since REVISION lists, by name, what changed after a revision, including edits made in the App.",
|
|
23
23
|
validate:
|
|
24
24
|
"validate checks canonical validity and reports scoped text/layout/contrast issues separately. status valid means valid data, not complete visual approval. Always read issueCount, visualStatus and coverage.skipped. Pagination does not reduce the checked scope.",
|
|
25
25
|
export:
|
|
26
|
-
"export saves
|
|
26
|
+
"export saves a page/component/region specification, wireframe or PNG. --format png --evidence writes all three from one native solve. Compare layout.hash, target, themes and revision; read coverage and unresolved fields.",
|
|
27
27
|
},
|
|
28
28
|
modelling: {
|
|
29
29
|
tokenLayers:
|
|
@@ -44,9 +44,9 @@ export const CLI_RULES = Object.freeze({
|
|
|
44
44
|
output: {
|
|
45
45
|
budgetBytes: 8192,
|
|
46
46
|
strategy:
|
|
47
|
-
"
|
|
47
|
+
"Choose one page/component/variant/element. Read spec for implementation: authored fixed/fill/hug constraints, native bounds, field-level Token and component sources, overrides and node diagnostics. Read wireframe for spatial orientation; its character grid is approximate. Read PNG for high-fidelity appearance when the caller supports images. Text is a compact navigation outline, not an implementation specification. Specification, wireframe and PNG use native App layout; compare layout.hash, revision and selection, not revision alone. export --format png --evidence writes all three and JSON evidence from one solve. Unknown geometry stays ?/partial with a reason; seed rectangles are never actual coordinates. Coverage applies only to the selected size/themes/state, not untested combinations. --element expands details; --full expands the selected tree. Read resultFile.text.path for exact LF/space text, resultFile.path for complete JSON. Temporary paths are unique, expire after 30 minutes and must be consumed promptly; --output retains deliverables. Omitted stdout detail never means a check passed.",
|
|
48
48
|
imagePolicy:
|
|
49
|
-
"view defaults to a text outline; export defaults to a
|
|
49
|
+
"view defaults to a text outline for navigation; implementation starts with view --as spec. export defaults to a wireframe; --format spec writes a specification and --format png writes a high-fidelity image. A PNG cannot prove Token bindings, auto-sizing intent or interaction rules. A specification cannot prove visual quality. Models without image input must use spec/issues for structural checks; they may export PNG but must not claim visual inspection. Raster errors fail explicitly; no substitute painter fills in missing geometry.",
|
|
50
50
|
protocol:
|
|
51
51
|
"All stdout JSON is compact, including --full. Default directories expose only public identity, ownership, selection and count fields; read one target for its description or definition. Omit normal public visibility and false deprecation; actual private restrictions, deprecation and replacements remain actionable. Internal object fields never become public directory fields automatically. schema command project list or component list/search documents the output contract. token theme list omits unrelated stored App selection unless --full is explicitly requested. This omission policy must not discard meaningful design values such as 0, false, empty text or a node's hidden state.",
|
|
52
52
|
writes:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smallpen/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.13",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"bin"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@smallpen/core": "0.1.0-alpha.
|
|
19
|
-
"@smallpen/local-package": "0.1.0-alpha.
|
|
18
|
+
"@smallpen/core": "0.1.0-alpha.13",
|
|
19
|
+
"@smallpen/local-package": "0.1.0-alpha.13"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|