foss-design 0.5.0 → 0.6.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/dist/cli.js +539 -358
- package/dist/runtime/index.js +72 -0
- package/dist/viewer-lib/types/src/shared/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import
|
|
4
|
+
import path26 from "path";
|
|
5
5
|
import { parseArgs } from "util";
|
|
6
6
|
|
|
7
7
|
// src/core/paths.ts
|
|
@@ -134,12 +134,12 @@ function resolveFrom(anchor, request) {
|
|
|
134
134
|
|
|
135
135
|
// src/cli/commands/build.ts
|
|
136
136
|
import { spawnSync } from "child_process";
|
|
137
|
-
import
|
|
138
|
-
import
|
|
137
|
+
import fs12 from "fs";
|
|
138
|
+
import path12 from "path";
|
|
139
139
|
|
|
140
140
|
// src/build/static.ts
|
|
141
|
-
import
|
|
142
|
-
import
|
|
141
|
+
import fs11 from "fs";
|
|
142
|
+
import path11 from "path";
|
|
143
143
|
import { build } from "vite";
|
|
144
144
|
|
|
145
145
|
// src/core/png.ts
|
|
@@ -307,8 +307,8 @@ function ensureThumb(file) {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
// src/core/project.ts
|
|
310
|
-
import
|
|
311
|
-
import
|
|
310
|
+
import fs7 from "fs";
|
|
311
|
+
import path7 from "path";
|
|
312
312
|
|
|
313
313
|
// src/core/canvas.ts
|
|
314
314
|
import { createHash } from "crypto";
|
|
@@ -346,8 +346,8 @@ function routeError(pattern) {
|
|
|
346
346
|
}
|
|
347
347
|
return null;
|
|
348
348
|
}
|
|
349
|
-
function split(
|
|
350
|
-
return
|
|
349
|
+
function split(path27) {
|
|
350
|
+
return path27.split("/").filter(Boolean);
|
|
351
351
|
}
|
|
352
352
|
function compileRoute(pattern) {
|
|
353
353
|
return {
|
|
@@ -366,8 +366,8 @@ function decode2(part) {
|
|
|
366
366
|
return part;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
-
function scoreRoute(route2,
|
|
370
|
-
const parts = split(
|
|
369
|
+
function scoreRoute(route2, path27) {
|
|
370
|
+
const parts = split(path27.split(/[?#]/)[0] ?? "").map(decode2);
|
|
371
371
|
const walk2 = (s, p) => {
|
|
372
372
|
if (s === route2.segments.length) return p === parts.length ? 0 : null;
|
|
373
373
|
const segment = route2.segments[s];
|
|
@@ -385,11 +385,11 @@ function scoreRoute(route2, path26) {
|
|
|
385
385
|
};
|
|
386
386
|
return walk2(0, 0);
|
|
387
387
|
}
|
|
388
|
-
function matchRoute(entries,
|
|
388
|
+
function matchRoute(entries, path27) {
|
|
389
389
|
let best = null;
|
|
390
390
|
for (const entry of entries) {
|
|
391
391
|
for (const route2 of entry.routes) {
|
|
392
|
-
const score = scoreRoute(route2,
|
|
392
|
+
const score = scoreRoute(route2, path27);
|
|
393
393
|
if (score !== null && (!best || score > best.score)) best = { score, value: entry.value };
|
|
394
394
|
}
|
|
395
395
|
}
|
|
@@ -426,6 +426,8 @@ var DesignConfigSchema = z.strictObject({
|
|
|
426
426
|
alias: z.record(z.string(), z.string()).optional(),
|
|
427
427
|
/** Extra folders Tailwind scans for class names, relative to the project root. */
|
|
428
428
|
sources: z.array(z.string()).optional(),
|
|
429
|
+
/** A folder served at the root of every screen, like Vite's `public`, relative to the project root. */
|
|
430
|
+
public: z.string().min(1).optional(),
|
|
429
431
|
port: z.number().int().min(1).max(65535).optional()
|
|
430
432
|
});
|
|
431
433
|
var SystemConfigSchema = z.strictObject({
|
|
@@ -510,9 +512,9 @@ var CanvasSchema = z.strictObject({
|
|
|
510
512
|
pages: z.array(z.unknown()).min(1, "add at least one page")
|
|
511
513
|
});
|
|
512
514
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
513
|
-
function formatPath(base,
|
|
515
|
+
function formatPath(base, path27) {
|
|
514
516
|
let out = base;
|
|
515
|
-
for (const part of
|
|
517
|
+
for (const part of path27) out += typeof part === "number" ? `[${part}]` : out ? `.${String(part)}` : String(part);
|
|
516
518
|
return out;
|
|
517
519
|
}
|
|
518
520
|
function describeZodError(error, base = "") {
|
|
@@ -749,7 +751,7 @@ function loadCanvas(paths, canvasId, urls, snapshots) {
|
|
|
749
751
|
};
|
|
750
752
|
doc.pages.push(out);
|
|
751
753
|
});
|
|
752
|
-
checkNavigation(doc, screens, issues, fileRel, (file2) => relToRoot(paths, file2));
|
|
754
|
+
checkNavigation(doc, canvasCodeFiles(dir, screens), issues, fileRel, (file2) => relToRoot(paths, file2));
|
|
753
755
|
return { doc, screens };
|
|
754
756
|
}
|
|
755
757
|
var GO_IMPORT = /import\s*\{[^}]*\bgo\b[^}]*\}\s*from\s*['"]@design\/runtime['"]/;
|
|
@@ -768,7 +770,27 @@ function legacyGoLines(file) {
|
|
|
768
770
|
const calls = [...source.matchAll(GO_USE)].map((match) => lineOf(match.index)).filter((line) => line !== lineOf(imported.index));
|
|
769
771
|
return calls.length ? [...new Set(calls)] : [lineOf(imported.index)];
|
|
770
772
|
}
|
|
771
|
-
|
|
773
|
+
var CODE_FILE = /\.(?:[cm]?[jt]sx?|html?)$/i;
|
|
774
|
+
function canvasCodeFiles(dir, screens) {
|
|
775
|
+
const files = new Set(screens.map((screen) => screen.file));
|
|
776
|
+
const visit = (current) => {
|
|
777
|
+
let entries;
|
|
778
|
+
try {
|
|
779
|
+
entries = fs3.readdirSync(current, { withFileTypes: true });
|
|
780
|
+
} catch {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
785
|
+
const full = path3.join(current, entry.name);
|
|
786
|
+
if (entry.isDirectory()) visit(full);
|
|
787
|
+
else if (entry.isFile() && CODE_FILE.test(entry.name)) files.add(full);
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
visit(dir);
|
|
791
|
+
return [...files];
|
|
792
|
+
}
|
|
793
|
+
function checkNavigation(doc, codeFiles, issues, canvasFile, rel) {
|
|
772
794
|
const frames = doc.pages.flatMap(
|
|
773
795
|
(page) => page.sections.flatMap(
|
|
774
796
|
(section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url").map((item) => ({ page, item }))
|
|
@@ -787,7 +809,7 @@ function checkNavigation(doc, screens, issues, canvasFile, rel) {
|
|
|
787
809
|
});
|
|
788
810
|
}
|
|
789
811
|
}
|
|
790
|
-
for (const file of
|
|
812
|
+
for (const file of codeFiles) {
|
|
791
813
|
for (const line of legacyGoLines(file)) {
|
|
792
814
|
issues.push({ severity: "error", file: rel(file), at: `line ${line}`, message: GO_REMOVED });
|
|
793
815
|
}
|
|
@@ -809,13 +831,49 @@ function summarize(canvas) {
|
|
|
809
831
|
};
|
|
810
832
|
}
|
|
811
833
|
|
|
834
|
+
// src/core/public.ts
|
|
835
|
+
import fs4 from "fs";
|
|
836
|
+
import path4 from "path";
|
|
837
|
+
function publicFiles(dir) {
|
|
838
|
+
const files = [];
|
|
839
|
+
let root;
|
|
840
|
+
try {
|
|
841
|
+
root = fs4.realpathSync(dir);
|
|
842
|
+
} catch {
|
|
843
|
+
return files;
|
|
844
|
+
}
|
|
845
|
+
const visit = (current) => {
|
|
846
|
+
let entries;
|
|
847
|
+
try {
|
|
848
|
+
entries = fs4.readdirSync(current, { withFileTypes: true });
|
|
849
|
+
} catch {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
for (const entry of entries) {
|
|
853
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
854
|
+
const full = path4.join(current, entry.name);
|
|
855
|
+
if (entry.isDirectory()) visit(full);
|
|
856
|
+
else if (entry.isFile()) files.push(toPosix(path4.relative(dir, full)));
|
|
857
|
+
else if (entry.isSymbolicLink()) {
|
|
858
|
+
try {
|
|
859
|
+
const target = fs4.realpathSync(full);
|
|
860
|
+
if (isInside(root, target) && fs4.statSync(target).isFile()) files.push(toPosix(path4.relative(dir, full)));
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
visit(dir);
|
|
867
|
+
return files.sort();
|
|
868
|
+
}
|
|
869
|
+
|
|
812
870
|
// src/core/system.ts
|
|
813
|
-
import
|
|
814
|
-
import
|
|
871
|
+
import fs6 from "fs";
|
|
872
|
+
import path6 from "path";
|
|
815
873
|
|
|
816
874
|
// src/core/tokens.ts
|
|
817
|
-
import
|
|
818
|
-
import
|
|
875
|
+
import fs5 from "fs";
|
|
876
|
+
import path5 from "path";
|
|
819
877
|
import postcss from "postcss";
|
|
820
878
|
var NAMESPACES = [
|
|
821
879
|
["--color-", "color"],
|
|
@@ -974,12 +1032,12 @@ function readStylesheets(entry) {
|
|
|
974
1032
|
const seen = /* @__PURE__ */ new Set();
|
|
975
1033
|
const files = [];
|
|
976
1034
|
const walk2 = (file) => {
|
|
977
|
-
if (seen.has(file) || !
|
|
1035
|
+
if (seen.has(file) || !fs5.existsSync(file)) return;
|
|
978
1036
|
seen.add(file);
|
|
979
|
-
const css2 =
|
|
1037
|
+
const css2 = fs5.readFileSync(file, "utf8");
|
|
980
1038
|
const imports = [...css2.matchAll(/@import\s+(?:url\()?\s*["']([^"']+)["']/g)].map((m) => m[1]);
|
|
981
1039
|
for (const spec of imports) {
|
|
982
|
-
if (spec.startsWith("./") || spec.startsWith("../")) walk2(
|
|
1040
|
+
if (spec.startsWith("./") || spec.startsWith("../")) walk2(path5.resolve(path5.dirname(file), spec));
|
|
983
1041
|
}
|
|
984
1042
|
files.push({ file, css: css2 });
|
|
985
1043
|
};
|
|
@@ -1115,11 +1173,11 @@ var COMPONENT_EXT = [".tsx", ".jsx", ".ts", ".js", ".vue", ".svelte", ".html"];
|
|
|
1115
1173
|
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".svg", ".ico"]);
|
|
1116
1174
|
var FONT_EXT = /* @__PURE__ */ new Set([".woff", ".woff2", ".ttf", ".otf"]);
|
|
1117
1175
|
function listFiles(dir, deep = false) {
|
|
1118
|
-
if (!
|
|
1176
|
+
if (!fs6.existsSync(dir)) return [];
|
|
1119
1177
|
const out = [];
|
|
1120
|
-
for (const entry of
|
|
1178
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1121
1179
|
if (entry.name.startsWith(".")) continue;
|
|
1122
|
-
const full =
|
|
1180
|
+
const full = path6.join(dir, entry.name);
|
|
1123
1181
|
if (entry.isDirectory()) {
|
|
1124
1182
|
if (deep) out.push(...listFiles(full, true));
|
|
1125
1183
|
} else out.push(full);
|
|
@@ -1127,16 +1185,16 @@ function listFiles(dir, deep = false) {
|
|
|
1127
1185
|
return out.sort();
|
|
1128
1186
|
}
|
|
1129
1187
|
function guessSources(componentsDir, stem) {
|
|
1130
|
-
if (!
|
|
1188
|
+
if (!fs6.existsSync(componentsDir)) return [];
|
|
1131
1189
|
const wanted = slugify(stem);
|
|
1132
1190
|
for (const file of listFiles(componentsDir)) {
|
|
1133
|
-
const ext =
|
|
1134
|
-
if (COMPONENT_EXT.includes(ext) && slugify(
|
|
1191
|
+
const ext = path6.extname(file);
|
|
1192
|
+
if (COMPONENT_EXT.includes(ext) && slugify(path6.basename(file, ext)) === wanted) return [file];
|
|
1135
1193
|
}
|
|
1136
|
-
for (const entry of
|
|
1194
|
+
for (const entry of fs6.readdirSync(componentsDir, { withFileTypes: true })) {
|
|
1137
1195
|
if (entry.isDirectory() && slugify(entry.name) === wanted) {
|
|
1138
|
-
const files = listFiles(
|
|
1139
|
-
(f) => COMPONENT_EXT.includes(
|
|
1196
|
+
const files = listFiles(path6.join(componentsDir, entry.name)).filter(
|
|
1197
|
+
(f) => COMPONENT_EXT.includes(path6.extname(f))
|
|
1140
1198
|
);
|
|
1141
1199
|
if (files.length) return files;
|
|
1142
1200
|
}
|
|
@@ -1144,14 +1202,14 @@ function guessSources(componentsDir, stem) {
|
|
|
1144
1202
|
return [];
|
|
1145
1203
|
}
|
|
1146
1204
|
function loadSystem(paths, urls) {
|
|
1147
|
-
if (!
|
|
1205
|
+
if (!fs6.existsSync(paths.system)) return null;
|
|
1148
1206
|
const issues = [];
|
|
1149
|
-
const configFile =
|
|
1207
|
+
const configFile = path6.join(paths.system, "system.json");
|
|
1150
1208
|
const report = (severity, file, message, at) => issues.push({ severity, file: relToRoot(paths, file), message, at });
|
|
1151
1209
|
let config = null;
|
|
1152
|
-
if (
|
|
1210
|
+
if (fs6.existsSync(configFile)) {
|
|
1153
1211
|
try {
|
|
1154
|
-
const parsed = SystemConfigSchema.safeParse(JSON.parse(
|
|
1212
|
+
const parsed = SystemConfigSchema.safeParse(JSON.parse(fs6.readFileSync(configFile, "utf8")));
|
|
1155
1213
|
if (parsed.success) config = parsed.data;
|
|
1156
1214
|
else for (const issue of describeZodError(parsed.error)) report("error", configFile, issue.message, issue.at);
|
|
1157
1215
|
} catch (error) {
|
|
@@ -1160,36 +1218,36 @@ function loadSystem(paths, urls) {
|
|
|
1160
1218
|
} else {
|
|
1161
1219
|
report("warning", configFile, 'system.json is missing; run `design system init` or add { "name": "\u2026" }');
|
|
1162
1220
|
}
|
|
1163
|
-
const tokensFile =
|
|
1221
|
+
const tokensFile = path6.join(paths.system, "tokens.css");
|
|
1164
1222
|
let stylesheet = null;
|
|
1165
1223
|
if (config?.stylesheet) {
|
|
1166
|
-
stylesheet =
|
|
1167
|
-
if (!
|
|
1224
|
+
stylesheet = path6.resolve(paths.system, config.stylesheet);
|
|
1225
|
+
if (!fs6.existsSync(stylesheet)) {
|
|
1168
1226
|
report("error", configFile, `stylesheet not found: ${config.stylesheet}`, "stylesheet");
|
|
1169
1227
|
stylesheet = null;
|
|
1170
1228
|
}
|
|
1171
1229
|
}
|
|
1172
|
-
const tokenEntry = stylesheet ?? (
|
|
1230
|
+
const tokenEntry = stylesheet ?? (fs6.existsSync(tokensFile) ? tokensFile : null);
|
|
1173
1231
|
const tokens = tokenEntry ? parseTokens(readStylesheets(tokenEntry)) : [];
|
|
1174
1232
|
if (!tokenEntry) report("warning", tokensFile, "no tokens.css: screens get the default Tailwind theme");
|
|
1175
|
-
const specimensDir =
|
|
1176
|
-
const componentsDir =
|
|
1233
|
+
const specimensDir = path6.join(paths.system, "specimens");
|
|
1234
|
+
const componentsDir = path6.join(paths.system, "components");
|
|
1177
1235
|
const components = [];
|
|
1178
1236
|
const specimens = [];
|
|
1179
1237
|
const usedIds = /* @__PURE__ */ new Set();
|
|
1180
1238
|
for (const file of listFiles(specimensDir)) {
|
|
1181
|
-
const ext =
|
|
1239
|
+
const ext = path6.extname(file).toLowerCase();
|
|
1182
1240
|
if (!SPECIMEN_EXT.has(ext)) continue;
|
|
1183
|
-
const stem =
|
|
1241
|
+
const stem = path6.basename(file, ext);
|
|
1184
1242
|
let id2 = slugify(stem);
|
|
1185
1243
|
if (usedIds.has(id2)) id2 = `${id2}-${usedIds.size + 1}`;
|
|
1186
1244
|
usedIds.add(id2);
|
|
1187
|
-
const tags = parseDocTags(
|
|
1188
|
-
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) =>
|
|
1245
|
+
const tags = parseDocTags(fs6.readFileSync(file, "utf8"));
|
|
1246
|
+
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) => path6.resolve(path6.dirname(file), value));
|
|
1189
1247
|
for (const source of sources) {
|
|
1190
|
-
if (!
|
|
1248
|
+
if (!fs6.existsSync(source)) report("warning", file, `@source not found: ${path6.relative(paths.system, source)}`);
|
|
1191
1249
|
}
|
|
1192
|
-
const found = sources.filter((source) =>
|
|
1250
|
+
const found = sources.filter((source) => fs6.existsSync(source));
|
|
1193
1251
|
const title = tags.title?.[0] || titleize(stem);
|
|
1194
1252
|
const format = ext === ".html" ? "html" : "module";
|
|
1195
1253
|
components.push({
|
|
@@ -1215,10 +1273,10 @@ function loadSystem(paths, urls) {
|
|
|
1215
1273
|
});
|
|
1216
1274
|
}
|
|
1217
1275
|
const guidelines = [];
|
|
1218
|
-
for (const file of listFiles(
|
|
1219
|
-
if (
|
|
1220
|
-
const stem =
|
|
1221
|
-
const { data, body } = parseFrontmatter(
|
|
1276
|
+
for (const file of listFiles(path6.join(paths.system, "guidelines"))) {
|
|
1277
|
+
if (path6.extname(file).toLowerCase() !== ".md") continue;
|
|
1278
|
+
const stem = path6.basename(file, ".md");
|
|
1279
|
+
const { data, body } = parseFrontmatter(fs6.readFileSync(file, "utf8"));
|
|
1222
1280
|
const prefix = /^(\d+)[-_. ]+(.*)$/.exec(stem);
|
|
1223
1281
|
const order = data.order !== void 0 ? Number(data.order) : prefix ? Number(prefix[1]) : 1e3;
|
|
1224
1282
|
guidelines.push({
|
|
@@ -1230,14 +1288,14 @@ function loadSystem(paths, urls) {
|
|
|
1230
1288
|
}
|
|
1231
1289
|
guidelines.sort((a, b) => a.order - b.order || a.title.localeCompare(b.title));
|
|
1232
1290
|
const assets = [];
|
|
1233
|
-
const assetsDir =
|
|
1291
|
+
const assetsDir = path6.join(paths.system, "assets");
|
|
1234
1292
|
for (const file of listFiles(assetsDir, true)) {
|
|
1235
|
-
const ext =
|
|
1293
|
+
const ext = path6.extname(file).toLowerCase();
|
|
1236
1294
|
assets.push({
|
|
1237
|
-
name:
|
|
1238
|
-
path:
|
|
1295
|
+
name: path6.basename(file),
|
|
1296
|
+
path: path6.relative(paths.system, file).split(path6.sep).join("/"),
|
|
1239
1297
|
url: urls.file(relToDesign(paths, file)),
|
|
1240
|
-
size:
|
|
1298
|
+
size: fs6.statSync(file).size,
|
|
1241
1299
|
kind: IMAGE_EXT.has(ext) ? "image" : FONT_EXT.has(ext) ? "font" : "other"
|
|
1242
1300
|
});
|
|
1243
1301
|
}
|
|
@@ -1273,7 +1331,7 @@ function loadSystem(paths, urls) {
|
|
|
1273
1331
|
},
|
|
1274
1332
|
config,
|
|
1275
1333
|
stylesheet,
|
|
1276
|
-
tokensFile:
|
|
1334
|
+
tokensFile: fs6.existsSync(tokensFile) ? tokensFile : null,
|
|
1277
1335
|
specimens
|
|
1278
1336
|
};
|
|
1279
1337
|
}
|
|
@@ -1289,6 +1347,14 @@ function summarizeSystem(system) {
|
|
|
1289
1347
|
}
|
|
1290
1348
|
|
|
1291
1349
|
// src/core/project.ts
|
|
1350
|
+
function publicProblem(root, value) {
|
|
1351
|
+
const dir = path7.resolve(root, value);
|
|
1352
|
+
if (!fs7.existsSync(dir) || !fs7.statSync(dir).isDirectory()) return `no folder "${value}"`;
|
|
1353
|
+
const real = fs7.realpathSync(dir);
|
|
1354
|
+
const realRoot = fs7.realpathSync(root);
|
|
1355
|
+
if (real === realRoot || !isInside(realRoot, real)) return `"${value}" is not a folder inside the project`;
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1292
1358
|
var DesignProject = class {
|
|
1293
1359
|
constructor(paths, urls, snapshots = null) {
|
|
1294
1360
|
this.paths = paths;
|
|
@@ -1301,14 +1367,15 @@ var DesignProject = class {
|
|
|
1301
1367
|
configCache = null;
|
|
1302
1368
|
systemCache;
|
|
1303
1369
|
canvasCache = /* @__PURE__ */ new Map();
|
|
1370
|
+
publicCache = null;
|
|
1304
1371
|
config() {
|
|
1305
1372
|
if (this.configCache) return this.configCache;
|
|
1306
1373
|
const issues = [];
|
|
1307
1374
|
let config = {};
|
|
1308
1375
|
const file = this.paths.config;
|
|
1309
|
-
if (
|
|
1376
|
+
if (fs7.existsSync(file)) {
|
|
1310
1377
|
try {
|
|
1311
|
-
const parsed = DesignConfigSchema.safeParse(JSON.parse(
|
|
1378
|
+
const parsed = DesignConfigSchema.safeParse(JSON.parse(fs7.readFileSync(file, "utf8")));
|
|
1312
1379
|
if (parsed.success) config = parsed.data;
|
|
1313
1380
|
else
|
|
1314
1381
|
for (const issue of describeZodError(parsed.error))
|
|
@@ -1321,18 +1388,44 @@ var DesignProject = class {
|
|
|
1321
1388
|
});
|
|
1322
1389
|
}
|
|
1323
1390
|
}
|
|
1391
|
+
if (config.public !== void 0) {
|
|
1392
|
+
const problem = publicProblem(this.paths.root, config.public);
|
|
1393
|
+
if (problem) {
|
|
1394
|
+
issues.push({ severity: "error", file: relToRoot(this.paths, file), at: "public", message: problem });
|
|
1395
|
+
config = { ...config, public: void 0 };
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1324
1398
|
this.configCache = { config, issues };
|
|
1325
1399
|
return this.configCache;
|
|
1326
1400
|
}
|
|
1401
|
+
/** The public folder from design.json, when it is a folder inside the project. */
|
|
1402
|
+
publicDir() {
|
|
1403
|
+
const configured = this.config().config.public;
|
|
1404
|
+
return configured === void 0 ? null : path7.resolve(this.paths.root, configured);
|
|
1405
|
+
}
|
|
1406
|
+
/** The public folder's files, relative to it; `invalidatePublic` when they change. */
|
|
1407
|
+
publicFiles() {
|
|
1408
|
+
if (!this.publicCache) {
|
|
1409
|
+
const dir = this.publicDir();
|
|
1410
|
+
const list = dir ? publicFiles(dir) : [];
|
|
1411
|
+
this.publicCache = { list, set: new Set(list) };
|
|
1412
|
+
}
|
|
1413
|
+
return this.publicCache.list;
|
|
1414
|
+
}
|
|
1415
|
+
/** Whether `rel` is a file the public folder serves (dot-files and links out of it are not). */
|
|
1416
|
+
isPublicFile(rel) {
|
|
1417
|
+
this.publicFiles();
|
|
1418
|
+
return this.publicCache?.set.has(rel) ?? false;
|
|
1419
|
+
}
|
|
1327
1420
|
name() {
|
|
1328
1421
|
const configured = this.config().config.name;
|
|
1329
1422
|
if (configured) return configured;
|
|
1330
1423
|
try {
|
|
1331
|
-
const manifest = JSON.parse(
|
|
1424
|
+
const manifest = JSON.parse(fs7.readFileSync(path7.join(this.paths.root, "package.json"), "utf8"));
|
|
1332
1425
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
1333
1426
|
} catch {
|
|
1334
1427
|
}
|
|
1335
|
-
return
|
|
1428
|
+
return path7.basename(this.paths.root);
|
|
1336
1429
|
}
|
|
1337
1430
|
system() {
|
|
1338
1431
|
if (this.systemCache === void 0) this.systemCache = loadSystem(this.paths, this.urls);
|
|
@@ -1344,7 +1437,7 @@ var DesignProject = class {
|
|
|
1344
1437
|
canvas(id2) {
|
|
1345
1438
|
const cached = this.canvasCache.get(id2);
|
|
1346
1439
|
if (cached) return cached;
|
|
1347
|
-
if (!
|
|
1440
|
+
if (!fs7.existsSync(path7.join(this.paths.canvases, id2))) return null;
|
|
1348
1441
|
const canvas = loadCanvas(this.paths, id2, this.urls, this.snapshots);
|
|
1349
1442
|
this.canvasCache.set(id2, canvas);
|
|
1350
1443
|
return canvas;
|
|
@@ -1382,6 +1475,10 @@ var DesignProject = class {
|
|
|
1382
1475
|
}
|
|
1383
1476
|
invalidateConfig() {
|
|
1384
1477
|
this.configCache = null;
|
|
1478
|
+
this.publicCache = null;
|
|
1479
|
+
}
|
|
1480
|
+
invalidatePublic() {
|
|
1481
|
+
this.publicCache = null;
|
|
1385
1482
|
}
|
|
1386
1483
|
invalidateSystem() {
|
|
1387
1484
|
this.systemCache = void 0;
|
|
@@ -1411,21 +1508,21 @@ var STATIC_URLS = {
|
|
|
1411
1508
|
};
|
|
1412
1509
|
|
|
1413
1510
|
// src/server/entries.ts
|
|
1414
|
-
import
|
|
1415
|
-
import
|
|
1511
|
+
import fs8 from "fs";
|
|
1512
|
+
import path8 from "path";
|
|
1416
1513
|
var SYSTEM_CSS = "system.css";
|
|
1417
1514
|
var HTML_BOOT = "html-boot.js";
|
|
1418
1515
|
function writeIfChanged(file, content) {
|
|
1419
1516
|
try {
|
|
1420
|
-
if (
|
|
1517
|
+
if (fs8.readFileSync(file, "utf8") === content) return false;
|
|
1421
1518
|
} catch {
|
|
1422
1519
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1520
|
+
fs8.mkdirSync(path8.dirname(file), { recursive: true });
|
|
1521
|
+
fs8.writeFileSync(file, content);
|
|
1425
1522
|
return true;
|
|
1426
1523
|
}
|
|
1427
1524
|
function importPath(fromDir, file) {
|
|
1428
|
-
const rel = toPosix(
|
|
1525
|
+
const rel = toPosix(path8.relative(fromDir, file));
|
|
1429
1526
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1430
1527
|
}
|
|
1431
1528
|
var css = (file) => JSON.stringify(toPosix(file));
|
|
@@ -1438,10 +1535,10 @@ var Entries = class {
|
|
|
1438
1535
|
return this.project.paths.entries;
|
|
1439
1536
|
}
|
|
1440
1537
|
systemCssFile() {
|
|
1441
|
-
return
|
|
1538
|
+
return path8.join(this.dir, SYSTEM_CSS);
|
|
1442
1539
|
}
|
|
1443
1540
|
entryFile(source) {
|
|
1444
|
-
return
|
|
1541
|
+
return path8.join(this.dir, source.canvas, `${source.id}.js`);
|
|
1445
1542
|
}
|
|
1446
1543
|
/** Tailwind with the design tokens, or the stylesheet system.json names; scans every file in `.design`. */
|
|
1447
1544
|
systemCss() {
|
|
@@ -1458,14 +1555,14 @@ var Entries = class {
|
|
|
1458
1555
|
if (system?.tokensFile) lines.push(`@import ${css(system.tokensFile)};`);
|
|
1459
1556
|
}
|
|
1460
1557
|
lines.push(`@source ${css(paths.design)};`, `@source not ${css(paths.cache)};`);
|
|
1461
|
-
lines.push(`@source not ${css(
|
|
1462
|
-
for (const extra of config.sources ?? []) lines.push(`@source ${css(
|
|
1558
|
+
lines.push(`@source not ${css(path8.join(paths.design, "node_modules"))};`);
|
|
1559
|
+
for (const extra of config.sources ?? []) lines.push(`@source ${css(path8.resolve(paths.root, extra))};`);
|
|
1463
1560
|
return `${lines.join("\n")}
|
|
1464
1561
|
`;
|
|
1465
1562
|
}
|
|
1466
1563
|
entrySource(source) {
|
|
1467
1564
|
const file = this.entryFile(source);
|
|
1468
|
-
const dir =
|
|
1565
|
+
const dir = path8.dirname(file);
|
|
1469
1566
|
const lines = [];
|
|
1470
1567
|
if (source.system) lines.push(`import ${JSON.stringify(importPath(dir, this.systemCssFile()))}`);
|
|
1471
1568
|
if (source.builtin === "typography") {
|
|
@@ -1480,7 +1577,7 @@ var Entries = class {
|
|
|
1480
1577
|
`import * as screen from ${JSON.stringify(importPath(dir, source.file))}`,
|
|
1481
1578
|
"",
|
|
1482
1579
|
`const Screen = screen.default ?? Object.values(screen).find((value) => typeof value === 'function')`,
|
|
1483
|
-
"if (!Screen) throw new Error(" + JSON.stringify(`${
|
|
1580
|
+
"if (!Screen) throw new Error(" + JSON.stringify(`${path8.basename(source.file)} has no component to render: export one as default`) + ")",
|
|
1484
1581
|
"hmr(import.meta.hot)",
|
|
1485
1582
|
"mount(Screen)"
|
|
1486
1583
|
);
|
|
@@ -1493,14 +1590,14 @@ ${lines.join("\n")}
|
|
|
1493
1590
|
sync() {
|
|
1494
1591
|
writeIfChanged(this.systemCssFile(), this.systemCss());
|
|
1495
1592
|
writeIfChanged(
|
|
1496
|
-
|
|
1593
|
+
path8.join(this.dir, HTML_BOOT),
|
|
1497
1594
|
`// Generated by foss-design.
|
|
1498
1595
|
import { boot, hmr } from '@design/runtime'
|
|
1499
1596
|
boot()
|
|
1500
1597
|
hmr(import.meta.hot)
|
|
1501
1598
|
`
|
|
1502
1599
|
);
|
|
1503
|
-
const wanted = /* @__PURE__ */ new Set([this.systemCssFile(),
|
|
1600
|
+
const wanted = /* @__PURE__ */ new Set([this.systemCssFile(), path8.join(this.dir, HTML_BOOT)]);
|
|
1504
1601
|
for (const source of this.project.sources()) {
|
|
1505
1602
|
if (source.format !== "module") continue;
|
|
1506
1603
|
const file = this.entryFile(source);
|
|
@@ -1509,18 +1606,27 @@ hmr(import.meta.hot)
|
|
|
1509
1606
|
}
|
|
1510
1607
|
this.prune(this.dir, wanted);
|
|
1511
1608
|
}
|
|
1512
|
-
/**
|
|
1609
|
+
/**
|
|
1610
|
+
* What the dev server scans for dependencies before any frame loads: the generated entries,
|
|
1611
|
+
* the HTML screens and the boot they load. A dependency found only once frames load makes
|
|
1612
|
+
* Vite bundle again and fail the requests in flight (504 Outdated Optimize Dep).
|
|
1613
|
+
*/
|
|
1513
1614
|
files() {
|
|
1514
|
-
|
|
1615
|
+
const files = [path8.join(this.dir, HTML_BOOT)];
|
|
1616
|
+
for (const source of this.project.sources()) {
|
|
1617
|
+
if (source.format === "module") files.push(this.entryFile(source));
|
|
1618
|
+
else if (!source.builtin && fs8.existsSync(source.file)) files.push(source.file);
|
|
1619
|
+
}
|
|
1620
|
+
return files;
|
|
1515
1621
|
}
|
|
1516
1622
|
prune(dir, wanted) {
|
|
1517
|
-
if (!
|
|
1518
|
-
for (const entry of
|
|
1519
|
-
const full =
|
|
1623
|
+
if (!fs8.existsSync(dir)) return;
|
|
1624
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
1625
|
+
const full = path8.join(dir, entry.name);
|
|
1520
1626
|
if (entry.isDirectory()) {
|
|
1521
1627
|
this.prune(full, wanted);
|
|
1522
|
-
if (
|
|
1523
|
-
} else if (!wanted.has(full))
|
|
1628
|
+
if (fs8.readdirSync(full).length === 0) fs8.rmdirSync(full);
|
|
1629
|
+
} else if (!wanted.has(full)) fs8.rmSync(full);
|
|
1524
1630
|
}
|
|
1525
1631
|
}
|
|
1526
1632
|
};
|
|
@@ -1536,7 +1642,8 @@ function headTags(head) {
|
|
|
1536
1642
|
props: source.props,
|
|
1537
1643
|
theme: source.theme,
|
|
1538
1644
|
snapshots: head.snapshots,
|
|
1539
|
-
autoHeight: head.autoHeight
|
|
1645
|
+
autoHeight: head.autoHeight,
|
|
1646
|
+
public: head.public
|
|
1540
1647
|
};
|
|
1541
1648
|
const fallback = source.theme ?? head.theme;
|
|
1542
1649
|
const theme2 = `(function(){var q=new URLSearchParams(location.search).get('theme');var t=${JSON.stringify(source.theme ?? null)}||(q==='dark'||q==='light'?q:${JSON.stringify(fallback)});var r=document.documentElement;r.dataset.theme=t;r.classList.toggle('dark',t==='dark');r.style.colorScheme=t})()`;
|
|
@@ -1587,68 +1694,68 @@ ${html}
|
|
|
1587
1694
|
}
|
|
1588
1695
|
|
|
1589
1696
|
// src/server/links.ts
|
|
1590
|
-
import
|
|
1591
|
-
import
|
|
1697
|
+
import fs9 from "fs";
|
|
1698
|
+
import path9 from "path";
|
|
1592
1699
|
var SHIPPED_PACKAGES = ["react", "react-dom", "motion", "lucide-react", "clsx", "tailwind-merge"];
|
|
1593
1700
|
function findPackageDir(fromDir, name, skip) {
|
|
1594
|
-
let dir =
|
|
1701
|
+
let dir = path9.resolve(fromDir);
|
|
1595
1702
|
for (; ; ) {
|
|
1596
|
-
const modules =
|
|
1597
|
-
const candidate =
|
|
1598
|
-
if (modules !== skip &&
|
|
1599
|
-
const parent =
|
|
1703
|
+
const modules = path9.join(dir, "node_modules");
|
|
1704
|
+
const candidate = path9.join(modules, name);
|
|
1705
|
+
if (modules !== skip && fs9.existsSync(path9.join(candidate, "package.json"))) return candidate;
|
|
1706
|
+
const parent = path9.dirname(dir);
|
|
1600
1707
|
if (parent === dir) return null;
|
|
1601
1708
|
dir = parent;
|
|
1602
1709
|
}
|
|
1603
1710
|
}
|
|
1604
1711
|
function isLink(file) {
|
|
1605
1712
|
try {
|
|
1606
|
-
return
|
|
1713
|
+
return fs9.lstatSync(file).isSymbolicLink();
|
|
1607
1714
|
} catch {
|
|
1608
1715
|
return false;
|
|
1609
1716
|
}
|
|
1610
1717
|
}
|
|
1611
1718
|
function linkShippedPackages(paths) {
|
|
1612
|
-
const modules =
|
|
1719
|
+
const modules = path9.join(paths.design, "node_modules");
|
|
1613
1720
|
const linked = [];
|
|
1614
1721
|
const allow = /* @__PURE__ */ new Set();
|
|
1615
1722
|
for (const name of SHIPPED_PACKAGES) {
|
|
1616
|
-
const link =
|
|
1617
|
-
const placedByUser =
|
|
1723
|
+
const link = path9.join(modules, name);
|
|
1724
|
+
const placedByUser = fs9.existsSync(link) && !isLink(link);
|
|
1618
1725
|
const own = placedByUser ? link : findPackageDir(paths.design, name, modules);
|
|
1619
1726
|
if (own) {
|
|
1620
|
-
allow.add(
|
|
1621
|
-
if (isLink(link))
|
|
1727
|
+
allow.add(path9.dirname(fs9.realpathSync(own)));
|
|
1728
|
+
if (isLink(link)) fs9.unlinkSync(link);
|
|
1622
1729
|
continue;
|
|
1623
1730
|
}
|
|
1624
1731
|
const shipped = findPackageDir(PKG_ROOT, name);
|
|
1625
1732
|
if (!shipped) continue;
|
|
1626
|
-
const target2 =
|
|
1627
|
-
allow.add(
|
|
1733
|
+
const target2 = fs9.realpathSync(shipped);
|
|
1734
|
+
allow.add(path9.dirname(target2));
|
|
1628
1735
|
let current2 = null;
|
|
1629
1736
|
try {
|
|
1630
|
-
current2 =
|
|
1737
|
+
current2 = fs9.realpathSync(link);
|
|
1631
1738
|
} catch {
|
|
1632
1739
|
}
|
|
1633
1740
|
if (current2 !== target2) {
|
|
1634
|
-
if (isLink(link))
|
|
1635
|
-
|
|
1636
|
-
|
|
1741
|
+
if (isLink(link)) fs9.unlinkSync(link);
|
|
1742
|
+
fs9.mkdirSync(path9.dirname(link), { recursive: true });
|
|
1743
|
+
fs9.symlinkSync(target2, link, process.platform === "win32" ? "junction" : "dir");
|
|
1637
1744
|
}
|
|
1638
1745
|
linked.push(name);
|
|
1639
1746
|
}
|
|
1640
|
-
const self =
|
|
1641
|
-
const target =
|
|
1747
|
+
const self = path9.join(modules, "foss-design");
|
|
1748
|
+
const target = fs9.realpathSync(PKG_ROOT);
|
|
1642
1749
|
let current = null;
|
|
1643
1750
|
try {
|
|
1644
|
-
current =
|
|
1751
|
+
current = fs9.realpathSync(self);
|
|
1645
1752
|
} catch {
|
|
1646
1753
|
}
|
|
1647
1754
|
if (current !== target) {
|
|
1648
|
-
if (isLink(self))
|
|
1649
|
-
if (!
|
|
1650
|
-
|
|
1651
|
-
|
|
1755
|
+
if (isLink(self)) fs9.unlinkSync(self);
|
|
1756
|
+
if (!fs9.existsSync(self)) {
|
|
1757
|
+
fs9.mkdirSync(modules, { recursive: true });
|
|
1758
|
+
fs9.symlinkSync(target, self, process.platform === "win32" ? "junction" : "dir");
|
|
1652
1759
|
}
|
|
1653
1760
|
}
|
|
1654
1761
|
allow.add(target);
|
|
@@ -1656,7 +1763,9 @@ function linkShippedPackages(paths) {
|
|
|
1656
1763
|
}
|
|
1657
1764
|
|
|
1658
1765
|
// src/server/vite.ts
|
|
1659
|
-
import
|
|
1766
|
+
import { createHash as createHash2 } from "crypto";
|
|
1767
|
+
import fs10 from "fs";
|
|
1768
|
+
import path10 from "path";
|
|
1660
1769
|
import tailwindcss from "@tailwindcss/vite";
|
|
1661
1770
|
import react from "@vitejs/plugin-react";
|
|
1662
1771
|
var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1668,21 +1777,40 @@ function aliases(project) {
|
|
|
1668
1777
|
{ find: /^@system\//, replacement: `${toPosix(paths.system)}/` }
|
|
1669
1778
|
];
|
|
1670
1779
|
for (const [key, target] of Object.entries(project.config().config.alias ?? {})) {
|
|
1671
|
-
const replacement = toPosix(
|
|
1780
|
+
const replacement = toPosix(path10.resolve(paths.root, target));
|
|
1672
1781
|
list.push(
|
|
1673
1782
|
key.endsWith("/") ? { find: new RegExp(`^${escapeRegExp(key)}`), replacement: `${replacement.replace(/\/$/, "")}/` } : { find: new RegExp(`^${escapeRegExp(key)}(?=/|$)`), replacement }
|
|
1674
1783
|
);
|
|
1675
1784
|
}
|
|
1676
1785
|
return list;
|
|
1677
1786
|
}
|
|
1787
|
+
function dropStaleDepCache(paths) {
|
|
1788
|
+
const dir = path10.join(paths.cache, "vite");
|
|
1789
|
+
const stamp = path10.join(paths.cache, "vite-runtime");
|
|
1790
|
+
let runtime = "";
|
|
1791
|
+
try {
|
|
1792
|
+
runtime = createHash2("sha256").update(fs10.readFileSync(PKG.runtime)).digest("hex");
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
let seen = "";
|
|
1796
|
+
try {
|
|
1797
|
+
seen = fs10.readFileSync(stamp, "utf8");
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
if (seen === runtime) return;
|
|
1801
|
+
fs10.rmSync(dir, { recursive: true, force: true });
|
|
1802
|
+
fs10.mkdirSync(paths.cache, { recursive: true });
|
|
1803
|
+
fs10.writeFileSync(stamp, runtime);
|
|
1804
|
+
}
|
|
1678
1805
|
function baseConfig(project) {
|
|
1679
1806
|
const { paths } = project;
|
|
1680
1807
|
return {
|
|
1681
1808
|
configFile: false,
|
|
1682
1809
|
envDir: false,
|
|
1683
1810
|
root: paths.design,
|
|
1684
|
-
cacheDir:
|
|
1685
|
-
|
|
1811
|
+
cacheDir: path10.join(paths.cache, "vite"),
|
|
1812
|
+
// design.json `public`: served under the frames' base, so HTML and CSS paths such as `/logo.png` resolve.
|
|
1813
|
+
publicDir: project.publicDir() ?? false,
|
|
1686
1814
|
logLevel: "warn",
|
|
1687
1815
|
clearScreen: false,
|
|
1688
1816
|
resolve: {
|
|
@@ -1700,7 +1828,7 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts",
|
|
|
1700
1828
|
var BUNDLED_REF = /<(script|link|img|source|video|audio|image|use|input)\b[^>]*>/gi;
|
|
1701
1829
|
var encodePath2 = (rel) => rel.split("/").map(encodeURIComponent).join("/");
|
|
1702
1830
|
function relUrl(fromDir, file) {
|
|
1703
|
-
const rel = toPosix(
|
|
1831
|
+
const rel = toPosix(path11.relative(fromDir, file));
|
|
1704
1832
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1705
1833
|
}
|
|
1706
1834
|
function staticSnapshots(paths) {
|
|
@@ -1710,7 +1838,7 @@ function staticSnapshots(paths) {
|
|
|
1710
1838
|
let data = metaCache.get(canvas);
|
|
1711
1839
|
if (!data) {
|
|
1712
1840
|
try {
|
|
1713
|
-
data = JSON.parse(
|
|
1841
|
+
data = JSON.parse(fs11.readFileSync(path11.join(paths.snapshots, canvas, "meta.json"), "utf8"));
|
|
1714
1842
|
} catch {
|
|
1715
1843
|
}
|
|
1716
1844
|
data ??= {};
|
|
@@ -1724,8 +1852,8 @@ function staticSnapshots(paths) {
|
|
|
1724
1852
|
const urls = {};
|
|
1725
1853
|
const thumbs = {};
|
|
1726
1854
|
for (const theme2 of THEMES) {
|
|
1727
|
-
const file =
|
|
1728
|
-
if (!entry[theme2] || !
|
|
1855
|
+
const file = path11.join(paths.snapshots, canvas, `${id2}.${theme2}.png`);
|
|
1856
|
+
if (!entry[theme2] || !fs11.existsSync(file)) continue;
|
|
1729
1857
|
const rel = `_snap/${canvas}/${id2}.${theme2}.png`;
|
|
1730
1858
|
copies.set(file, rel);
|
|
1731
1859
|
urls[theme2] = `${encodePath2(rel)}?v=${entry[theme2]}`;
|
|
@@ -1749,29 +1877,29 @@ function rewriteRefs(html, originalDir, stagedDir) {
|
|
|
1749
1877
|
(whole, attr, quote, value) => {
|
|
1750
1878
|
if (!value || /^(?:[a-z][a-z0-9+.-]*:|\/|#|\{)/i.test(value)) return whole;
|
|
1751
1879
|
const [pathname = "", suffix = ""] = /^([^?#]*)(.*)$/.exec(value).slice(1);
|
|
1752
|
-
const target =
|
|
1753
|
-
if (!
|
|
1880
|
+
const target = path11.resolve(originalDir, decodeURIComponent(pathname));
|
|
1881
|
+
if (!fs11.existsSync(target)) return whole;
|
|
1754
1882
|
return `${attr}${quote}${relUrl(stagedDir, target)}${suffix}${quote}`;
|
|
1755
1883
|
}
|
|
1756
1884
|
);
|
|
1757
1885
|
});
|
|
1758
1886
|
}
|
|
1759
1887
|
function copyTree(from, to, filter) {
|
|
1760
|
-
if (!
|
|
1761
|
-
for (const entry of
|
|
1888
|
+
if (!fs11.existsSync(from)) return;
|
|
1889
|
+
for (const entry of fs11.readdirSync(from, { withFileTypes: true })) {
|
|
1762
1890
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1763
|
-
const source =
|
|
1764
|
-
const target =
|
|
1891
|
+
const source = path11.join(from, entry.name);
|
|
1892
|
+
const target = path11.join(to, entry.name);
|
|
1765
1893
|
if (entry.isDirectory()) copyTree(source, target, filter);
|
|
1766
|
-
else if (filter(source) && !
|
|
1767
|
-
|
|
1768
|
-
|
|
1894
|
+
else if (filter(source) && !fs11.existsSync(target)) {
|
|
1895
|
+
fs11.mkdirSync(path11.dirname(target), { recursive: true });
|
|
1896
|
+
fs11.copyFileSync(source, target);
|
|
1769
1897
|
}
|
|
1770
1898
|
}
|
|
1771
1899
|
}
|
|
1772
1900
|
function writeJson(file, value) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1901
|
+
fs11.mkdirSync(path11.dirname(file), { recursive: true });
|
|
1902
|
+
fs11.writeFileSync(file, JSON.stringify(value));
|
|
1775
1903
|
}
|
|
1776
1904
|
async function buildSite(paths, out, options = {}) {
|
|
1777
1905
|
const snapshots = staticSnapshots(paths);
|
|
@@ -1787,42 +1915,46 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1787
1915
|
const entries = new Entries(project);
|
|
1788
1916
|
entries.sync();
|
|
1789
1917
|
linkShippedPackages(paths);
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1918
|
+
const publicDir = project.publicDir();
|
|
1919
|
+
const publicFiles2 = project.publicFiles();
|
|
1920
|
+
const staging = path11.join(paths.cache, "build", "src");
|
|
1921
|
+
fs11.rmSync(staging, { recursive: true, force: true });
|
|
1792
1922
|
const fonts = loadedSystem?.doc.fonts ?? [];
|
|
1793
1923
|
const sources = [...system?.specimens ?? [], ...canvases.flatMap((canvas) => canvas.screens)];
|
|
1794
1924
|
const inputs = [];
|
|
1795
1925
|
const htmlScreens = [];
|
|
1796
1926
|
for (const source of sources) {
|
|
1797
|
-
if (!source.builtin && !
|
|
1798
|
-
const siteDir =
|
|
1799
|
-
const dir =
|
|
1800
|
-
const file =
|
|
1801
|
-
|
|
1927
|
+
if (!source.builtin && !fs11.existsSync(source.file)) continue;
|
|
1928
|
+
const siteDir = path11.join("_s", source.canvas, source.id);
|
|
1929
|
+
const dir = path11.join(staging, siteDir);
|
|
1930
|
+
const file = path11.join(dir, "index.html");
|
|
1931
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
1802
1932
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
1803
1933
|
const head = {
|
|
1804
1934
|
source,
|
|
1805
1935
|
theme: canvasTheme ?? "light",
|
|
1806
1936
|
fonts,
|
|
1807
1937
|
snapshots: false,
|
|
1808
|
-
autoHeight: source.autoHeight ?? false
|
|
1938
|
+
autoHeight: source.autoHeight ?? false,
|
|
1939
|
+
// The public folder lands at the site root, which frames reach through `../`.
|
|
1940
|
+
public: publicFiles2.length ? { base: `${toPosix(path11.relative(dir, staging))}/`, files: publicFiles2 } : void 0
|
|
1809
1941
|
};
|
|
1810
1942
|
if (source.format === "module") {
|
|
1811
|
-
|
|
1943
|
+
fs11.writeFileSync(file, moduleShell(head, relUrl(dir, entries.entryFile(source))));
|
|
1812
1944
|
} else {
|
|
1813
|
-
const raw = rewriteRefs(
|
|
1945
|
+
const raw = rewriteRefs(fs11.readFileSync(source.file, "utf8"), path11.dirname(source.file), dir);
|
|
1814
1946
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
1815
1947
|
const html = injectIntoHtml(raw, {
|
|
1816
1948
|
...head,
|
|
1817
1949
|
systemCss: withSystem ? relUrl(dir, entries.systemCssFile()) : void 0,
|
|
1818
|
-
boot: relUrl(dir,
|
|
1950
|
+
boot: relUrl(dir, path11.join(entries.dir, HTML_BOOT))
|
|
1819
1951
|
});
|
|
1820
|
-
|
|
1952
|
+
fs11.writeFileSync(file, html);
|
|
1821
1953
|
htmlScreens.push({ source, dir: siteDir });
|
|
1822
1954
|
}
|
|
1823
1955
|
inputs.push(file);
|
|
1824
1956
|
}
|
|
1825
|
-
|
|
1957
|
+
fs11.mkdirSync(out, { recursive: true });
|
|
1826
1958
|
if (inputs.length) {
|
|
1827
1959
|
const base = baseConfig(project);
|
|
1828
1960
|
await build({
|
|
@@ -1837,18 +1969,20 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1837
1969
|
target: "es2022",
|
|
1838
1970
|
reportCompressedSize: false,
|
|
1839
1971
|
chunkSizeWarningLimit: 8e3,
|
|
1972
|
+
// Copied below, without dot-files and links out of the folder, and after the site's own files.
|
|
1973
|
+
copyPublicDir: false,
|
|
1840
1974
|
rolldownOptions: { input: inputs }
|
|
1841
1975
|
}
|
|
1842
1976
|
});
|
|
1843
1977
|
} else {
|
|
1844
|
-
|
|
1845
|
-
|
|
1978
|
+
fs11.rmSync(out, { recursive: true, force: true });
|
|
1979
|
+
fs11.mkdirSync(out, { recursive: true });
|
|
1846
1980
|
}
|
|
1847
1981
|
for (const { source, dir } of htmlScreens) {
|
|
1848
1982
|
copyTree(
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
(file) => !SOURCE_EXT.has(
|
|
1983
|
+
path11.dirname(source.file),
|
|
1984
|
+
path11.join(out, dir),
|
|
1985
|
+
(file) => !SOURCE_EXT.has(path11.extname(file).toLowerCase())
|
|
1852
1986
|
);
|
|
1853
1987
|
}
|
|
1854
1988
|
const files = /* @__PURE__ */ new Set();
|
|
@@ -1860,51 +1994,59 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1860
1994
|
for (const asset of system?.doc.assets ?? []) files.add(asset.url);
|
|
1861
1995
|
for (const url of files) {
|
|
1862
1996
|
const rel = url.slice("_f/".length).split("/").map(decodeURIComponent).join("/");
|
|
1863
|
-
const from =
|
|
1864
|
-
if (!
|
|
1865
|
-
const to =
|
|
1866
|
-
|
|
1867
|
-
|
|
1997
|
+
const from = path11.join(paths.design, rel);
|
|
1998
|
+
if (!fs11.existsSync(from)) continue;
|
|
1999
|
+
const to = path11.join(out, "_f", rel);
|
|
2000
|
+
fs11.mkdirSync(path11.dirname(to), { recursive: true });
|
|
2001
|
+
fs11.copyFileSync(from, to);
|
|
1868
2002
|
}
|
|
1869
2003
|
const info = project.info(true);
|
|
1870
2004
|
const snapshotDirs = canvasIds.map((id2) => `_snap/${id2}/`);
|
|
1871
2005
|
for (const [from, rel] of snapshots.copies) {
|
|
1872
2006
|
if (!snapshotDirs.some((dir) => rel.startsWith(dir))) continue;
|
|
1873
|
-
const to =
|
|
1874
|
-
|
|
1875
|
-
|
|
2007
|
+
const to = path11.join(out, rel);
|
|
2008
|
+
fs11.mkdirSync(path11.dirname(to), { recursive: true });
|
|
2009
|
+
fs11.copyFileSync(from, to);
|
|
1876
2010
|
}
|
|
1877
2011
|
if (!system) info.system = null;
|
|
1878
2012
|
info.canvases = info.canvases.filter((canvas) => canvasIds.includes(canvas.id));
|
|
1879
|
-
writeJson(
|
|
1880
|
-
for (const canvas of canvases) writeJson(
|
|
2013
|
+
writeJson(path11.join(out, "api", "project.json"), info);
|
|
2014
|
+
for (const canvas of canvases) writeJson(path11.join(out, "api", "canvas", `${canvas.doc.id}.json`), canvas.doc);
|
|
1881
2015
|
if (system) {
|
|
1882
|
-
writeJson(
|
|
2016
|
+
writeJson(path11.join(out, "api", "system.json"), system.doc);
|
|
1883
2017
|
const texts = {};
|
|
1884
2018
|
for (const component of system.doc.components) {
|
|
1885
2019
|
for (const rel of [component.specimen, ...component.sources]) {
|
|
1886
|
-
const file =
|
|
1887
|
-
if (!(rel in texts) &&
|
|
2020
|
+
const file = path11.join(paths.root, rel);
|
|
2021
|
+
if (!(rel in texts) && fs11.existsSync(file)) texts[rel] = fs11.readFileSync(file, "utf8");
|
|
1888
2022
|
}
|
|
1889
2023
|
}
|
|
1890
|
-
writeJson(
|
|
2024
|
+
writeJson(path11.join(out, "api", "sources.json"), texts);
|
|
1891
2025
|
}
|
|
1892
|
-
|
|
2026
|
+
fs11.rmSync(staging, { recursive: true, force: true });
|
|
1893
2027
|
const errors = [
|
|
1894
2028
|
...only ? [] : project.config().issues,
|
|
1895
2029
|
...system?.doc.issues ?? [],
|
|
1896
2030
|
...canvases.flatMap((canvas) => canvas.doc.issues)
|
|
1897
2031
|
].filter((issue) => issue.severity === "error").length;
|
|
1898
2032
|
const { absoluteAssets } = options.includeViewer === false ? { absoluteAssets: false } : copyViewer(PKG.viewer, out);
|
|
2033
|
+
if (publicDir) {
|
|
2034
|
+
for (const rel of publicFiles2) {
|
|
2035
|
+
const to = path11.join(out, rel);
|
|
2036
|
+
if (fs11.existsSync(to)) continue;
|
|
2037
|
+
fs11.mkdirSync(path11.dirname(to), { recursive: true });
|
|
2038
|
+
fs11.copyFileSync(path11.join(publicDir, rel), to);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1899
2041
|
return { out, canvases: canvasIds, frames: inputs.length, errors, absoluteAssets };
|
|
1900
2042
|
}
|
|
1901
2043
|
function copyViewer(viewerDir, out) {
|
|
1902
2044
|
copyTree(viewerDir, out, () => true);
|
|
1903
|
-
const index =
|
|
1904
|
-
const html =
|
|
2045
|
+
const index = path11.join(out, "index.html");
|
|
2046
|
+
const html = fs11.readFileSync(index, "utf8");
|
|
1905
2047
|
const flag2 = "<script>window.__DESIGN_STATIC__=true</script>";
|
|
1906
2048
|
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
1907
|
-
|
|
2049
|
+
fs11.writeFileSync(
|
|
1908
2050
|
index,
|
|
1909
2051
|
head ? html.slice(0, head.index + head[0].length) + flag2 + html.slice(head.index + head[0].length) : flag2 + html
|
|
1910
2052
|
);
|
|
@@ -1986,8 +2128,8 @@ function progress() {
|
|
|
1986
2128
|
// src/cli/commands/build.ts
|
|
1987
2129
|
function pack(out) {
|
|
1988
2130
|
const archive = `${out.replace(/[\\/]+$/, "")}.tar.gz`;
|
|
1989
|
-
|
|
1990
|
-
const entries =
|
|
2131
|
+
fs12.rmSync(archive, { force: true });
|
|
2132
|
+
const entries = fs12.readdirSync(out);
|
|
1991
2133
|
const result = spawnSync("tar", ["-czf", archive, "-C", out, ...entries], {
|
|
1992
2134
|
stdio: ["ignore", "ignore", "pipe"],
|
|
1993
2135
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
@@ -1997,10 +2139,10 @@ function pack(out) {
|
|
|
1997
2139
|
return archive;
|
|
1998
2140
|
}
|
|
1999
2141
|
async function runBuild(paths, canvases, options) {
|
|
2000
|
-
if (!
|
|
2142
|
+
if (!fs12.existsSync(path12.join(PKG.viewer, "index.html"))) {
|
|
2001
2143
|
throw new CliError("The viewer is missing from this foss-design install (dist/viewer). Rebuild or reinstall it.");
|
|
2002
2144
|
}
|
|
2003
|
-
const out =
|
|
2145
|
+
const out = path12.resolve(options.out ?? path12.join(paths.cache, "site"));
|
|
2004
2146
|
if (out === paths.root || out === paths.design) throw new CliError(`Refusing to build into ${out}`);
|
|
2005
2147
|
print(dim(`Building ${canvases.length ? canvases.join(", ") : "every canvas"} and the design system\u2026`));
|
|
2006
2148
|
let result;
|
|
@@ -2024,9 +2166,9 @@ async function runBuild(paths, canvases, options) {
|
|
|
2024
2166
|
import os from "os";
|
|
2025
2167
|
|
|
2026
2168
|
// src/cloud/units.ts
|
|
2027
|
-
import { createHash as
|
|
2028
|
-
import
|
|
2029
|
-
import
|
|
2169
|
+
import { createHash as createHash3 } from "crypto";
|
|
2170
|
+
import fs13 from "fs";
|
|
2171
|
+
import path13 from "path";
|
|
2030
2172
|
var SYSTEM_UNIT = "system";
|
|
2031
2173
|
var CANVAS_PREFIX = "canvas/";
|
|
2032
2174
|
var LOCAL_ONLY = /* @__PURE__ */ new Set(["cloud.json", "node_modules"]);
|
|
@@ -2054,7 +2196,7 @@ function caseClashes(keys) {
|
|
|
2054
2196
|
}
|
|
2055
2197
|
function unitRoot(paths, key) {
|
|
2056
2198
|
const id2 = unitCanvasId(key);
|
|
2057
|
-
return id2 === null ? paths.design :
|
|
2199
|
+
return id2 === null ? paths.design : path13.join(paths.canvases, id2);
|
|
2058
2200
|
}
|
|
2059
2201
|
function unitRelative(key, rel) {
|
|
2060
2202
|
const id2 = unitCanvasId(key);
|
|
@@ -2069,10 +2211,10 @@ function isUnitPath(key, rel) {
|
|
|
2069
2211
|
return id2 !== null && segments[0] === "canvas" && segments[1] === id2 && segments.length > 2;
|
|
2070
2212
|
}
|
|
2071
2213
|
function hashBuffer(data) {
|
|
2072
|
-
return
|
|
2214
|
+
return createHash3("sha256").update(data).digest("hex");
|
|
2073
2215
|
}
|
|
2074
2216
|
function hashFile(file) {
|
|
2075
|
-
const data =
|
|
2217
|
+
const data = fs13.readFileSync(file);
|
|
2076
2218
|
return { hash: hashBuffer(data), size: data.length };
|
|
2077
2219
|
}
|
|
2078
2220
|
var warnedLinks = /* @__PURE__ */ new Set();
|
|
@@ -2085,34 +2227,34 @@ function warnLink(rel) {
|
|
|
2085
2227
|
function walk(dir, base, out) {
|
|
2086
2228
|
let entries;
|
|
2087
2229
|
try {
|
|
2088
|
-
entries =
|
|
2230
|
+
entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
2089
2231
|
} catch {
|
|
2090
2232
|
return;
|
|
2091
2233
|
}
|
|
2092
2234
|
for (const entry of entries) {
|
|
2093
2235
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2094
|
-
const full =
|
|
2236
|
+
const full = path13.join(dir, entry.name);
|
|
2095
2237
|
let isFile = entry.isFile();
|
|
2096
2238
|
let isDir = entry.isDirectory();
|
|
2097
2239
|
if (entry.isSymbolicLink()) {
|
|
2098
2240
|
isDir = false;
|
|
2099
2241
|
isFile = false;
|
|
2100
2242
|
try {
|
|
2101
|
-
const target =
|
|
2102
|
-
const root =
|
|
2103
|
-
if (target.startsWith(root +
|
|
2104
|
-
else warnLink(toPosix(
|
|
2243
|
+
const target = fs13.realpathSync(full);
|
|
2244
|
+
const root = fs13.realpathSync(base);
|
|
2245
|
+
if (target.startsWith(root + path13.sep)) isFile = fs13.statSync(target).isFile();
|
|
2246
|
+
else warnLink(toPosix(path13.relative(base, full)));
|
|
2105
2247
|
} catch {
|
|
2106
2248
|
}
|
|
2107
2249
|
}
|
|
2108
2250
|
if (isDir) walk(full, base, out);
|
|
2109
|
-
else if (isFile) out[toPosix(
|
|
2251
|
+
else if (isFile) out[toPosix(path13.relative(base, full))] = hashFile(full);
|
|
2110
2252
|
}
|
|
2111
2253
|
}
|
|
2112
2254
|
function unitManifest(paths, key) {
|
|
2113
2255
|
const manifest = {};
|
|
2114
2256
|
if (key === SYSTEM_UNIT) {
|
|
2115
|
-
if (
|
|
2257
|
+
if (fs13.existsSync(paths.config) && fs13.statSync(paths.config).isFile())
|
|
2116
2258
|
manifest["design.json"] = hashFile(paths.config);
|
|
2117
2259
|
walk(paths.system, paths.design, manifest);
|
|
2118
2260
|
} else walk(unitRoot(paths, key), paths.design, manifest);
|
|
@@ -2121,13 +2263,13 @@ function unitManifest(paths, key) {
|
|
|
2121
2263
|
function buildManifest(dir) {
|
|
2122
2264
|
const manifest = {};
|
|
2123
2265
|
const visit = (current) => {
|
|
2124
|
-
for (const entry of
|
|
2125
|
-
const full =
|
|
2266
|
+
for (const entry of fs13.readdirSync(current, { withFileTypes: true })) {
|
|
2267
|
+
const full = path13.join(current, entry.name);
|
|
2126
2268
|
if (entry.isDirectory()) visit(full);
|
|
2127
|
-
else if (entry.isFile()) manifest[toPosix(
|
|
2269
|
+
else if (entry.isFile()) manifest[toPosix(path13.relative(dir, full))] = hashFile(full);
|
|
2128
2270
|
}
|
|
2129
2271
|
};
|
|
2130
|
-
if (
|
|
2272
|
+
if (fs13.existsSync(dir)) visit(dir);
|
|
2131
2273
|
return sortManifest(manifest);
|
|
2132
2274
|
}
|
|
2133
2275
|
function sortManifest(manifest) {
|
|
@@ -2136,18 +2278,18 @@ function sortManifest(manifest) {
|
|
|
2136
2278
|
function scanLocal(paths) {
|
|
2137
2279
|
const units = /* @__PURE__ */ new Map();
|
|
2138
2280
|
const notSynced = [];
|
|
2139
|
-
if (!
|
|
2281
|
+
if (!fs13.existsSync(paths.design)) return { units, notSynced };
|
|
2140
2282
|
const system = unitManifest(paths, SYSTEM_UNIT);
|
|
2141
2283
|
if (Object.keys(system).length) units.set(SYSTEM_UNIT, { key: SYSTEM_UNIT, kind: "system", manifest: system });
|
|
2142
|
-
for (const entry of
|
|
2284
|
+
for (const entry of fs13.readdirSync(paths.design, { withFileTypes: true })) {
|
|
2143
2285
|
const { name } = entry;
|
|
2144
2286
|
if (name.startsWith(".") || LOCAL_ONLY.has(name)) continue;
|
|
2145
2287
|
if (name === "design.json" && entry.isFile()) continue;
|
|
2146
2288
|
if ((name === "system" || name === "canvas") && entry.isDirectory()) continue;
|
|
2147
2289
|
notSynced.push(entry.isDirectory() ? `${name}/` : name);
|
|
2148
2290
|
}
|
|
2149
|
-
if (
|
|
2150
|
-
for (const entry of
|
|
2291
|
+
if (fs13.existsSync(paths.canvases) && fs13.statSync(paths.canvases).isDirectory()) {
|
|
2292
|
+
for (const entry of fs13.readdirSync(paths.canvases, { withFileTypes: true })) {
|
|
2151
2293
|
if (entry.name.startsWith(".")) continue;
|
|
2152
2294
|
if (!entry.isDirectory() || !ID_PATTERN.test(entry.name)) {
|
|
2153
2295
|
notSynced.push(`canvas/${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
@@ -2161,11 +2303,11 @@ function scanLocal(paths) {
|
|
|
2161
2303
|
return { units, notSynced: notSynced.sort() };
|
|
2162
2304
|
}
|
|
2163
2305
|
function unitBuildDir(paths, key) {
|
|
2164
|
-
return
|
|
2306
|
+
return path13.join(paths.cache, "cloud", "build", ...key.split("/"));
|
|
2165
2307
|
}
|
|
2166
2308
|
async function buildUnit(paths, key, out = unitBuildDir(paths, key)) {
|
|
2167
2309
|
const id2 = unitCanvasId(key);
|
|
2168
|
-
|
|
2310
|
+
fs13.rmSync(out, { recursive: true, force: true });
|
|
2169
2311
|
return buildSite(paths, out, {
|
|
2170
2312
|
canvases: id2 === null ? [] : [id2],
|
|
2171
2313
|
includeSystem: id2 === null,
|
|
@@ -2296,7 +2438,7 @@ var CloudClient = class {
|
|
|
2296
2438
|
}
|
|
2297
2439
|
host;
|
|
2298
2440
|
token;
|
|
2299
|
-
async send(method,
|
|
2441
|
+
async send(method, path27, init) {
|
|
2300
2442
|
const headers = {
|
|
2301
2443
|
"User-Agent": `foss-design/${packageVersion()} (${os.platform()})`,
|
|
2302
2444
|
Accept: "application/json"
|
|
@@ -2304,7 +2446,7 @@ var CloudClient = class {
|
|
|
2304
2446
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
2305
2447
|
if (init.type) headers["Content-Type"] = init.type;
|
|
2306
2448
|
try {
|
|
2307
|
-
return await fetch(`${this.host}/api${
|
|
2449
|
+
return await fetch(`${this.host}/api${path27}`, {
|
|
2308
2450
|
method,
|
|
2309
2451
|
headers,
|
|
2310
2452
|
body: init.body,
|
|
@@ -2325,8 +2467,8 @@ var CloudClient = class {
|
|
|
2325
2467
|
throw errorFor(this.host, response.status, body, response.headers.get("retry-after"));
|
|
2326
2468
|
}
|
|
2327
2469
|
/** JSON in, JSON out; any non-2xx answer throws a CloudError. */
|
|
2328
|
-
async request(method,
|
|
2329
|
-
const response = await this.send(method,
|
|
2470
|
+
async request(method, path27, body) {
|
|
2471
|
+
const response = await this.send(method, path27, {
|
|
2330
2472
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2331
2473
|
type: body === void 0 ? void 0 : "application/json",
|
|
2332
2474
|
timeout: JSON_TIMEOUT
|
|
@@ -2338,7 +2480,7 @@ var CloudClient = class {
|
|
|
2338
2480
|
try {
|
|
2339
2481
|
return JSON.parse(text2);
|
|
2340
2482
|
} catch {
|
|
2341
|
-
throw new CloudError(`${this.host} answered ${method} ${
|
|
2483
|
+
throw new CloudError(`${this.host} answered ${method} ${path27} with something other than JSON`, 0, "bad_response");
|
|
2342
2484
|
}
|
|
2343
2485
|
}
|
|
2344
2486
|
/** POST /device/code. */
|
|
@@ -2472,9 +2614,9 @@ var CloudClient = class {
|
|
|
2472
2614
|
};
|
|
2473
2615
|
|
|
2474
2616
|
// src/cloud/credentials.ts
|
|
2475
|
-
import
|
|
2617
|
+
import fs14 from "fs";
|
|
2476
2618
|
import os2 from "os";
|
|
2477
|
-
import
|
|
2619
|
+
import path14 from "path";
|
|
2478
2620
|
var DEFAULT_CLOUD = "https://app.fossdesign.dev";
|
|
2479
2621
|
function normalizeHost(value) {
|
|
2480
2622
|
let url;
|
|
@@ -2491,14 +2633,14 @@ function cloudHost() {
|
|
|
2491
2633
|
return normalizeHost(process.env.FOSS_DESIGN_CLOUD || DEFAULT_CLOUD);
|
|
2492
2634
|
}
|
|
2493
2635
|
function credentialsFile() {
|
|
2494
|
-
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME ||
|
|
2495
|
-
return
|
|
2636
|
+
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME || path14.join(os2.homedir(), ".config");
|
|
2637
|
+
return path14.join(base, "foss-design", "credentials.json");
|
|
2496
2638
|
}
|
|
2497
2639
|
function readAll() {
|
|
2498
2640
|
const file = credentialsFile();
|
|
2499
|
-
if (!
|
|
2641
|
+
if (!fs14.existsSync(file)) return {};
|
|
2500
2642
|
try {
|
|
2501
|
-
const data = JSON.parse(
|
|
2643
|
+
const data = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
2502
2644
|
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
2503
2645
|
} catch (error) {
|
|
2504
2646
|
throw new CliError(`${file} is not valid JSON (${error.message}); fix or delete it`);
|
|
@@ -2506,12 +2648,12 @@ function readAll() {
|
|
|
2506
2648
|
}
|
|
2507
2649
|
function writeAll(data) {
|
|
2508
2650
|
const file = credentialsFile();
|
|
2509
|
-
|
|
2651
|
+
fs14.mkdirSync(path14.dirname(file), { recursive: true, mode: 448 });
|
|
2510
2652
|
const temp = `${file}.${process.pid}.tmp`;
|
|
2511
|
-
|
|
2653
|
+
fs14.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2512
2654
|
`, { mode: 384 });
|
|
2513
|
-
|
|
2514
|
-
|
|
2655
|
+
fs14.chmodSync(temp, 384);
|
|
2656
|
+
fs14.renameSync(temp, file);
|
|
2515
2657
|
}
|
|
2516
2658
|
function getCredential(host) {
|
|
2517
2659
|
const entry = readAll()[host];
|
|
@@ -2534,17 +2676,17 @@ function requireCredential(host) {
|
|
|
2534
2676
|
}
|
|
2535
2677
|
|
|
2536
2678
|
// src/cloud/state.ts
|
|
2537
|
-
import
|
|
2538
|
-
import
|
|
2539
|
-
var linkFile = (paths) =>
|
|
2540
|
-
var cloudCache = (paths) =>
|
|
2541
|
-
var incomingDir = (paths, key) =>
|
|
2679
|
+
import fs15 from "fs";
|
|
2680
|
+
import path15 from "path";
|
|
2681
|
+
var linkFile = (paths) => path15.join(paths.design, "cloud.json");
|
|
2682
|
+
var cloudCache = (paths) => path15.join(paths.cache, "cloud");
|
|
2683
|
+
var incomingDir = (paths, key) => path15.join(cloudCache(paths), "incoming", ...key.split("/"));
|
|
2542
2684
|
function readLink(paths) {
|
|
2543
2685
|
const file = linkFile(paths);
|
|
2544
|
-
if (!
|
|
2686
|
+
if (!fs15.existsSync(file)) return null;
|
|
2545
2687
|
let data;
|
|
2546
2688
|
try {
|
|
2547
|
-
data = JSON.parse(
|
|
2689
|
+
data = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
2548
2690
|
} catch (error) {
|
|
2549
2691
|
throw new CliError(`${file} is not valid JSON (${error.message}); run \`design link <project>\` again`);
|
|
2550
2692
|
}
|
|
@@ -2574,9 +2716,9 @@ function writeLink(paths, link) {
|
|
|
2574
2716
|
units: sorted(link.units),
|
|
2575
2717
|
conflicts: sorted(link.conflicts)
|
|
2576
2718
|
};
|
|
2577
|
-
|
|
2719
|
+
fs15.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2578
2720
|
`);
|
|
2579
|
-
|
|
2721
|
+
fs15.renameSync(temp, file);
|
|
2580
2722
|
}
|
|
2581
2723
|
var hashes = (manifest) => Object.fromEntries(Object.entries(manifest).map(([file, entry]) => [file, entry.hash]));
|
|
2582
2724
|
function diffFiles(a, b) {
|
|
@@ -2654,8 +2796,8 @@ var byUnit = (a, b) => a === SYSTEM_UNIT ? -1 : b === SYSTEM_UNIT ? 1 : a < b ?
|
|
|
2654
2796
|
var plural = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
2655
2797
|
|
|
2656
2798
|
// src/cloud/pull.ts
|
|
2657
|
-
import
|
|
2658
|
-
import
|
|
2799
|
+
import fs16 from "fs";
|
|
2800
|
+
import path16 from "path";
|
|
2659
2801
|
|
|
2660
2802
|
// src/capture/frames.ts
|
|
2661
2803
|
var AUTO_VIEWPORT_HEIGHT = 900;
|
|
@@ -2738,34 +2880,34 @@ function checkManifest(key, manifest) {
|
|
|
2738
2880
|
}
|
|
2739
2881
|
}
|
|
2740
2882
|
function writeInside(root, rel, data) {
|
|
2741
|
-
const file =
|
|
2883
|
+
const file = path16.join(root, rel);
|
|
2742
2884
|
if (!isInside(root, file)) throw new CliError(`Refusing to write outside ${root}: ${rel}`);
|
|
2743
|
-
|
|
2744
|
-
if (!isInside(
|
|
2885
|
+
fs16.mkdirSync(path16.dirname(file), { recursive: true });
|
|
2886
|
+
if (!isInside(fs16.realpathSync(root), fs16.realpathSync(path16.dirname(file))))
|
|
2745
2887
|
throw new CliError(`Refusing to write through a link out of ${root}: ${rel}`);
|
|
2746
|
-
if (
|
|
2747
|
-
|
|
2888
|
+
if (fs16.existsSync(file) && fs16.lstatSync(file).isSymbolicLink()) fs16.unlinkSync(file);
|
|
2889
|
+
fs16.writeFileSync(file, data);
|
|
2748
2890
|
}
|
|
2749
2891
|
function pruneEmpty(dir, stop) {
|
|
2750
2892
|
let current = dir;
|
|
2751
2893
|
while (isInside(stop, current) && current !== stop) {
|
|
2752
2894
|
try {
|
|
2753
|
-
if (
|
|
2754
|
-
|
|
2895
|
+
if (fs16.readdirSync(current).length) return;
|
|
2896
|
+
fs16.rmdirSync(current);
|
|
2755
2897
|
} catch {
|
|
2756
2898
|
return;
|
|
2757
2899
|
}
|
|
2758
|
-
current =
|
|
2900
|
+
current = path16.dirname(current);
|
|
2759
2901
|
}
|
|
2760
2902
|
}
|
|
2761
2903
|
function applyUnit(paths, key, local, target, blobs) {
|
|
2762
2904
|
const counts = { added: 0, changed: 0, removed: 0 };
|
|
2763
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
2905
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path16.dirname(unitRoot(paths, key));
|
|
2764
2906
|
for (const rel of Object.keys(local)) {
|
|
2765
2907
|
if (rel in target) continue;
|
|
2766
|
-
const file =
|
|
2767
|
-
|
|
2768
|
-
pruneEmpty(
|
|
2908
|
+
const file = path16.join(paths.design, rel);
|
|
2909
|
+
fs16.rmSync(file, { force: true });
|
|
2910
|
+
pruneEmpty(path16.dirname(file), stop);
|
|
2769
2911
|
counts.removed++;
|
|
2770
2912
|
}
|
|
2771
2913
|
for (const [rel, entry] of Object.entries(target)) {
|
|
@@ -2828,7 +2970,7 @@ async function pull(ctx, options) {
|
|
|
2828
2970
|
} else if (!s.ahead) {
|
|
2829
2971
|
if (s.conflictRev !== void 0) {
|
|
2830
2972
|
delete link.conflicts[key];
|
|
2831
|
-
|
|
2973
|
+
fs16.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
2832
2974
|
}
|
|
2833
2975
|
report.units.push({ ...reportFor("up_to_date"), rev: s.baseRev });
|
|
2834
2976
|
} else if (!s.changed.length) {
|
|
@@ -2858,14 +3000,14 @@ async function pull(ctx, options) {
|
|
|
2858
3000
|
headRev: unit.headRev,
|
|
2859
3001
|
incoming: incomingDir(paths, key)
|
|
2860
3002
|
};
|
|
2861
|
-
entry.message = `changed here and in the cloud; the cloud version (r${unit.headRev}) is in ${
|
|
2862
|
-
if (s.conflictRev === unit.headRev &&
|
|
3003
|
+
entry.message = `changed here and in the cloud; the cloud version (r${unit.headRev}) is in ${path16.relative(paths.root, entry.incoming)}`;
|
|
3004
|
+
if (s.conflictRev === unit.headRev && fs16.existsSync(entry.incoming)) report.units.push(entry);
|
|
2863
3005
|
else plans.push({ key, kind: "incoming", remote: unit, report: entry });
|
|
2864
3006
|
}
|
|
2865
3007
|
}
|
|
2866
3008
|
const byHash = /* @__PURE__ */ new Map();
|
|
2867
3009
|
for (const unit of scan.units.values())
|
|
2868
|
-
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash,
|
|
3010
|
+
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash, path16.join(paths.design, rel));
|
|
2869
3011
|
const needed = /* @__PURE__ */ new Set();
|
|
2870
3012
|
for (const plan of plans) {
|
|
2871
3013
|
if (!plan.remote) continue;
|
|
@@ -2876,7 +3018,7 @@ async function pull(ctx, options) {
|
|
|
2876
3018
|
const download = [];
|
|
2877
3019
|
for (const hash of needed) {
|
|
2878
3020
|
const file = byHash.get(hash);
|
|
2879
|
-
if (file) blobs.set(hash,
|
|
3021
|
+
if (file) blobs.set(hash, fs16.readFileSync(file));
|
|
2880
3022
|
else download.push(hash);
|
|
2881
3023
|
}
|
|
2882
3024
|
if (download.length) {
|
|
@@ -2902,11 +3044,11 @@ async function pull(ctx, options) {
|
|
|
2902
3044
|
if (plan.remote) link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
2903
3045
|
else delete link.units[key];
|
|
2904
3046
|
delete link.conflicts[key];
|
|
2905
|
-
|
|
3047
|
+
fs16.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
2906
3048
|
} else {
|
|
2907
3049
|
const dir = incomingDir(paths, key);
|
|
2908
|
-
|
|
2909
|
-
|
|
3050
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
3051
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
2910
3052
|
for (const [rel, file] of Object.entries(plan.remote.manifest))
|
|
2911
3053
|
writeInside(dir, unitRelative(key, rel), blobs.get(file.hash));
|
|
2912
3054
|
link.conflicts[key] = plan.remote.headRev;
|
|
@@ -2927,8 +3069,8 @@ async function pull(ctx, options) {
|
|
|
2927
3069
|
}
|
|
2928
3070
|
|
|
2929
3071
|
// src/cloud/push.ts
|
|
2930
|
-
import
|
|
2931
|
-
import
|
|
3072
|
+
import fs17 from "fs";
|
|
3073
|
+
import path17 from "path";
|
|
2932
3074
|
function blockedReport(status, message) {
|
|
2933
3075
|
return {
|
|
2934
3076
|
unit: status.key,
|
|
@@ -3034,7 +3176,7 @@ async function push(ctx, options) {
|
|
|
3034
3176
|
for (const key of toPush) {
|
|
3035
3177
|
const s = status(key);
|
|
3036
3178
|
if (s.conflictRev !== void 0) {
|
|
3037
|
-
const dir =
|
|
3179
|
+
const dir = path17.relative(paths.root, incomingDir(paths, key));
|
|
3038
3180
|
blocked.push(
|
|
3039
3181
|
blockedReport(s, `pull left the cloud version in ${dir}; merge it, then \`design push --resolved ${key}\``)
|
|
3040
3182
|
);
|
|
@@ -3078,7 +3220,7 @@ async function push(ctx, options) {
|
|
|
3078
3220
|
const files = /* @__PURE__ */ new Map();
|
|
3079
3221
|
const addFiles = (manifest, dir) => {
|
|
3080
3222
|
for (const [rel, entry] of Object.entries(manifest))
|
|
3081
|
-
if (!files.has(entry.hash)) files.set(entry.hash,
|
|
3223
|
+
if (!files.has(entry.hash)) files.set(entry.hash, path17.join(dir, rel));
|
|
3082
3224
|
};
|
|
3083
3225
|
for (const key of pushing) {
|
|
3084
3226
|
const id2 = unitCanvasId(key);
|
|
@@ -3110,7 +3252,7 @@ async function push(ctx, options) {
|
|
|
3110
3252
|
if (missing.length) {
|
|
3111
3253
|
const unknown = missing.find((hash) => !files.has(hash));
|
|
3112
3254
|
if (unknown) throw new CliError(`The cloud asked for ${unknown.slice(0, 12)}, which is not part of this push`);
|
|
3113
|
-
const bytes = missing.reduce((sum, hash) => sum +
|
|
3255
|
+
const bytes = missing.reduce((sum, hash) => sum + fs17.statSync(files.get(hash)).size, 0);
|
|
3114
3256
|
const bar = ctx.progress();
|
|
3115
3257
|
const started = Date.now();
|
|
3116
3258
|
let sentFiles = 0;
|
|
@@ -3119,12 +3261,12 @@ async function push(ctx, options) {
|
|
|
3119
3261
|
bar.update(line());
|
|
3120
3262
|
await mapLimit(missing, TRANSFERS, async (hash) => {
|
|
3121
3263
|
const file = files.get(hash);
|
|
3122
|
-
const data =
|
|
3264
|
+
const data = fs17.readFileSync(file);
|
|
3123
3265
|
try {
|
|
3124
3266
|
await client.putBlob(link.project, hash, data);
|
|
3125
3267
|
} catch (error) {
|
|
3126
3268
|
if (error instanceof CloudError && error.status === 413)
|
|
3127
|
-
throw new CliError(`${
|
|
3269
|
+
throw new CliError(`${path17.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
3128
3270
|
throw error;
|
|
3129
3271
|
}
|
|
3130
3272
|
sentFiles++;
|
|
@@ -3146,7 +3288,7 @@ async function push(ctx, options) {
|
|
|
3146
3288
|
url: webUrl(link, unit.key)
|
|
3147
3289
|
});
|
|
3148
3290
|
}
|
|
3149
|
-
for (const key of resolved)
|
|
3291
|
+
for (const key of resolved) fs17.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3150
3292
|
writeLink(paths, link);
|
|
3151
3293
|
report.units.sort((a, b) => byUnit(a.unit, b.unit));
|
|
3152
3294
|
return report;
|
|
@@ -3156,8 +3298,9 @@ function refuseLegacyGo(paths, keys) {
|
|
|
3156
3298
|
const hits = /* @__PURE__ */ new Set();
|
|
3157
3299
|
for (const key of keys) {
|
|
3158
3300
|
if (!key.startsWith("canvas/")) continue;
|
|
3159
|
-
|
|
3160
|
-
|
|
3301
|
+
const id2 = key.slice("canvas/".length);
|
|
3302
|
+
for (const file of canvasCodeFiles(path17.join(paths.canvases, id2), project.canvas(id2)?.screens ?? [])) {
|
|
3303
|
+
for (const line of legacyGoLines(file)) hits.add(`${relToRoot(paths, file)}:${line}`);
|
|
3161
3304
|
}
|
|
3162
3305
|
}
|
|
3163
3306
|
if (!hits.size) return;
|
|
@@ -3477,7 +3620,7 @@ async function runMe(paths, options) {
|
|
|
3477
3620
|
}
|
|
3478
3621
|
|
|
3479
3622
|
// src/capture/chrome.ts
|
|
3480
|
-
import
|
|
3623
|
+
import fs18 from "fs";
|
|
3481
3624
|
var CANDIDATES = {
|
|
3482
3625
|
darwin: [
|
|
3483
3626
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -3501,8 +3644,8 @@ var CANDIDATES = {
|
|
|
3501
3644
|
};
|
|
3502
3645
|
function findChrome() {
|
|
3503
3646
|
const configured = process.env.DESIGN_CHROME;
|
|
3504
|
-
if (configured &&
|
|
3505
|
-
for (const candidate of CANDIDATES[process.platform] ?? []) if (
|
|
3647
|
+
if (configured && fs18.existsSync(configured)) return configured;
|
|
3648
|
+
for (const candidate of CANDIDATES[process.platform] ?? []) if (fs18.existsSync(candidate)) return candidate;
|
|
3506
3649
|
return null;
|
|
3507
3650
|
}
|
|
3508
3651
|
async function launchChrome() {
|
|
@@ -3515,22 +3658,40 @@ async function launchChrome() {
|
|
|
3515
3658
|
const { chromium } = await import("playwright-core");
|
|
3516
3659
|
return chromium.launch({ executablePath, headless: true, args: ["--hide-scrollbars", "--force-color-profile=srgb"] });
|
|
3517
3660
|
}
|
|
3661
|
+
var OUTDATED_DEP = /Outdated Optimize Dep/;
|
|
3518
3662
|
async function openFrame(page, url, options = {}) {
|
|
3519
|
-
|
|
3520
|
-
|
|
3663
|
+
let errors = [];
|
|
3664
|
+
let outdated = () => {
|
|
3665
|
+
};
|
|
3666
|
+
const record2 = (text2) => {
|
|
3667
|
+
errors.push(text2);
|
|
3668
|
+
if (OUTDATED_DEP.test(text2)) outdated();
|
|
3669
|
+
};
|
|
3670
|
+
page.on("pageerror", (error) => record2(error.message));
|
|
3521
3671
|
page.on("console", (message) => {
|
|
3522
|
-
if (message.type()
|
|
3672
|
+
if (message.type() !== "error") return;
|
|
3673
|
+
let text2 = message.text().replace(/^(?:\s*%[osdifcO])+\s*/, "");
|
|
3674
|
+
const where = message.location().url;
|
|
3675
|
+
if (/^Failed to load resource/.test(text2) && where && !text2.includes(where)) text2 += ` (${where})`;
|
|
3676
|
+
record2(text2);
|
|
3523
3677
|
});
|
|
3524
|
-
|
|
3525
|
-
let
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3678
|
+
let ready = false;
|
|
3679
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3680
|
+
errors = [];
|
|
3681
|
+
ready = options.waitForReady === false;
|
|
3682
|
+
const turnedAway = new Promise((resolve) => {
|
|
3683
|
+
outdated = () => resolve("outdated");
|
|
3684
|
+
});
|
|
3685
|
+
await page.goto(url, { waitUntil: "load", timeout: options.timeoutMs ?? 3e4 });
|
|
3686
|
+
if (!ready) {
|
|
3687
|
+
const signal = page.waitForFunction(() => window.__DESIGN_READY__ === true, null, {
|
|
3529
3688
|
timeout: options.timeoutMs ?? 2e4
|
|
3689
|
+
}).then(() => "ready");
|
|
3690
|
+
signal.catch(() => {
|
|
3530
3691
|
});
|
|
3531
|
-
ready =
|
|
3532
|
-
} catch {
|
|
3692
|
+
ready = await Promise.race([signal, turnedAway]).catch(() => null) === "ready";
|
|
3533
3693
|
}
|
|
3694
|
+
if (!errors.some((error) => OUTDATED_DEP.test(error))) break;
|
|
3534
3695
|
}
|
|
3535
3696
|
await page.waitForTimeout(options.settleMs ?? 700);
|
|
3536
3697
|
const runtimeErrors = await page.evaluate(() => window.__DESIGN_ERRORS__ ?? []).catch(() => []);
|
|
@@ -3547,26 +3708,26 @@ async function openFrame(page, url, options = {}) {
|
|
|
3547
3708
|
}
|
|
3548
3709
|
|
|
3549
3710
|
// src/server/state.ts
|
|
3550
|
-
import
|
|
3551
|
-
import
|
|
3552
|
-
var stateFile = (paths) =>
|
|
3553
|
-
var logFile = (paths) =>
|
|
3711
|
+
import fs19 from "fs";
|
|
3712
|
+
import path18 from "path";
|
|
3713
|
+
var stateFile = (paths) => path18.join(paths.cache, "server.json");
|
|
3714
|
+
var logFile = (paths) => path18.join(paths.cache, "server.log");
|
|
3554
3715
|
function readState(paths) {
|
|
3555
3716
|
try {
|
|
3556
|
-
return JSON.parse(
|
|
3717
|
+
return JSON.parse(fs19.readFileSync(stateFile(paths), "utf8"));
|
|
3557
3718
|
} catch {
|
|
3558
3719
|
return null;
|
|
3559
3720
|
}
|
|
3560
3721
|
}
|
|
3561
3722
|
function writeState(paths, state) {
|
|
3562
|
-
|
|
3563
|
-
|
|
3723
|
+
fs19.mkdirSync(paths.cache, { recursive: true });
|
|
3724
|
+
fs19.writeFileSync(stateFile(paths), `${JSON.stringify(state, null, 2)}
|
|
3564
3725
|
`);
|
|
3565
3726
|
}
|
|
3566
3727
|
function clearState(paths, pid) {
|
|
3567
3728
|
const state = readState(paths);
|
|
3568
3729
|
if (pid !== void 0 && state && state.pid !== pid) return;
|
|
3569
|
-
|
|
3730
|
+
fs19.rmSync(stateFile(paths), { force: true });
|
|
3570
3731
|
}
|
|
3571
3732
|
function pidAlive(pid) {
|
|
3572
3733
|
try {
|
|
@@ -3591,18 +3752,18 @@ async function liveServer(paths) {
|
|
|
3591
3752
|
|
|
3592
3753
|
// src/cli/commands/preview.ts
|
|
3593
3754
|
import { spawn } from "child_process";
|
|
3594
|
-
import
|
|
3755
|
+
import fs23 from "fs";
|
|
3595
3756
|
|
|
3596
3757
|
// src/server/index.ts
|
|
3597
|
-
import
|
|
3758
|
+
import fs22 from "fs";
|
|
3598
3759
|
import http from "http";
|
|
3599
|
-
import
|
|
3760
|
+
import path21 from "path";
|
|
3600
3761
|
import sirv from "sirv";
|
|
3601
3762
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
3602
3763
|
|
|
3603
3764
|
// src/server/api.ts
|
|
3604
|
-
import
|
|
3605
|
-
import
|
|
3765
|
+
import fs20 from "fs";
|
|
3766
|
+
import path19 from "path";
|
|
3606
3767
|
function sendJson(res, status, body) {
|
|
3607
3768
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
3608
3769
|
res.end(JSON.stringify(body));
|
|
@@ -3653,17 +3814,17 @@ async function handleApi(ctx, req, res, url) {
|
|
|
3653
3814
|
}
|
|
3654
3815
|
if (route2 === "/source") {
|
|
3655
3816
|
const rel = url.searchParams.get("path") ?? "";
|
|
3656
|
-
const file =
|
|
3657
|
-
if (!rel || !isInside(project.paths.root, file) || !
|
|
3817
|
+
const file = path19.resolve(project.paths.root, rel);
|
|
3818
|
+
if (!rel || !isInside(project.paths.root, file) || !fs20.existsSync(file) || fs20.statSync(file).isDirectory()) {
|
|
3658
3819
|
sendJson(res, 404, { error: "not found" });
|
|
3659
3820
|
return true;
|
|
3660
3821
|
}
|
|
3661
|
-
if (
|
|
3822
|
+
if (fs20.statSync(file).size > 2 * 1024 * 1024) {
|
|
3662
3823
|
sendJson(res, 413, { error: "file too large to show" });
|
|
3663
3824
|
return true;
|
|
3664
3825
|
}
|
|
3665
3826
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
3666
|
-
res.end(
|
|
3827
|
+
res.end(fs20.readFileSync(file, "utf8"));
|
|
3667
3828
|
return true;
|
|
3668
3829
|
}
|
|
3669
3830
|
const snapshotPost = /^\/snapshots\/([^/]+)\/([^/]+)$/.exec(route2);
|
|
@@ -3693,7 +3854,7 @@ async function handleApi(ctx, req, res, url) {
|
|
|
3693
3854
|
return true;
|
|
3694
3855
|
}
|
|
3695
3856
|
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=31536000, immutable" });
|
|
3696
|
-
|
|
3857
|
+
fs20.createReadStream(file).pipe(res);
|
|
3697
3858
|
return true;
|
|
3698
3859
|
}
|
|
3699
3860
|
return false;
|
|
@@ -3733,8 +3894,8 @@ var EventHub = class {
|
|
|
3733
3894
|
};
|
|
3734
3895
|
|
|
3735
3896
|
// src/server/snapshots.ts
|
|
3736
|
-
import
|
|
3737
|
-
import
|
|
3897
|
+
import fs21 from "fs";
|
|
3898
|
+
import path20 from "path";
|
|
3738
3899
|
var SAFE = /^@?[a-z0-9][a-z0-9_-]*$/i;
|
|
3739
3900
|
function isSafeSegment(value) {
|
|
3740
3901
|
return SAFE.test(value);
|
|
@@ -3748,14 +3909,14 @@ var SnapshotStore = class {
|
|
|
3748
3909
|
urlBase;
|
|
3749
3910
|
meta = /* @__PURE__ */ new Map();
|
|
3750
3911
|
metaFile(canvas) {
|
|
3751
|
-
return
|
|
3912
|
+
return path20.join(this.dir, canvas, "meta.json");
|
|
3752
3913
|
}
|
|
3753
3914
|
load(canvas) {
|
|
3754
3915
|
const cached = this.meta.get(canvas);
|
|
3755
3916
|
if (cached) return cached;
|
|
3756
3917
|
let data = {};
|
|
3757
3918
|
try {
|
|
3758
|
-
data = JSON.parse(
|
|
3919
|
+
data = JSON.parse(fs21.readFileSync(this.metaFile(canvas), "utf8"));
|
|
3759
3920
|
} catch {
|
|
3760
3921
|
}
|
|
3761
3922
|
this.meta.set(canvas, data);
|
|
@@ -3781,21 +3942,21 @@ var SnapshotStore = class {
|
|
|
3781
3942
|
};
|
|
3782
3943
|
save(canvas, id2, theme2, png, height) {
|
|
3783
3944
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) throw new Error("bad snapshot key");
|
|
3784
|
-
const dir =
|
|
3785
|
-
|
|
3786
|
-
|
|
3945
|
+
const dir = path20.join(this.dir, canvas);
|
|
3946
|
+
fs21.mkdirSync(dir, { recursive: true });
|
|
3947
|
+
fs21.writeFileSync(path20.join(dir, `${id2}.${theme2}.png`), png);
|
|
3787
3948
|
const data = this.load(canvas);
|
|
3788
3949
|
const version = Date.now();
|
|
3789
3950
|
data[id2] = { ...data[id2], [theme2]: version, ...height ? { height } : {} };
|
|
3790
|
-
|
|
3951
|
+
fs21.writeFileSync(this.metaFile(canvas), JSON.stringify(data));
|
|
3791
3952
|
return { url: this.url(canvas, id2, theme2, version) };
|
|
3792
3953
|
}
|
|
3793
3954
|
/** Absolute path of `<id>.<theme>.png`, or of its thumbnail for `<id>.<theme>.thumb.png` (made on first use); null when missing. */
|
|
3794
3955
|
file(canvas, name) {
|
|
3795
3956
|
const match = /^(@?[a-z0-9][a-z0-9_-]*)\.(light|dark)(\.thumb)?\.png$/i.exec(name);
|
|
3796
3957
|
if (!isSafeSegment(canvas) || !match) return null;
|
|
3797
|
-
const file =
|
|
3798
|
-
if (!
|
|
3958
|
+
const file = path20.join(this.dir, canvas, `${match[1]}.${match[2]}.png`);
|
|
3959
|
+
if (!fs21.existsSync(file)) return null;
|
|
3799
3960
|
return match[3] ? ensureThumb(file) : file;
|
|
3800
3961
|
}
|
|
3801
3962
|
};
|
|
@@ -3834,8 +3995,8 @@ function notFoundPage(res, title, text2) {
|
|
|
3834
3995
|
}
|
|
3835
3996
|
async function startDevServer(options) {
|
|
3836
3997
|
const paths = designPaths(options.root);
|
|
3837
|
-
if (!
|
|
3838
|
-
|
|
3998
|
+
if (!fs22.existsSync(paths.design)) throw new Error(`No .design folder in ${options.root}; run \`design init\``);
|
|
3999
|
+
fs22.mkdirSync(paths.cache, { recursive: true });
|
|
3839
4000
|
const snapshots = new SnapshotStore(paths.snapshots);
|
|
3840
4001
|
const project = new DesignProject(paths, DEV_URLS, snapshots.lookup);
|
|
3841
4002
|
const entries = new Entries(project);
|
|
@@ -3843,6 +4004,7 @@ async function startDevServer(options) {
|
|
|
3843
4004
|
const links = linkShippedPackages(paths);
|
|
3844
4005
|
const events = new EventHub();
|
|
3845
4006
|
const httpServer = http.createServer();
|
|
4007
|
+
dropStaleDepCache(paths);
|
|
3846
4008
|
const base = baseConfig(project);
|
|
3847
4009
|
const vite = await createServer({
|
|
3848
4010
|
...base,
|
|
@@ -3861,19 +4023,28 @@ async function startDevServer(options) {
|
|
|
3861
4023
|
}
|
|
3862
4024
|
},
|
|
3863
4025
|
optimizeDeps: {
|
|
3864
|
-
entries: entries.files().map((file) => toPosix(
|
|
4026
|
+
entries: entries.files().map((file) => toPosix(path21.relative(paths.design, file)))
|
|
3865
4027
|
}
|
|
3866
4028
|
});
|
|
3867
|
-
const
|
|
3868
|
-
const
|
|
4029
|
+
const publicDir = project.publicDir();
|
|
4030
|
+
const servePublic = publicDir ? sirv(publicDir, { dev: true }) : null;
|
|
4031
|
+
const publicRel = (encoded) => {
|
|
4032
|
+
try {
|
|
4033
|
+
return decodeURIComponent(encoded);
|
|
4034
|
+
} catch {
|
|
4035
|
+
return null;
|
|
4036
|
+
}
|
|
4037
|
+
};
|
|
4038
|
+
const viewer = fs22.existsSync(PKG.viewer) ? sirv(PKG.viewer, { dev: true, etag: true }) : null;
|
|
4039
|
+
const viewerIndex = path21.join(PKG.viewer, "index.html");
|
|
3869
4040
|
const serveViewerIndex = (res) => {
|
|
3870
|
-
const html =
|
|
4041
|
+
const html = fs22.readFileSync(viewerIndex, "utf8").replace(/<head>/i, '<head>\n<base href="/">');
|
|
3871
4042
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
3872
4043
|
res.end(html);
|
|
3873
4044
|
};
|
|
3874
4045
|
const ctx = { project, events, snapshots };
|
|
3875
4046
|
const systemCssUrl = `/${relToDesign(paths, entries.systemCssFile())}`;
|
|
3876
|
-
const bootUrl = `/${relToDesign(paths,
|
|
4047
|
+
const bootUrl = `/${relToDesign(paths, path21.join(entries.dir, HTML_BOOT))}`;
|
|
3877
4048
|
const frameHead = (source, extra = {}) => {
|
|
3878
4049
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
3879
4050
|
return {
|
|
@@ -3882,6 +4053,8 @@ async function startDevServer(options) {
|
|
|
3882
4053
|
fonts: project.system()?.doc.fonts ?? [],
|
|
3883
4054
|
snapshots: source.canvas !== SYSTEM_CANVAS,
|
|
3884
4055
|
autoHeight: source.autoHeight ?? false,
|
|
4056
|
+
// Vite serves the public folder under the frames' base.
|
|
4057
|
+
public: { base: "/_fs/", files: project.publicFiles() },
|
|
3885
4058
|
...extra
|
|
3886
4059
|
};
|
|
3887
4060
|
};
|
|
@@ -3892,7 +4065,7 @@ async function startDevServer(options) {
|
|
|
3892
4065
|
notFoundPage(res, "Screen not found", `No screen "${id2}" in "${canvas}". Check canvas.json.`);
|
|
3893
4066
|
return;
|
|
3894
4067
|
}
|
|
3895
|
-
if (!
|
|
4068
|
+
if (!fs22.existsSync(entries.entryFile(source))) entries.sync();
|
|
3896
4069
|
const entryUrl = `/${relToDesign(paths, entries.entryFile(source))}`;
|
|
3897
4070
|
const html = await vite.transformIndexHtml(url2.pathname + url2.search, moduleShell(frameHead(source), entryUrl));
|
|
3898
4071
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -3901,8 +4074,8 @@ async function startDevServer(options) {
|
|
|
3901
4074
|
};
|
|
3902
4075
|
const serveHtmlScreen = async (res, url2) => {
|
|
3903
4076
|
const rel = decodeURIComponent(url2.pathname.slice("/_fs/".length));
|
|
3904
|
-
const file =
|
|
3905
|
-
if (!isInside(paths.design, file) || !
|
|
4077
|
+
const file = path21.resolve(paths.design, rel);
|
|
4078
|
+
if (!isInside(paths.design, file) || !fs22.existsSync(file)) {
|
|
3906
4079
|
notFoundPage(res, "Screen not found", `No file .design/${rel}`);
|
|
3907
4080
|
return;
|
|
3908
4081
|
}
|
|
@@ -3912,13 +4085,13 @@ async function startDevServer(options) {
|
|
|
3912
4085
|
key: `${canvas}/${id2}`,
|
|
3913
4086
|
canvas,
|
|
3914
4087
|
id: id2,
|
|
3915
|
-
title:
|
|
4088
|
+
title: path21.basename(file),
|
|
3916
4089
|
file,
|
|
3917
4090
|
format: "html",
|
|
3918
4091
|
props: {},
|
|
3919
4092
|
system: true
|
|
3920
4093
|
};
|
|
3921
|
-
const raw =
|
|
4094
|
+
const raw = fs22.readFileSync(file, "utf8");
|
|
3922
4095
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
3923
4096
|
const html = injectIntoHtml(
|
|
3924
4097
|
raw,
|
|
@@ -3950,17 +4123,24 @@ async function startDevServer(options) {
|
|
|
3950
4123
|
return;
|
|
3951
4124
|
}
|
|
3952
4125
|
if (url2.pathname.startsWith("/_fs/")) {
|
|
4126
|
+
const rel = publicRel(url2.pathname.slice("/_fs/".length));
|
|
4127
|
+
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs22.existsSync(path21.join(publicDir, rel))) {
|
|
4128
|
+
notFoundPage(res, "Not found", url2.pathname);
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
3953
4131
|
vite.middlewares(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
3954
4132
|
return;
|
|
3955
4133
|
}
|
|
3956
4134
|
if (viewer) {
|
|
4135
|
+
const notViewer = () => {
|
|
4136
|
+
const rel = publicRel(url2.pathname.slice(1));
|
|
4137
|
+
if (servePublic && rel !== null && project.isPublicFile(rel))
|
|
4138
|
+
servePublic(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
4139
|
+
else if (path21.extname(url2.pathname)) notFoundPage(res, "Not found", url2.pathname);
|
|
4140
|
+
else serveViewerIndex(res);
|
|
4141
|
+
};
|
|
3957
4142
|
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(res);
|
|
3958
|
-
else
|
|
3959
|
-
viewer(
|
|
3960
|
-
req,
|
|
3961
|
-
res,
|
|
3962
|
-
() => path20.extname(url2.pathname) ? notFoundPage(res, "Not found", url2.pathname) : serveViewerIndex(res)
|
|
3963
|
-
);
|
|
4143
|
+
else viewer(req, res, notViewer);
|
|
3964
4144
|
} else notFoundPage(res, "Viewer is not built", "Run `pnpm build` in the foss-design package.");
|
|
3965
4145
|
} catch (error) {
|
|
3966
4146
|
fail(error);
|
|
@@ -3983,7 +4163,8 @@ async function startDevServer(options) {
|
|
|
3983
4163
|
}, 60);
|
|
3984
4164
|
};
|
|
3985
4165
|
vite.watcher.on("all", (event, file) => {
|
|
3986
|
-
|
|
4166
|
+
if (publicDir && isInside(publicDir, file)) project.invalidatePublic();
|
|
4167
|
+
const rel = toPosix(path21.relative(paths.design, file));
|
|
3987
4168
|
if (rel.startsWith("..") || rel.startsWith(".cache/") || rel.startsWith("node_modules/")) return;
|
|
3988
4169
|
if (rel === "design.json") {
|
|
3989
4170
|
queue("config", () => {
|
|
@@ -4091,15 +4272,15 @@ async function ensureServer(paths, options) {
|
|
|
4091
4272
|
if (stale)
|
|
4092
4273
|
print(dim(`Restarting the preview server: it runs foss-design ${live2.version}, this is ${packageVersion()}.`));
|
|
4093
4274
|
if (live2) await stopServer(paths);
|
|
4094
|
-
|
|
4095
|
-
const log =
|
|
4275
|
+
fs23.mkdirSync(paths.cache, { recursive: true });
|
|
4276
|
+
const log = fs23.openSync(logFile(paths), "w");
|
|
4096
4277
|
const args = [PKG.cli, "preview", "--foreground", "--root", paths.root];
|
|
4097
4278
|
if (options.port) args.push("--port", String(options.port));
|
|
4098
4279
|
const child = spawn(process.execPath, args, { detached: true, stdio: ["ignore", log, log], cwd: paths.root });
|
|
4099
4280
|
child.unref();
|
|
4100
4281
|
const state = await waitForServer(paths, 4e4);
|
|
4101
4282
|
if (!state) {
|
|
4102
|
-
const tail =
|
|
4283
|
+
const tail = fs23.existsSync(logFile(paths)) ? fs23.readFileSync(logFile(paths), "utf8").split("\n").slice(-25) : [];
|
|
4103
4284
|
throw new CliError(`The preview server did not start.
|
|
4104
4285
|
${tail.join("\n")}`);
|
|
4105
4286
|
}
|
|
@@ -4277,9 +4458,9 @@ function printLinkIssues(issues, project) {
|
|
|
4277
4458
|
}
|
|
4278
4459
|
|
|
4279
4460
|
// src/cli/commands/cloud.ts
|
|
4280
|
-
import
|
|
4461
|
+
import fs24 from "fs";
|
|
4281
4462
|
import os3 from "os";
|
|
4282
|
-
import
|
|
4463
|
+
import path22 from "path";
|
|
4283
4464
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4284
4465
|
async function runLogin() {
|
|
4285
4466
|
const host = cloudHost();
|
|
@@ -4380,7 +4561,7 @@ async function runLink(paths, wanted, options) {
|
|
|
4380
4561
|
print(`${dim("Already linked to")} ${project.name} ${dim(`(${project.id}, ${project.role})`)}`);
|
|
4381
4562
|
} else {
|
|
4382
4563
|
writeLink(paths, { host, project: project.id, units: {}, conflicts: {} });
|
|
4383
|
-
|
|
4564
|
+
fs24.rmSync(path22.join(cloudCache(paths), "incoming"), { recursive: true, force: true });
|
|
4384
4565
|
const replaced = current ? dim(` (replacing the link to ${current.project})`) : "";
|
|
4385
4566
|
print(`${green("linked")} .design \u2192 ${bold(project.name)} ${dim(`(${project.id}, ${project.role})`)}${replaced}`);
|
|
4386
4567
|
}
|
|
@@ -4405,8 +4586,8 @@ async function runLink(paths, wanted, options) {
|
|
|
4405
4586
|
}
|
|
4406
4587
|
|
|
4407
4588
|
// src/cli/commands/scaffold.ts
|
|
4408
|
-
import
|
|
4409
|
-
import
|
|
4589
|
+
import fs25 from "fs";
|
|
4590
|
+
import path23 from "path";
|
|
4410
4591
|
|
|
4411
4592
|
// src/cli/templates.ts
|
|
4412
4593
|
var TOKENS_CSS = `/*
|
|
@@ -4617,40 +4798,40 @@ function mainScreen(canvasId) {
|
|
|
4617
4798
|
|
|
4618
4799
|
// src/cli/commands/scaffold.ts
|
|
4619
4800
|
function write(file, content, root) {
|
|
4620
|
-
if (
|
|
4621
|
-
print(` ${dim("exists ")} ${
|
|
4801
|
+
if (fs25.existsSync(file)) {
|
|
4802
|
+
print(` ${dim("exists ")} ${path23.relative(root, file)}`);
|
|
4622
4803
|
return false;
|
|
4623
4804
|
}
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
print(` ${green("created")} ${
|
|
4805
|
+
fs25.mkdirSync(path23.dirname(file), { recursive: true });
|
|
4806
|
+
fs25.writeFileSync(file, content);
|
|
4807
|
+
print(` ${green("created")} ${path23.relative(root, file)}`);
|
|
4627
4808
|
return true;
|
|
4628
4809
|
}
|
|
4629
4810
|
function ensureGitignored(root) {
|
|
4630
|
-
const file =
|
|
4631
|
-
const current =
|
|
4811
|
+
const file = path23.join(root, ".gitignore");
|
|
4812
|
+
const current = fs25.existsSync(file) ? fs25.readFileSync(file, "utf8") : "";
|
|
4632
4813
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
4633
4814
|
if (covered) return "present";
|
|
4634
4815
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
4635
|
-
|
|
4816
|
+
fs25.writeFileSync(file, `${current}${prefix}${current ? "\n" : ""}# Design canvases (foss-design)
|
|
4636
4817
|
.design/
|
|
4637
4818
|
`);
|
|
4638
4819
|
return "added";
|
|
4639
4820
|
}
|
|
4640
4821
|
function projectName(root) {
|
|
4641
4822
|
try {
|
|
4642
|
-
const manifest = JSON.parse(
|
|
4823
|
+
const manifest = JSON.parse(fs25.readFileSync(path23.join(root, "package.json"), "utf8"));
|
|
4643
4824
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
4644
4825
|
} catch {
|
|
4645
4826
|
}
|
|
4646
|
-
return
|
|
4827
|
+
return path23.basename(root);
|
|
4647
4828
|
}
|
|
4648
4829
|
function runInit(root, options) {
|
|
4649
4830
|
const paths = designPaths(root);
|
|
4650
4831
|
print(`Setting up ${DESIGN_DIR} in ${root}`);
|
|
4651
4832
|
write(paths.config, `${JSON.stringify({ name: options.name ?? projectName(root) }, null, 2)}
|
|
4652
4833
|
`, root);
|
|
4653
|
-
|
|
4834
|
+
fs25.mkdirSync(paths.canvases, { recursive: true });
|
|
4654
4835
|
if (options.gitignore) {
|
|
4655
4836
|
const result = ensureGitignored(root);
|
|
4656
4837
|
print(` ${result === "added" ? green("ignored") : dim("ignored")} .design/ in .gitignore`);
|
|
@@ -4662,44 +4843,44 @@ function runSystemInit(root, options) {
|
|
|
4662
4843
|
const paths = designPaths(root);
|
|
4663
4844
|
let base = projectName(root);
|
|
4664
4845
|
try {
|
|
4665
|
-
base = JSON.parse(
|
|
4846
|
+
base = JSON.parse(fs25.readFileSync(paths.config, "utf8")).name || base;
|
|
4666
4847
|
} catch {
|
|
4667
4848
|
}
|
|
4668
4849
|
const name = options.name ?? `${base} design system`;
|
|
4669
|
-
print(`Scaffolding the design system in ${
|
|
4670
|
-
write(
|
|
4850
|
+
print(`Scaffolding the design system in ${path23.relative(root, paths.system)}`);
|
|
4851
|
+
write(path23.join(paths.system, "system.json"), `${JSON.stringify({ name }, null, 2)}
|
|
4671
4852
|
`, root);
|
|
4672
|
-
write(
|
|
4853
|
+
write(path23.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
4673
4854
|
if (!options.empty) {
|
|
4674
|
-
write(
|
|
4675
|
-
write(
|
|
4676
|
-
write(
|
|
4855
|
+
write(path23.join(paths.system, "guidelines", "01-overview.md"), OVERVIEW_MD, root);
|
|
4856
|
+
write(path23.join(paths.system, "components", "button.tsx"), BUTTON_TSX, root);
|
|
4857
|
+
write(path23.join(paths.system, "specimens", "button.tsx"), BUTTON_SPECIMEN_TSX, root);
|
|
4677
4858
|
}
|
|
4678
4859
|
}
|
|
4679
4860
|
function runNew(root, id2, options) {
|
|
4680
4861
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
4681
4862
|
const paths = designPaths(root);
|
|
4682
|
-
const dir =
|
|
4683
|
-
if (
|
|
4863
|
+
const dir = path23.join(paths.canvases, id2);
|
|
4864
|
+
if (fs25.existsSync(path23.join(dir, "canvas.json"))) throw new CliError(`Canvas "${id2}" already exists in ${dir}`);
|
|
4684
4865
|
print(`Creating canvas "${id2}"`);
|
|
4685
4866
|
const title = options.title ?? titleize(id2);
|
|
4686
4867
|
if (options.empty) {
|
|
4687
4868
|
write(
|
|
4688
|
-
|
|
4869
|
+
path23.join(dir, "canvas.json"),
|
|
4689
4870
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
4690
4871
|
`,
|
|
4691
4872
|
root
|
|
4692
4873
|
);
|
|
4693
|
-
|
|
4874
|
+
fs25.mkdirSync(path23.join(dir, "screens"), { recursive: true });
|
|
4694
4875
|
} else {
|
|
4695
|
-
write(
|
|
4696
|
-
write(
|
|
4876
|
+
write(path23.join(dir, "canvas.json"), canvasJson(title), root);
|
|
4877
|
+
write(path23.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
4697
4878
|
}
|
|
4698
4879
|
}
|
|
4699
4880
|
|
|
4700
4881
|
// src/cli/commands/shot.ts
|
|
4701
|
-
import
|
|
4702
|
-
import
|
|
4882
|
+
import fs26 from "fs";
|
|
4883
|
+
import path24 from "path";
|
|
4703
4884
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
4704
4885
|
async function runShot(paths, target, options) {
|
|
4705
4886
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -4715,15 +4896,15 @@ async function runShot(paths, target, options) {
|
|
|
4715
4896
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
4716
4897
|
}
|
|
4717
4898
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
4718
|
-
const outBase =
|
|
4719
|
-
const outDir =
|
|
4720
|
-
|
|
4899
|
+
const outBase = path24.resolve(options.out ?? paths.shots);
|
|
4900
|
+
const outDir = path24.join(outBase, canvasId);
|
|
4901
|
+
fs26.mkdirSync(outDir, { recursive: true });
|
|
4721
4902
|
const base = await serverBase(paths);
|
|
4722
4903
|
const browser = await launchChrome();
|
|
4723
4904
|
let failed = false;
|
|
4724
4905
|
try {
|
|
4725
4906
|
if (options.overview) {
|
|
4726
|
-
if (!
|
|
4907
|
+
if (!fs26.existsSync(PKG.viewer)) throw new CliError("The viewer is not built, so there is no canvas to shoot.");
|
|
4727
4908
|
const pages = options.page ? [options.page] : screenId ? doc.pages.filter((page) => page.sections.some((section) => section.items.some((item) => item.id === screenId))).map((page) => page.id) : doc.pages.map((page) => page.id);
|
|
4728
4909
|
for (const pageId of pages) {
|
|
4729
4910
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1200 } });
|
|
@@ -4739,7 +4920,7 @@ async function runShot(paths, target, options) {
|
|
|
4739
4920
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
4740
4921
|
}
|
|
4741
4922
|
await page.waitForTimeout(400);
|
|
4742
|
-
const file =
|
|
4923
|
+
const file = path24.join(outDir, `${pageId}.overview.png`);
|
|
4743
4924
|
await page.screenshot({ path: file });
|
|
4744
4925
|
await page.close();
|
|
4745
4926
|
print(file);
|
|
@@ -4768,7 +4949,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
4768
4949
|
let failed = false;
|
|
4769
4950
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
4770
4951
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
4771
|
-
const file =
|
|
4952
|
+
const file = path24.join(outDir, `${job.id}.${job.theme}.png`);
|
|
4772
4953
|
try {
|
|
4773
4954
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
4774
4955
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -4800,8 +4981,8 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4800
4981
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
4801
4982
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
4802
4983
|
}
|
|
4803
|
-
const outDir =
|
|
4804
|
-
|
|
4984
|
+
const outDir = path24.join(path24.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
4985
|
+
fs26.mkdirSync(outDir, { recursive: true });
|
|
4805
4986
|
const base = await serverBase(paths);
|
|
4806
4987
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
4807
4988
|
const browser = await launchChrome();
|
|
@@ -4813,7 +4994,7 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4813
4994
|
}
|
|
4814
4995
|
|
|
4815
4996
|
// src/cli/commands/cloud-status.ts
|
|
4816
|
-
import
|
|
4997
|
+
import path25 from "path";
|
|
4817
4998
|
var STATE_TEXT = {
|
|
4818
4999
|
in_sync: green,
|
|
4819
5000
|
local_changes: yellow,
|
|
@@ -4836,7 +5017,7 @@ function detail(paths, status) {
|
|
|
4836
5017
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
4837
5018
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
4838
5019
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
4839
|
-
if (status.conflictRev !== void 0) parts.push(`merge ${
|
|
5020
|
+
if (status.conflictRev !== void 0) parts.push(`merge ${path25.relative(paths.root, incomingDir(paths, status.key))}`);
|
|
4840
5021
|
return parts.join(" \xB7 ");
|
|
4841
5022
|
}
|
|
4842
5023
|
async function printCloudStatus(paths, label) {
|
|
@@ -5010,7 +5191,7 @@ ${bold("Environment")}
|
|
|
5010
5191
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
5011
5192
|
`;
|
|
5012
5193
|
function projectPaths(rootOption, requireDesign = true) {
|
|
5013
|
-
const found = rootOption ?
|
|
5194
|
+
const found = rootOption ? path26.resolve(rootOption) : findProjectRoot();
|
|
5014
5195
|
if (!found) {
|
|
5015
5196
|
if (!requireDesign) return designPaths(process.cwd());
|
|
5016
5197
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -5056,7 +5237,7 @@ async function main(argv) {
|
|
|
5056
5237
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
5057
5238
|
switch (command) {
|
|
5058
5239
|
case "init":
|
|
5059
|
-
return runInit(values.root ?
|
|
5240
|
+
return runInit(values.root ? path26.resolve(values.root) : process.cwd(), {
|
|
5060
5241
|
name: values.name,
|
|
5061
5242
|
gitignore: !values["no-gitignore"]
|
|
5062
5243
|
});
|