foss-design 0.9.9 → 0.9.11
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 +622 -382
- package/dist/runtime/index.js +27 -0
- package/dist/viewer/assets/index-Cy6JlR85.css +2 -0
- package/dist/viewer/assets/index-mvGp1q3N.js +159 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +7355 -6819
- package/dist/viewer-lib/styles.css +132 -12
- package/dist/viewer-lib/types/src/shared/types.d.ts +17 -0
- package/dist/viewer-lib/types/viewer/src/app.d.ts +8 -1
- package/dist/viewer-lib/types/viewer/src/components/corner-plate.d.ts +21 -0
- package/dist/viewer-lib/types/viewer/src/components/error-boundary.d.ts +5 -0
- package/dist/viewer-lib/types/viewer/src/components/markdown.d.ts +4 -1
- package/dist/viewer-lib/types/viewer/src/lib/frames.d.ts +10 -2
- package/dist/viewer-lib/types/viewer/src/lib/messages.d.ts +12 -0
- package/dist/viewer-lib/types/viewer/src/lib/source.d.ts +8 -2
- package/dist/viewer-lib/types/viewer/src/lib/viewer.d.ts +14 -0
- package/dist/viewer-lib/types/viewer/src/library.d.ts +4 -1
- package/dist/viewer-lib/types/viewer/src/pages/canvas/full-page.d.ts +1 -1
- package/dist/viewer-lib/types/viewer/src/ui/copy-button.d.ts +7 -0
- package/dist/viewer-lib/types/viewer/src/ui/dialog.d.ts +7 -2
- package/package.json +1 -1
- package/dist/viewer/assets/index-6h0AwUgn.js +0 -158
- package/dist/viewer/assets/index-DnxkAHbH.css +0 -2
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import fs38 from "fs";
|
|
6
|
+
import path37 from "path";
|
|
7
7
|
import { parseArgs } from "util";
|
|
8
8
|
|
|
9
9
|
// src/core/paths.ts
|
|
@@ -136,12 +136,12 @@ function resolveFrom(anchor, request) {
|
|
|
136
136
|
|
|
137
137
|
// src/cli/commands/build.ts
|
|
138
138
|
import { spawnSync } from "child_process";
|
|
139
|
-
import
|
|
140
|
-
import
|
|
139
|
+
import fs14 from "fs";
|
|
140
|
+
import path14 from "path";
|
|
141
141
|
|
|
142
142
|
// src/build/static.ts
|
|
143
|
-
import
|
|
144
|
-
import
|
|
143
|
+
import fs13 from "fs";
|
|
144
|
+
import path13 from "path";
|
|
145
145
|
import { build } from "vite";
|
|
146
146
|
|
|
147
147
|
// src/core/png.ts
|
|
@@ -297,8 +297,8 @@ function ensureThumb(file) {
|
|
|
297
297
|
const thumb = thumbPath(file);
|
|
298
298
|
try {
|
|
299
299
|
const source = fs2.statSync(file);
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
300
|
+
const existing2 = fs2.statSync(thumb, { throwIfNoEntry: false });
|
|
301
|
+
if (existing2 && existing2.mtimeMs >= source.mtimeMs) return thumb;
|
|
302
302
|
const small = thumbnail(fs2.readFileSync(file));
|
|
303
303
|
if (!small) return file;
|
|
304
304
|
fs2.writeFileSync(thumb, small);
|
|
@@ -348,8 +348,8 @@ function routeError(pattern) {
|
|
|
348
348
|
}
|
|
349
349
|
return null;
|
|
350
350
|
}
|
|
351
|
-
function split(
|
|
352
|
-
return
|
|
351
|
+
function split(path38) {
|
|
352
|
+
return path38.split("/").filter(Boolean);
|
|
353
353
|
}
|
|
354
354
|
function compileRoute(pattern) {
|
|
355
355
|
return {
|
|
@@ -368,8 +368,8 @@ function decode2(part) {
|
|
|
368
368
|
return part;
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
-
function scoreRoute(route2,
|
|
372
|
-
const parts = split(
|
|
371
|
+
function scoreRoute(route2, path38) {
|
|
372
|
+
const parts = split(path38.split(/[?#]/)[0] ?? "").map(decode2);
|
|
373
373
|
const walk2 = (s, p) => {
|
|
374
374
|
if (s === route2.segments.length) return p === parts.length ? 0 : null;
|
|
375
375
|
const segment = route2.segments[s];
|
|
@@ -387,11 +387,11 @@ function scoreRoute(route2, path37) {
|
|
|
387
387
|
};
|
|
388
388
|
return walk2(0, 0);
|
|
389
389
|
}
|
|
390
|
-
function matchRoute(entries,
|
|
390
|
+
function matchRoute(entries, path38) {
|
|
391
391
|
let best = null;
|
|
392
392
|
for (const entry of entries) {
|
|
393
393
|
for (const route2 of entry.routes) {
|
|
394
|
-
const score = scoreRoute(route2,
|
|
394
|
+
const score = scoreRoute(route2, path38);
|
|
395
395
|
if (score !== null && (!best || score > best.score)) best = { score, value: entry.value };
|
|
396
396
|
}
|
|
397
397
|
}
|
|
@@ -518,9 +518,9 @@ var CanvasSchema = z.strictObject({
|
|
|
518
518
|
pages: z.array(z.unknown()).min(1, "add at least one page")
|
|
519
519
|
});
|
|
520
520
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
521
|
-
function formatPath(base,
|
|
521
|
+
function formatPath(base, path38) {
|
|
522
522
|
let out = base;
|
|
523
|
-
for (const part of
|
|
523
|
+
for (const part of path38) out += typeof part === "number" ? `[${part}]` : out ? `.${String(part)}` : String(part);
|
|
524
524
|
return out;
|
|
525
525
|
}
|
|
526
526
|
function describeZodError(error, base = "") {
|
|
@@ -1269,6 +1269,16 @@ var SPECIMEN_EXT = /* @__PURE__ */ new Set([".tsx", ".jsx", ".html"]);
|
|
|
1269
1269
|
var COMPONENT_EXT = [".tsx", ".jsx", ".ts", ".js", ".vue", ".svelte", ".html"];
|
|
1270
1270
|
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".svg", ".ico"]);
|
|
1271
1271
|
var FONT_EXT = /* @__PURE__ */ new Set([".woff", ".woff2", ".ttf", ".otf"]);
|
|
1272
|
+
function isShowableSource(paths, file) {
|
|
1273
|
+
if (!isInside(paths.root, file)) return false;
|
|
1274
|
+
const parts = path7.relative(paths.root, file).split(path7.sep);
|
|
1275
|
+
if (parts.some((part, index) => part.startsWith(".") && !(index === 0 && part === DESIGN_DIR))) return false;
|
|
1276
|
+
try {
|
|
1277
|
+
return fs7.statSync(file).isFile() && isInside(fs7.realpathSync(paths.root), fs7.realpathSync(file));
|
|
1278
|
+
} catch {
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1272
1282
|
function listFiles(dir, deep = false) {
|
|
1273
1283
|
if (!fs7.existsSync(dir)) return [];
|
|
1274
1284
|
const out = [];
|
|
@@ -1343,8 +1353,14 @@ function loadSystem(paths, urls) {
|
|
|
1343
1353
|
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) => path7.resolve(path7.dirname(file), value));
|
|
1344
1354
|
for (const source of sources) {
|
|
1345
1355
|
if (!fs7.existsSync(source)) report("warning", file, `@source not found: ${path7.relative(paths.system, source)}`);
|
|
1356
|
+
else if (!isShowableSource(paths, source))
|
|
1357
|
+
report(
|
|
1358
|
+
"warning",
|
|
1359
|
+
file,
|
|
1360
|
+
`@source not shown (outside the project, or a dot-file): ${path7.relative(paths.system, source)}`
|
|
1361
|
+
);
|
|
1346
1362
|
}
|
|
1347
|
-
const found = sources.filter((source) =>
|
|
1363
|
+
const found = sources.filter((source) => isShowableSource(paths, source));
|
|
1348
1364
|
const title = tags.title?.[0] || titleize(stem);
|
|
1349
1365
|
const format = ext === ".html" ? "html" : "module";
|
|
1350
1366
|
components.push({
|
|
@@ -1354,7 +1370,7 @@ function loadSystem(paths, urls) {
|
|
|
1354
1370
|
description: tags.description?.[0] || void 0,
|
|
1355
1371
|
status: tags.status?.[0] || void 0,
|
|
1356
1372
|
specimen: relToRoot(paths, file),
|
|
1357
|
-
sources: (found.length ? found : guessSources(componentsDir, stem)).map((source) => relToRoot(paths, source)),
|
|
1373
|
+
sources: (found.length ? found : guessSources(componentsDir, stem)).filter((source) => isShowableSource(paths, source)).map((source) => relToRoot(paths, source)),
|
|
1358
1374
|
url: format === "module" ? urls.screen(SYSTEM_CANVAS, id2) : urls.html(relToDesign(paths, file), SYSTEM_CANVAS, id2)
|
|
1359
1375
|
});
|
|
1360
1376
|
specimens.push({
|
|
@@ -1957,10 +1973,156 @@ function linkShippedPackages(paths, appDir = null) {
|
|
|
1957
1973
|
|
|
1958
1974
|
// src/server/vite.ts
|
|
1959
1975
|
import { createHash as createHash3 } from "crypto";
|
|
1960
|
-
import
|
|
1961
|
-
import
|
|
1976
|
+
import fs12 from "fs";
|
|
1977
|
+
import path12 from "path";
|
|
1962
1978
|
import tailwindcss from "@tailwindcss/vite";
|
|
1963
1979
|
import react from "@vitejs/plugin-react";
|
|
1980
|
+
|
|
1981
|
+
// src/server/css-guard.ts
|
|
1982
|
+
import fs11 from "fs";
|
|
1983
|
+
import { createRequire as createRequire2 } from "module";
|
|
1984
|
+
import path11 from "path";
|
|
1985
|
+
import { createIdResolver } from "vite";
|
|
1986
|
+
var LOADS_CODE = /@(plugin|config)\b([^;{}]*)|\buse\(\s*(["'])(.*?)\3/gi;
|
|
1987
|
+
var IMPORTS = /@(import|reference|require)\b([^;{}]*)/gi;
|
|
1988
|
+
var STYLESHEET = /\.(css|less|styl|stylus)$/i;
|
|
1989
|
+
var UNTOUCHED = /[?&](?:worker|sharedworker|raw|url)\b/;
|
|
1990
|
+
var JS_EXT = ["", ".js", ".mjs", ".cjs", ".ts", ".mts", ".cts", "/index.js", "/index.mjs", "/index.cjs", "/index.ts"];
|
|
1991
|
+
var STYLE_EXT = ["", ".css", ".less", ".styl", "/index.css", "/index.styl"];
|
|
1992
|
+
var idFile = (id2) => id2.replace(/[?#].*$/, "");
|
|
1993
|
+
function isStylesheetId(id2) {
|
|
1994
|
+
if (UNTOUCHED.test(id2) || id2.startsWith("\0") || id2.includes("/.vite/")) return false;
|
|
1995
|
+
return STYLESHEET.test(idFile(id2)) || /&lang\.(css|less|styl)/.test(id2) || /[?&]index=\d+\.css$/.test(id2);
|
|
1996
|
+
}
|
|
1997
|
+
var stripComments = (text2) => text2.replace(/\/\*[\s\S]*?(\*\/|$)/g, "");
|
|
1998
|
+
function targets(params) {
|
|
1999
|
+
const out = /* @__PURE__ */ new Set();
|
|
2000
|
+
for (const text2 of [params.trim(), stripComments(params).trim()]) {
|
|
2001
|
+
if (!text2) continue;
|
|
2002
|
+
out.add(text2.slice(1, -1));
|
|
2003
|
+
out.add(text2.slice(1, -1).trim());
|
|
2004
|
+
const quoted = /(["'])(.*?)\1/.exec(text2)?.[2];
|
|
2005
|
+
if (quoted) out.add(quoted);
|
|
2006
|
+
const url = /^url\(\s*(["']?)(.*?)\1\s*\)/.exec(text2)?.[2];
|
|
2007
|
+
if (url) out.add(url);
|
|
2008
|
+
const bare = /^[^\s"'()]+/.exec(text2)?.[0];
|
|
2009
|
+
if (bare) out.add(bare);
|
|
2010
|
+
}
|
|
2011
|
+
out.delete("");
|
|
2012
|
+
return [...out];
|
|
2013
|
+
}
|
|
2014
|
+
var isPathLike = (spec) => spec.startsWith(".") || path11.isAbsolute(spec);
|
|
2015
|
+
function existing(base, exts) {
|
|
2016
|
+
for (const ext of exts) {
|
|
2017
|
+
const file = base + ext;
|
|
2018
|
+
try {
|
|
2019
|
+
if (fs11.statSync(file).isFile()) return fs11.realpathSync(file);
|
|
2020
|
+
} catch {
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
function designCodeGuard(design) {
|
|
2026
|
+
let config = null;
|
|
2027
|
+
const pulled = (file) => isInside(design, file) && !toPosix(file).split("/").includes("node_modules");
|
|
2028
|
+
const shown = (file) => toPosix(path11.relative(path11.dirname(design), file));
|
|
2029
|
+
const resolvers = (environment) => {
|
|
2030
|
+
const resolveWith = (options, accept) => {
|
|
2031
|
+
const resolveId = config ? createIdResolver(config, options) : null;
|
|
2032
|
+
return async (spec, importer) => {
|
|
2033
|
+
if (!resolveId) return null;
|
|
2034
|
+
const from = path11.join(path11.dirname(importer), "__placeholder__.ts");
|
|
2035
|
+
for (const aliasOnly of [true, false]) {
|
|
2036
|
+
try {
|
|
2037
|
+
let found = await resolveId(environment, spec, from, aliasOnly);
|
|
2038
|
+
if (found && found !== spec) {
|
|
2039
|
+
if (found.startsWith(".")) found = path11.resolve(path11.dirname(importer), found);
|
|
2040
|
+
found = idFile(found);
|
|
2041
|
+
if (path11.isAbsolute(found) && accept(found)) return found;
|
|
2042
|
+
}
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return null;
|
|
2047
|
+
};
|
|
2048
|
+
};
|
|
2049
|
+
const style = resolveWith(
|
|
2050
|
+
{
|
|
2051
|
+
...config?.resolve,
|
|
2052
|
+
extensions: [".css"],
|
|
2053
|
+
mainFields: ["style"],
|
|
2054
|
+
conditions: ["style", "development|production"],
|
|
2055
|
+
tryIndex: false,
|
|
2056
|
+
preferRelative: true
|
|
2057
|
+
},
|
|
2058
|
+
(file) => file.endsWith(".css")
|
|
2059
|
+
);
|
|
2060
|
+
const script = resolveWith({ ...config?.resolve }, (file) => !file.endsWith(".css"));
|
|
2061
|
+
return { style, script };
|
|
2062
|
+
};
|
|
2063
|
+
const candidates = async (spec, importer, resolve, exts) => {
|
|
2064
|
+
const found = /* @__PURE__ */ new Set();
|
|
2065
|
+
const local = path11.resolve(path11.dirname(importer), spec);
|
|
2066
|
+
if (isPathLike(spec)) found.add(local);
|
|
2067
|
+
const file = existing(local, exts);
|
|
2068
|
+
if (file) found.add(file);
|
|
2069
|
+
const viaVite = await resolve(spec, importer);
|
|
2070
|
+
if (viaVite) found.add(viaVite);
|
|
2071
|
+
if (!isPathLike(spec)) {
|
|
2072
|
+
try {
|
|
2073
|
+
found.add(createRequire2(importer).resolve(spec));
|
|
2074
|
+
} catch {
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return [...found];
|
|
2078
|
+
};
|
|
2079
|
+
const check = async (environment, file, code, seen) => {
|
|
2080
|
+
const { style, script } = resolvers(environment);
|
|
2081
|
+
for (const match of code.matchAll(LOADS_CODE)) {
|
|
2082
|
+
const directive = match[1] ? `@${match[1]}` : "use()";
|
|
2083
|
+
const specs = match[1] ? targets(match[2] ?? "") : [match[4] ?? ""];
|
|
2084
|
+
for (const spec of specs) {
|
|
2085
|
+
for (const target of await candidates(spec, file, script, JS_EXT)) {
|
|
2086
|
+
if (!pulled(target)) continue;
|
|
2087
|
+
throw new Error(
|
|
2088
|
+
`${shown(file)}: \`${match[0].trim()}\` loads ${shown(target)}, which is refused. ${directive} runs its code in Node on this machine, and code under .design can come from a teammate through \`design pull\`. Put the plugin in the app\u2019s own source (outside .design) or name an installed package.`
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
for (const match of code.matchAll(IMPORTS)) {
|
|
2094
|
+
for (const spec of targets(match[2] ?? "")) {
|
|
2095
|
+
if (/^(data:|https?:|\/\/)/i.test(spec)) continue;
|
|
2096
|
+
for (const target of await candidates(spec, file, style, STYLE_EXT)) {
|
|
2097
|
+
if (seen.has(target) || !STYLESHEET.test(target) || toPosix(target).includes("/node_modules/")) continue;
|
|
2098
|
+
seen.add(target);
|
|
2099
|
+
let text2;
|
|
2100
|
+
try {
|
|
2101
|
+
text2 = fs11.readFileSync(target, "utf8");
|
|
2102
|
+
} catch {
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2105
|
+
await check(environment, target, text2, seen);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
return {
|
|
2111
|
+
name: "design:code-guard",
|
|
2112
|
+
enforce: "pre",
|
|
2113
|
+
configResolved(resolved) {
|
|
2114
|
+
config = resolved;
|
|
2115
|
+
},
|
|
2116
|
+
async transform(code, id2) {
|
|
2117
|
+
if (!isStylesheetId(id2)) return null;
|
|
2118
|
+
const file = path11.resolve(idFile(id2));
|
|
2119
|
+
await check(this.environment, file, code, /* @__PURE__ */ new Set([file]));
|
|
2120
|
+
return null;
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/server/vite.ts
|
|
1964
2126
|
var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1965
2127
|
function aliases(project) {
|
|
1966
2128
|
const { paths } = project;
|
|
@@ -1971,7 +2133,7 @@ function aliases(project) {
|
|
|
1971
2133
|
{ find: /^@system\//, replacement: `${toPosix(paths.system)}/` }
|
|
1972
2134
|
];
|
|
1973
2135
|
for (const [key, target] of Object.entries(project.config().config.alias ?? {})) {
|
|
1974
|
-
const replacement = toPosix(
|
|
2136
|
+
const replacement = toPosix(path12.resolve(paths.root, target));
|
|
1975
2137
|
list.push(
|
|
1976
2138
|
key.endsWith("/") ? { find: new RegExp(`^${escapeRegExp(key)}`), replacement: `${replacement.replace(/\/$/, "")}/` } : { find: new RegExp(`^${escapeRegExp(key)}(?=/|$)`), replacement }
|
|
1977
2139
|
);
|
|
@@ -1979,23 +2141,23 @@ function aliases(project) {
|
|
|
1979
2141
|
return list;
|
|
1980
2142
|
}
|
|
1981
2143
|
function dropStaleDepCache(paths, appDir = null) {
|
|
1982
|
-
const dir =
|
|
1983
|
-
const stamp =
|
|
2144
|
+
const dir = path12.join(paths.cache, "vite");
|
|
2145
|
+
const stamp = path12.join(paths.cache, "vite-runtime");
|
|
1984
2146
|
let runtime = "";
|
|
1985
2147
|
try {
|
|
1986
|
-
runtime = createHash3("sha256").update(
|
|
2148
|
+
runtime = createHash3("sha256").update(fs12.readFileSync(PKG.runtime)).digest("hex");
|
|
1987
2149
|
} catch {
|
|
1988
2150
|
}
|
|
1989
2151
|
if (appDir) runtime += ` ${appDir}`;
|
|
1990
2152
|
let seen = "";
|
|
1991
2153
|
try {
|
|
1992
|
-
seen =
|
|
2154
|
+
seen = fs12.readFileSync(stamp, "utf8");
|
|
1993
2155
|
} catch {
|
|
1994
2156
|
}
|
|
1995
2157
|
if (seen === runtime) return;
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2158
|
+
fs12.rmSync(dir, { recursive: true, force: true });
|
|
2159
|
+
fs12.mkdirSync(paths.cache, { recursive: true });
|
|
2160
|
+
fs12.writeFileSync(stamp, runtime);
|
|
1999
2161
|
}
|
|
2000
2162
|
function baseConfig(project) {
|
|
2001
2163
|
const { paths } = project;
|
|
@@ -2003,7 +2165,7 @@ function baseConfig(project) {
|
|
|
2003
2165
|
configFile: false,
|
|
2004
2166
|
envDir: false,
|
|
2005
2167
|
root: paths.design,
|
|
2006
|
-
cacheDir:
|
|
2168
|
+
cacheDir: path12.join(paths.cache, "vite"),
|
|
2007
2169
|
// design.json `public`: served under the frames' base, so HTML and CSS paths such as `/logo.png` resolve.
|
|
2008
2170
|
publicDir: project.publicDir() ?? false,
|
|
2009
2171
|
logLevel: "warn",
|
|
@@ -2013,7 +2175,12 @@ function baseConfig(project) {
|
|
|
2013
2175
|
// One React for screens, the runtime and project components: resolved from `.design`.
|
|
2014
2176
|
dedupe: ["react", "react-dom"]
|
|
2015
2177
|
},
|
|
2016
|
-
|
|
2178
|
+
// No PostCSS config is looked up (Vite would search from `.design` upward and load it in Node):
|
|
2179
|
+
// screens get Tailwind from the plugin, and the app's own config is for the app's build.
|
|
2180
|
+
css: { postcss: {} },
|
|
2181
|
+
// Pulled code runs in the browser only: the guard refuses stylesheets that would load code from
|
|
2182
|
+
// `.design` in Node (Tailwind's `@plugin` and `@config`), before Tailwind reads them.
|
|
2183
|
+
plugins: [react(), designCodeGuard(paths.design), tailwindcss()]
|
|
2017
2184
|
};
|
|
2018
2185
|
}
|
|
2019
2186
|
|
|
@@ -2023,7 +2190,7 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts",
|
|
|
2023
2190
|
var BUNDLED_REF = /<(script|link|img|source|video|audio|image|use|input)\b[^>]*>/gi;
|
|
2024
2191
|
var encodePath2 = (rel) => rel.split("/").map(encodeURIComponent).join("/");
|
|
2025
2192
|
function relUrl(fromDir, file) {
|
|
2026
|
-
const rel = toPosix(
|
|
2193
|
+
const rel = toPosix(path13.relative(fromDir, file));
|
|
2027
2194
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
2028
2195
|
}
|
|
2029
2196
|
function staticSnapshots(paths) {
|
|
@@ -2033,7 +2200,7 @@ function staticSnapshots(paths) {
|
|
|
2033
2200
|
let data = metaCache.get(canvas);
|
|
2034
2201
|
if (!data) {
|
|
2035
2202
|
try {
|
|
2036
|
-
data = JSON.parse(
|
|
2203
|
+
data = JSON.parse(fs13.readFileSync(path13.join(paths.snapshots, canvas, "meta.json"), "utf8"));
|
|
2037
2204
|
} catch {
|
|
2038
2205
|
}
|
|
2039
2206
|
data ??= {};
|
|
@@ -2047,8 +2214,8 @@ function staticSnapshots(paths) {
|
|
|
2047
2214
|
const urls = {};
|
|
2048
2215
|
const thumbs = {};
|
|
2049
2216
|
for (const theme2 of THEMES) {
|
|
2050
|
-
const file =
|
|
2051
|
-
if (!entry[theme2] || !
|
|
2217
|
+
const file = path13.join(paths.snapshots, canvas, `${id2}.${theme2}.png`);
|
|
2218
|
+
if (!entry[theme2] || !fs13.existsSync(file)) continue;
|
|
2052
2219
|
const rel = `_snap/${canvas}/${id2}.${theme2}.png`;
|
|
2053
2220
|
copies.set(file, rel);
|
|
2054
2221
|
urls[theme2] = `${encodePath2(rel)}?v=${entry[theme2]}`;
|
|
@@ -2072,29 +2239,29 @@ function rewriteRefs(html, originalDir, stagedDir) {
|
|
|
2072
2239
|
(whole, attr2, quote, value) => {
|
|
2073
2240
|
if (!value || /^(?:[a-z][a-z0-9+.-]*:|\/|#|\{)/i.test(value)) return whole;
|
|
2074
2241
|
const [pathname = "", suffix = ""] = /^([^?#]*)(.*)$/.exec(value).slice(1);
|
|
2075
|
-
const target =
|
|
2076
|
-
if (!
|
|
2242
|
+
const target = path13.resolve(originalDir, decodeURIComponent(pathname));
|
|
2243
|
+
if (!fs13.existsSync(target)) return whole;
|
|
2077
2244
|
return `${attr2}${quote}${relUrl(stagedDir, target)}${suffix}${quote}`;
|
|
2078
2245
|
}
|
|
2079
2246
|
);
|
|
2080
2247
|
});
|
|
2081
2248
|
}
|
|
2082
2249
|
function copyTree(from, to, filter) {
|
|
2083
|
-
if (!
|
|
2084
|
-
for (const entry of
|
|
2250
|
+
if (!fs13.existsSync(from)) return;
|
|
2251
|
+
for (const entry of fs13.readdirSync(from, { withFileTypes: true })) {
|
|
2085
2252
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2086
|
-
const source =
|
|
2087
|
-
const target =
|
|
2253
|
+
const source = path13.join(from, entry.name);
|
|
2254
|
+
const target = path13.join(to, entry.name);
|
|
2088
2255
|
if (entry.isDirectory()) copyTree(source, target, filter);
|
|
2089
|
-
else if (filter(source) && !
|
|
2090
|
-
|
|
2091
|
-
|
|
2256
|
+
else if (filter(source) && !fs13.existsSync(target)) {
|
|
2257
|
+
fs13.mkdirSync(path13.dirname(target), { recursive: true });
|
|
2258
|
+
fs13.copyFileSync(source, target);
|
|
2092
2259
|
}
|
|
2093
2260
|
}
|
|
2094
2261
|
}
|
|
2095
2262
|
function writeJson(file, value) {
|
|
2096
|
-
|
|
2097
|
-
|
|
2263
|
+
fs13.mkdirSync(path13.dirname(file), { recursive: true });
|
|
2264
|
+
fs13.writeFileSync(file, JSON.stringify(value));
|
|
2098
2265
|
}
|
|
2099
2266
|
async function buildSite(paths, out, options = {}) {
|
|
2100
2267
|
const snapshots = staticSnapshots(paths);
|
|
@@ -2112,18 +2279,18 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2112
2279
|
linkShippedPackages(paths, project.appDir());
|
|
2113
2280
|
const publicDir = project.publicDir();
|
|
2114
2281
|
const publicFiles2 = project.publicFiles();
|
|
2115
|
-
const staging =
|
|
2116
|
-
|
|
2282
|
+
const staging = path13.join(paths.cache, "build", "src");
|
|
2283
|
+
fs13.rmSync(staging, { recursive: true, force: true });
|
|
2117
2284
|
const fonts = loadedSystem?.doc.fonts ?? [];
|
|
2118
2285
|
const sources = [...system?.specimens ?? [], ...canvases.flatMap((canvas) => canvas.screens)];
|
|
2119
2286
|
const inputs = [];
|
|
2120
2287
|
const htmlScreens = [];
|
|
2121
2288
|
for (const source of sources) {
|
|
2122
|
-
if (!source.builtin && !
|
|
2123
|
-
const siteDir =
|
|
2124
|
-
const dir =
|
|
2125
|
-
const file =
|
|
2126
|
-
|
|
2289
|
+
if (!source.builtin && !fs13.existsSync(source.file)) continue;
|
|
2290
|
+
const siteDir = path13.join("_s", source.canvas, source.id);
|
|
2291
|
+
const dir = path13.join(staging, siteDir);
|
|
2292
|
+
const file = path13.join(dir, "index.html");
|
|
2293
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
2127
2294
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
2128
2295
|
const head = {
|
|
2129
2296
|
source,
|
|
@@ -2132,24 +2299,24 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2132
2299
|
snapshots: false,
|
|
2133
2300
|
autoHeight: source.autoHeight ?? false,
|
|
2134
2301
|
// The public folder lands at the site root, which frames reach through `../`.
|
|
2135
|
-
public: publicFiles2.length ? { base: `${toPosix(
|
|
2302
|
+
public: publicFiles2.length ? { base: `${toPosix(path13.relative(dir, staging))}/`, files: publicFiles2 } : void 0
|
|
2136
2303
|
};
|
|
2137
2304
|
if (source.format === "module") {
|
|
2138
|
-
|
|
2305
|
+
fs13.writeFileSync(file, moduleShell(head, relUrl(dir, entries.entryFile(source))));
|
|
2139
2306
|
} else {
|
|
2140
|
-
const raw = rewriteRefs(
|
|
2307
|
+
const raw = rewriteRefs(fs13.readFileSync(source.file, "utf8"), path13.dirname(source.file), dir);
|
|
2141
2308
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
2142
2309
|
const html = injectIntoHtml(raw, {
|
|
2143
2310
|
...head,
|
|
2144
2311
|
systemCss: withSystem ? relUrl(dir, entries.systemCssFile()) : void 0,
|
|
2145
|
-
boot: relUrl(dir,
|
|
2312
|
+
boot: relUrl(dir, path13.join(entries.dir, HTML_BOOT))
|
|
2146
2313
|
});
|
|
2147
|
-
|
|
2314
|
+
fs13.writeFileSync(file, html);
|
|
2148
2315
|
htmlScreens.push({ source, dir: siteDir });
|
|
2149
2316
|
}
|
|
2150
2317
|
inputs.push(file);
|
|
2151
2318
|
}
|
|
2152
|
-
|
|
2319
|
+
fs13.mkdirSync(out, { recursive: true });
|
|
2153
2320
|
const moduleMap = {};
|
|
2154
2321
|
if (inputs.length) {
|
|
2155
2322
|
const base = baseConfig(project);
|
|
@@ -2162,7 +2329,7 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2162
2329
|
generateBundle(_, bundle) {
|
|
2163
2330
|
for (const [file, chunk2] of Object.entries(bundle)) {
|
|
2164
2331
|
if (chunk2.type !== "chunk") continue;
|
|
2165
|
-
moduleMap[file] = (chunk2.moduleIds ?? []).map((id2) => id2.split("?")[0]).filter((id2) =>
|
|
2332
|
+
moduleMap[file] = (chunk2.moduleIds ?? []).map((id2) => id2.split("?")[0]).filter((id2) => path13.isAbsolute(id2) && !id2.includes(`${path13.sep}node_modules${path13.sep}`)).map((id2) => toPosix(path13.relative(paths.root, id2))).filter((rel) => !rel.startsWith(".."));
|
|
2166
2333
|
}
|
|
2167
2334
|
}
|
|
2168
2335
|
} : null
|
|
@@ -2183,15 +2350,15 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2183
2350
|
}
|
|
2184
2351
|
});
|
|
2185
2352
|
} else {
|
|
2186
|
-
|
|
2187
|
-
|
|
2353
|
+
fs13.rmSync(out, { recursive: true, force: true });
|
|
2354
|
+
fs13.mkdirSync(out, { recursive: true });
|
|
2188
2355
|
}
|
|
2189
|
-
if (options.moduleMap)
|
|
2356
|
+
if (options.moduleMap) fs13.writeFileSync(path13.join(out, "modules.json"), JSON.stringify(moduleMap));
|
|
2190
2357
|
for (const { source, dir } of htmlScreens) {
|
|
2191
2358
|
copyTree(
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
(file) => !SOURCE_EXT.has(
|
|
2359
|
+
path13.dirname(source.file),
|
|
2360
|
+
path13.join(out, dir),
|
|
2361
|
+
(file) => !SOURCE_EXT.has(path13.extname(file).toLowerCase())
|
|
2195
2362
|
);
|
|
2196
2363
|
}
|
|
2197
2364
|
const files = /* @__PURE__ */ new Set();
|
|
@@ -2207,36 +2374,36 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2207
2374
|
if (icon) files.add(icon.split("?")[0]);
|
|
2208
2375
|
for (const url of files) {
|
|
2209
2376
|
const rel = url.slice("_f/".length).split("/").map(decodeURIComponent).join("/");
|
|
2210
|
-
const from =
|
|
2211
|
-
if (!
|
|
2212
|
-
const to =
|
|
2213
|
-
|
|
2214
|
-
|
|
2377
|
+
const from = path13.join(paths.design, rel);
|
|
2378
|
+
if (!fs13.existsSync(from)) continue;
|
|
2379
|
+
const to = path13.join(out, "_f", rel);
|
|
2380
|
+
fs13.mkdirSync(path13.dirname(to), { recursive: true });
|
|
2381
|
+
fs13.copyFileSync(from, to);
|
|
2215
2382
|
}
|
|
2216
2383
|
const info = project.info(true);
|
|
2217
2384
|
const snapshotDirs = canvasIds.map((id2) => `_snap/${id2}/`);
|
|
2218
2385
|
for (const [from, rel] of snapshots.copies) {
|
|
2219
2386
|
if (!snapshotDirs.some((dir) => rel.startsWith(dir))) continue;
|
|
2220
|
-
const to =
|
|
2221
|
-
|
|
2222
|
-
|
|
2387
|
+
const to = path13.join(out, rel);
|
|
2388
|
+
fs13.mkdirSync(path13.dirname(to), { recursive: true });
|
|
2389
|
+
fs13.copyFileSync(from, to);
|
|
2223
2390
|
}
|
|
2224
2391
|
if (!system) info.system = null;
|
|
2225
2392
|
info.canvases = info.canvases.filter((canvas) => canvasIds.includes(canvas.id));
|
|
2226
|
-
writeJson(
|
|
2227
|
-
for (const canvas of canvases) writeJson(
|
|
2393
|
+
writeJson(path13.join(out, "api", "project.json"), info);
|
|
2394
|
+
for (const canvas of canvases) writeJson(path13.join(out, "api", "canvas", `${canvas.doc.id}.json`), canvas.doc);
|
|
2228
2395
|
if (system) {
|
|
2229
|
-
writeJson(
|
|
2396
|
+
writeJson(path13.join(out, "api", "system.json"), system.doc);
|
|
2230
2397
|
const texts = {};
|
|
2231
2398
|
for (const component of system.doc.components) {
|
|
2232
2399
|
for (const rel of [component.specimen, ...component.sources]) {
|
|
2233
|
-
const file =
|
|
2234
|
-
if (!(rel in texts) &&
|
|
2400
|
+
const file = path13.join(paths.root, rel);
|
|
2401
|
+
if (!(rel in texts) && fs13.existsSync(file)) texts[rel] = fs13.readFileSync(file, "utf8");
|
|
2235
2402
|
}
|
|
2236
2403
|
}
|
|
2237
|
-
writeJson(
|
|
2404
|
+
writeJson(path13.join(out, "api", "sources.json"), texts);
|
|
2238
2405
|
}
|
|
2239
|
-
|
|
2406
|
+
fs13.rmSync(staging, { recursive: true, force: true });
|
|
2240
2407
|
const errors = [
|
|
2241
2408
|
...only ? [] : project.config().issues,
|
|
2242
2409
|
...system?.doc.issues ?? [],
|
|
@@ -2245,21 +2412,21 @@ async function buildSite(paths, out, options = {}) {
|
|
|
2245
2412
|
const { absoluteAssets } = options.includeViewer === false ? { absoluteAssets: false } : copyViewer(PKG.viewer, out);
|
|
2246
2413
|
if (publicDir) {
|
|
2247
2414
|
for (const rel of publicFiles2) {
|
|
2248
|
-
const to =
|
|
2249
|
-
if (
|
|
2250
|
-
|
|
2251
|
-
|
|
2415
|
+
const to = path13.join(out, rel);
|
|
2416
|
+
if (fs13.existsSync(to)) continue;
|
|
2417
|
+
fs13.mkdirSync(path13.dirname(to), { recursive: true });
|
|
2418
|
+
fs13.copyFileSync(path13.join(publicDir, rel), to);
|
|
2252
2419
|
}
|
|
2253
2420
|
}
|
|
2254
2421
|
return { out, canvases: canvasIds, frames: inputs.length, errors, absoluteAssets };
|
|
2255
2422
|
}
|
|
2256
2423
|
function copyViewer(viewerDir, out) {
|
|
2257
2424
|
copyTree(viewerDir, out, () => true);
|
|
2258
|
-
const index =
|
|
2259
|
-
const html =
|
|
2425
|
+
const index = path13.join(out, "index.html");
|
|
2426
|
+
const html = fs13.readFileSync(index, "utf8");
|
|
2260
2427
|
const flag2 = "<script>window.__DESIGN_STATIC__=true</script>";
|
|
2261
2428
|
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
2262
|
-
|
|
2429
|
+
fs13.writeFileSync(
|
|
2263
2430
|
index,
|
|
2264
2431
|
head ? html.slice(0, head.index + head[0].length) + flag2 + html.slice(head.index + head[0].length) : flag2 + html
|
|
2265
2432
|
);
|
|
@@ -2341,8 +2508,8 @@ function progress() {
|
|
|
2341
2508
|
// src/cli/commands/build.ts
|
|
2342
2509
|
function pack(out) {
|
|
2343
2510
|
const archive = `${out.replace(/[\\/]+$/, "")}.tar.gz`;
|
|
2344
|
-
|
|
2345
|
-
const entries =
|
|
2511
|
+
fs14.rmSync(archive, { force: true });
|
|
2512
|
+
const entries = fs14.readdirSync(out);
|
|
2346
2513
|
const result = spawnSync("tar", ["-czf", archive, "-C", out, ...entries], {
|
|
2347
2514
|
stdio: ["ignore", "ignore", "pipe"],
|
|
2348
2515
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
@@ -2352,10 +2519,10 @@ function pack(out) {
|
|
|
2352
2519
|
return archive;
|
|
2353
2520
|
}
|
|
2354
2521
|
async function runBuild(paths, canvases, options) {
|
|
2355
|
-
if (!
|
|
2522
|
+
if (!fs14.existsSync(path14.join(PKG.viewer, "index.html"))) {
|
|
2356
2523
|
throw new CliError("The viewer is missing from this foss-design install (dist/viewer). Rebuild or reinstall it.");
|
|
2357
2524
|
}
|
|
2358
|
-
const out =
|
|
2525
|
+
const out = path14.resolve(options.out ?? path14.join(paths.cache, "site"));
|
|
2359
2526
|
if (out === paths.root || out === paths.design) throw new CliError(`Refusing to build into ${out}`);
|
|
2360
2527
|
print(dim(`Building ${canvases.length ? canvases.join(", ") : "every canvas"} and the design system\u2026`));
|
|
2361
2528
|
let result;
|
|
@@ -2380,8 +2547,8 @@ import os from "os";
|
|
|
2380
2547
|
|
|
2381
2548
|
// src/cloud/units.ts
|
|
2382
2549
|
import { createHash as createHash4 } from "crypto";
|
|
2383
|
-
import
|
|
2384
|
-
import
|
|
2550
|
+
import fs15 from "fs";
|
|
2551
|
+
import path15 from "path";
|
|
2385
2552
|
var SYSTEM_UNIT = "system";
|
|
2386
2553
|
var CANVAS_PREFIX = "canvas/";
|
|
2387
2554
|
var LOCAL_ONLY = /* @__PURE__ */ new Set(["cloud.json", "node_modules", "icon.svg", "icon.png", "icon.webp"]);
|
|
@@ -2409,7 +2576,7 @@ function caseClashes(keys) {
|
|
|
2409
2576
|
}
|
|
2410
2577
|
function unitRoot(paths, key) {
|
|
2411
2578
|
const id2 = unitCanvasId(key);
|
|
2412
|
-
return id2 === null ? paths.design :
|
|
2579
|
+
return id2 === null ? paths.design : path15.join(paths.canvases, id2);
|
|
2413
2580
|
}
|
|
2414
2581
|
function isUnitPath(key, rel) {
|
|
2415
2582
|
if (!rel || rel.includes("\\") || rel.includes("\0") || rel.startsWith("/")) return false;
|
|
@@ -2423,7 +2590,7 @@ function hashBuffer(data) {
|
|
|
2423
2590
|
return createHash4("sha256").update(data).digest("hex");
|
|
2424
2591
|
}
|
|
2425
2592
|
function hashFile(file) {
|
|
2426
|
-
const data =
|
|
2593
|
+
const data = fs15.readFileSync(file);
|
|
2427
2594
|
return { hash: hashBuffer(data), size: data.length };
|
|
2428
2595
|
}
|
|
2429
2596
|
var warnedLinks = /* @__PURE__ */ new Set();
|
|
@@ -2436,34 +2603,34 @@ function warnLink(rel) {
|
|
|
2436
2603
|
function walk(dir, base, out) {
|
|
2437
2604
|
let entries;
|
|
2438
2605
|
try {
|
|
2439
|
-
entries =
|
|
2606
|
+
entries = fs15.readdirSync(dir, { withFileTypes: true });
|
|
2440
2607
|
} catch {
|
|
2441
2608
|
return;
|
|
2442
2609
|
}
|
|
2443
2610
|
for (const entry of entries) {
|
|
2444
2611
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2445
|
-
const full =
|
|
2612
|
+
const full = path15.join(dir, entry.name);
|
|
2446
2613
|
let isFile = entry.isFile();
|
|
2447
2614
|
let isDir = entry.isDirectory();
|
|
2448
2615
|
if (entry.isSymbolicLink()) {
|
|
2449
2616
|
isDir = false;
|
|
2450
2617
|
isFile = false;
|
|
2451
2618
|
try {
|
|
2452
|
-
const target =
|
|
2453
|
-
const root =
|
|
2454
|
-
if (target.startsWith(root +
|
|
2455
|
-
else warnLink(toPosix(
|
|
2619
|
+
const target = fs15.realpathSync(full);
|
|
2620
|
+
const root = fs15.realpathSync(base);
|
|
2621
|
+
if (target.startsWith(root + path15.sep)) isFile = fs15.statSync(target).isFile();
|
|
2622
|
+
else warnLink(toPosix(path15.relative(base, full)));
|
|
2456
2623
|
} catch {
|
|
2457
2624
|
}
|
|
2458
2625
|
}
|
|
2459
2626
|
if (isDir) walk(full, base, out);
|
|
2460
|
-
else if (isFile) out[toPosix(
|
|
2627
|
+
else if (isFile) out[toPosix(path15.relative(base, full))] = hashFile(full);
|
|
2461
2628
|
}
|
|
2462
2629
|
}
|
|
2463
2630
|
function unitManifest(paths, key) {
|
|
2464
2631
|
const manifest = {};
|
|
2465
2632
|
if (key === SYSTEM_UNIT) {
|
|
2466
|
-
if (
|
|
2633
|
+
if (fs15.existsSync(paths.config) && fs15.statSync(paths.config).isFile())
|
|
2467
2634
|
manifest["design.json"] = hashFile(paths.config);
|
|
2468
2635
|
walk(paths.system, paths.design, manifest);
|
|
2469
2636
|
} else walk(unitRoot(paths, key), paths.design, manifest);
|
|
@@ -2472,13 +2639,13 @@ function unitManifest(paths, key) {
|
|
|
2472
2639
|
function buildManifest(dir) {
|
|
2473
2640
|
const manifest = {};
|
|
2474
2641
|
const visit = (current) => {
|
|
2475
|
-
for (const entry of
|
|
2476
|
-
const full =
|
|
2642
|
+
for (const entry of fs15.readdirSync(current, { withFileTypes: true })) {
|
|
2643
|
+
const full = path15.join(current, entry.name);
|
|
2477
2644
|
if (entry.isDirectory()) visit(full);
|
|
2478
|
-
else if (entry.isFile()) manifest[toPosix(
|
|
2645
|
+
else if (entry.isFile()) manifest[toPosix(path15.relative(dir, full))] = hashFile(full);
|
|
2479
2646
|
}
|
|
2480
2647
|
};
|
|
2481
|
-
if (
|
|
2648
|
+
if (fs15.existsSync(dir)) visit(dir);
|
|
2482
2649
|
return sortManifest(manifest);
|
|
2483
2650
|
}
|
|
2484
2651
|
function sortManifest(manifest) {
|
|
@@ -2487,18 +2654,18 @@ function sortManifest(manifest) {
|
|
|
2487
2654
|
function scanLocal(paths) {
|
|
2488
2655
|
const units = /* @__PURE__ */ new Map();
|
|
2489
2656
|
const notSynced = [];
|
|
2490
|
-
if (!
|
|
2657
|
+
if (!fs15.existsSync(paths.design)) return { units, notSynced };
|
|
2491
2658
|
const system = unitManifest(paths, SYSTEM_UNIT);
|
|
2492
2659
|
if (Object.keys(system).length) units.set(SYSTEM_UNIT, { key: SYSTEM_UNIT, kind: "system", manifest: system });
|
|
2493
|
-
for (const entry of
|
|
2660
|
+
for (const entry of fs15.readdirSync(paths.design, { withFileTypes: true })) {
|
|
2494
2661
|
const { name } = entry;
|
|
2495
2662
|
if (name.startsWith(".") || LOCAL_ONLY.has(name)) continue;
|
|
2496
2663
|
if (name === "design.json" && entry.isFile()) continue;
|
|
2497
2664
|
if ((name === "system" || name === "canvas") && entry.isDirectory()) continue;
|
|
2498
2665
|
notSynced.push(entry.isDirectory() ? `${name}/` : name);
|
|
2499
2666
|
}
|
|
2500
|
-
if (
|
|
2501
|
-
for (const entry of
|
|
2667
|
+
if (fs15.existsSync(paths.canvases) && fs15.statSync(paths.canvases).isDirectory()) {
|
|
2668
|
+
for (const entry of fs15.readdirSync(paths.canvases, { withFileTypes: true })) {
|
|
2502
2669
|
if (entry.name.startsWith(".")) continue;
|
|
2503
2670
|
if (!entry.isDirectory() || !ID_PATTERN.test(entry.name)) {
|
|
2504
2671
|
notSynced.push(`canvas/${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
@@ -2512,11 +2679,11 @@ function scanLocal(paths) {
|
|
|
2512
2679
|
return { units, notSynced: notSynced.sort() };
|
|
2513
2680
|
}
|
|
2514
2681
|
function unitBuildDir(paths, key) {
|
|
2515
|
-
return
|
|
2682
|
+
return path15.join(paths.cache, "cloud", "build", ...key.split("/"));
|
|
2516
2683
|
}
|
|
2517
2684
|
async function buildUnit(paths, key, out = unitBuildDir(paths, key)) {
|
|
2518
2685
|
const id2 = unitCanvasId(key);
|
|
2519
|
-
|
|
2686
|
+
fs15.rmSync(out, { recursive: true, force: true });
|
|
2520
2687
|
return buildSite(paths, out, {
|
|
2521
2688
|
canvases: id2 === null ? [] : [id2],
|
|
2522
2689
|
includeSystem: id2 === null,
|
|
@@ -2609,7 +2776,13 @@ function parseUnit(value) {
|
|
|
2609
2776
|
banned: flag(data.banned, data.bannedAt, data.banned_at),
|
|
2610
2777
|
manifest: parseManifest(data.sourceManifest ?? data.source_manifest ?? data.source),
|
|
2611
2778
|
systemRev: num(data.systemRev) ?? num(data.system_rev) ?? null,
|
|
2612
|
-
publicId: str(data.publicId) ?? null
|
|
2779
|
+
publicId: str(data.publicId) ?? null,
|
|
2780
|
+
screens: (Array.isArray(data.screens) ? data.screens : []).flatMap((value2) => {
|
|
2781
|
+
const screen = record(value2);
|
|
2782
|
+
const item = str(screen.item);
|
|
2783
|
+
const publicId = str(screen.publicId);
|
|
2784
|
+
return item && publicId ? [{ item, publicId }] : [];
|
|
2785
|
+
})
|
|
2613
2786
|
};
|
|
2614
2787
|
}
|
|
2615
2788
|
function parseRevision(value) {
|
|
@@ -2648,7 +2821,7 @@ var CloudClient = class {
|
|
|
2648
2821
|
}
|
|
2649
2822
|
host;
|
|
2650
2823
|
token;
|
|
2651
|
-
async send(method,
|
|
2824
|
+
async send(method, path38, init) {
|
|
2652
2825
|
const headers = {
|
|
2653
2826
|
"User-Agent": `foss-design/${packageVersion()} (${os.platform()})`,
|
|
2654
2827
|
Accept: "application/json"
|
|
@@ -2656,7 +2829,7 @@ var CloudClient = class {
|
|
|
2656
2829
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
2657
2830
|
if (init.type) headers["Content-Type"] = init.type;
|
|
2658
2831
|
try {
|
|
2659
|
-
return await fetch(`${this.host}/api${
|
|
2832
|
+
return await fetch(`${this.host}/api${path38}`, {
|
|
2660
2833
|
method,
|
|
2661
2834
|
headers,
|
|
2662
2835
|
body: init.body,
|
|
@@ -2677,8 +2850,8 @@ var CloudClient = class {
|
|
|
2677
2850
|
throw errorFor(this.host, response.status, body, response.headers.get("retry-after"));
|
|
2678
2851
|
}
|
|
2679
2852
|
/** JSON in, JSON out; any non-2xx answer throws a CloudError. */
|
|
2680
|
-
async request(method,
|
|
2681
|
-
const response = await this.send(method,
|
|
2853
|
+
async request(method, path38, body) {
|
|
2854
|
+
const response = await this.send(method, path38, {
|
|
2682
2855
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2683
2856
|
type: body === void 0 ? void 0 : "application/json",
|
|
2684
2857
|
timeout: JSON_TIMEOUT
|
|
@@ -2690,7 +2863,7 @@ var CloudClient = class {
|
|
|
2690
2863
|
try {
|
|
2691
2864
|
return JSON.parse(text2);
|
|
2692
2865
|
} catch {
|
|
2693
|
-
throw new CloudError(`${this.host} answered ${method} ${
|
|
2866
|
+
throw new CloudError(`${this.host} answered ${method} ${path38} with something other than JSON`, 0, "bad_response");
|
|
2694
2867
|
}
|
|
2695
2868
|
}
|
|
2696
2869
|
/** POST /device/code. */
|
|
@@ -2798,6 +2971,16 @@ var CloudClient = class {
|
|
|
2798
2971
|
);
|
|
2799
2972
|
return { publicId: str(body.publicId) ?? null };
|
|
2800
2973
|
}
|
|
2974
|
+
/** Publish one screen of a canvas on its own, or turn its link off (owner only). */
|
|
2975
|
+
async screenAction(projectId, canvas, screen, action) {
|
|
2976
|
+
const body = record(
|
|
2977
|
+
await this.request(
|
|
2978
|
+
"POST",
|
|
2979
|
+
`/projects/${encodeURIComponent(projectId)}/canvases/${encodeURIComponent(canvas)}/screens/${encodeURIComponent(screen)}/${action}`
|
|
2980
|
+
)
|
|
2981
|
+
);
|
|
2982
|
+
return { publicId: str(body.publicId) ?? null };
|
|
2983
|
+
}
|
|
2801
2984
|
/** POST /blobs/missing, in batches: the hashes the cloud does not have yet. */
|
|
2802
2985
|
async missing(projectId, hashes2) {
|
|
2803
2986
|
const missing = [];
|
|
@@ -2852,9 +3035,9 @@ var CloudClient = class {
|
|
|
2852
3035
|
};
|
|
2853
3036
|
|
|
2854
3037
|
// src/cloud/credentials.ts
|
|
2855
|
-
import
|
|
3038
|
+
import fs16 from "fs";
|
|
2856
3039
|
import os2 from "os";
|
|
2857
|
-
import
|
|
3040
|
+
import path16 from "path";
|
|
2858
3041
|
var DEFAULT_CLOUD = "https://app.fossdesign.dev";
|
|
2859
3042
|
function normalizeHost(value) {
|
|
2860
3043
|
let url;
|
|
@@ -2871,14 +3054,14 @@ function cloudHost() {
|
|
|
2871
3054
|
return normalizeHost(process.env.FOSS_DESIGN_CLOUD || DEFAULT_CLOUD);
|
|
2872
3055
|
}
|
|
2873
3056
|
function credentialsFile() {
|
|
2874
|
-
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME ||
|
|
2875
|
-
return
|
|
3057
|
+
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME || path16.join(os2.homedir(), ".config");
|
|
3058
|
+
return path16.join(base, "foss-design", "credentials.json");
|
|
2876
3059
|
}
|
|
2877
3060
|
function readAll() {
|
|
2878
3061
|
const file = credentialsFile();
|
|
2879
|
-
if (!
|
|
3062
|
+
if (!fs16.existsSync(file)) return {};
|
|
2880
3063
|
try {
|
|
2881
|
-
const data = JSON.parse(
|
|
3064
|
+
const data = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
2882
3065
|
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
2883
3066
|
} catch (error) {
|
|
2884
3067
|
throw new CliError(`${file} is not valid JSON (${error.message}); fix or delete it`);
|
|
@@ -2886,12 +3069,12 @@ function readAll() {
|
|
|
2886
3069
|
}
|
|
2887
3070
|
function writeAll(data) {
|
|
2888
3071
|
const file = credentialsFile();
|
|
2889
|
-
|
|
3072
|
+
fs16.mkdirSync(path16.dirname(file), { recursive: true, mode: 448 });
|
|
2890
3073
|
const temp = `${file}.${process.pid}.tmp`;
|
|
2891
|
-
|
|
3074
|
+
fs16.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2892
3075
|
`, { mode: 384 });
|
|
2893
|
-
|
|
2894
|
-
|
|
3076
|
+
fs16.chmodSync(temp, 384);
|
|
3077
|
+
fs16.renameSync(temp, file);
|
|
2895
3078
|
}
|
|
2896
3079
|
function getCredential(host) {
|
|
2897
3080
|
const entry = readAll()[host];
|
|
@@ -2914,17 +3097,17 @@ function requireCredential(host) {
|
|
|
2914
3097
|
}
|
|
2915
3098
|
|
|
2916
3099
|
// src/cloud/state.ts
|
|
2917
|
-
import
|
|
2918
|
-
import
|
|
2919
|
-
var linkFile = (paths) =>
|
|
2920
|
-
var cloudCache = (paths) =>
|
|
2921
|
-
var incomingDir = (paths, key) =>
|
|
3100
|
+
import fs17 from "fs";
|
|
3101
|
+
import path17 from "path";
|
|
3102
|
+
var linkFile = (paths) => path17.join(paths.design, "cloud.json");
|
|
3103
|
+
var cloudCache = (paths) => path17.join(paths.cache, "cloud");
|
|
3104
|
+
var incomingDir = (paths, key) => path17.join(cloudCache(paths), "incoming", ...key.split("/"));
|
|
2922
3105
|
function readLink(paths) {
|
|
2923
3106
|
const file = linkFile(paths);
|
|
2924
|
-
if (!
|
|
3107
|
+
if (!fs17.existsSync(file)) return null;
|
|
2925
3108
|
let data;
|
|
2926
3109
|
try {
|
|
2927
|
-
data = JSON.parse(
|
|
3110
|
+
data = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
2928
3111
|
} catch (error) {
|
|
2929
3112
|
throw new CliError(`${file} is not valid JSON (${error.message}); run \`design link <project>\` again`);
|
|
2930
3113
|
}
|
|
@@ -2963,9 +3146,9 @@ function writeLink(paths, link) {
|
|
|
2963
3146
|
units: sorted(link.units),
|
|
2964
3147
|
conflicts: sorted(link.conflicts)
|
|
2965
3148
|
};
|
|
2966
|
-
|
|
3149
|
+
fs17.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2967
3150
|
`);
|
|
2968
|
-
|
|
3151
|
+
fs17.renameSync(temp, file);
|
|
2969
3152
|
}
|
|
2970
3153
|
var hashes = (manifest) => Object.fromEntries(Object.entries(manifest).map(([file, entry]) => [file, entry.hash]));
|
|
2971
3154
|
function diffFiles(a, b) {
|
|
@@ -3043,20 +3226,20 @@ var byUnit = (a, b) => a === SYSTEM_UNIT ? -1 : b === SYSTEM_UNIT ? 1 : a < b ?
|
|
|
3043
3226
|
var plural = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
3044
3227
|
|
|
3045
3228
|
// src/capture/cover.ts
|
|
3046
|
-
import
|
|
3229
|
+
import fs21 from "fs";
|
|
3047
3230
|
import os3 from "os";
|
|
3048
|
-
import
|
|
3231
|
+
import path20 from "path";
|
|
3049
3232
|
|
|
3050
3233
|
// src/server/snapshots.ts
|
|
3051
3234
|
import { createHash as createHash5 } from "crypto";
|
|
3052
|
-
import
|
|
3053
|
-
import
|
|
3235
|
+
import fs18 from "fs";
|
|
3236
|
+
import path18 from "path";
|
|
3054
3237
|
function depsHash(root, rels, cache = /* @__PURE__ */ new Map()) {
|
|
3055
3238
|
const hash = createHash5("sha1");
|
|
3056
3239
|
for (const rel of [...rels].sort()) {
|
|
3057
3240
|
let one = cache.get(rel);
|
|
3058
3241
|
if (one === void 0) {
|
|
3059
|
-
one = sourceHash(
|
|
3242
|
+
one = sourceHash(path18.join(root, rel)) ?? "missing";
|
|
3060
3243
|
cache.set(rel, one);
|
|
3061
3244
|
}
|
|
3062
3245
|
hash.update(`${rel}\0${one}
|
|
@@ -3066,7 +3249,7 @@ function depsHash(root, rels, cache = /* @__PURE__ */ new Map()) {
|
|
|
3066
3249
|
}
|
|
3067
3250
|
function sourceHash(file) {
|
|
3068
3251
|
try {
|
|
3069
|
-
return createHash5("sha1").update(
|
|
3252
|
+
return createHash5("sha1").update(fs18.readFileSync(file)).digest("hex").slice(0, 16);
|
|
3070
3253
|
} catch {
|
|
3071
3254
|
return null;
|
|
3072
3255
|
}
|
|
@@ -3084,20 +3267,20 @@ var SnapshotStore = class {
|
|
|
3084
3267
|
urlBase;
|
|
3085
3268
|
meta = /* @__PURE__ */ new Map();
|
|
3086
3269
|
metaFile(canvas) {
|
|
3087
|
-
return
|
|
3270
|
+
return path18.join(this.dir, canvas, "meta.json");
|
|
3088
3271
|
}
|
|
3089
3272
|
/** Written aside and renamed: a run stopped mid-write leaves the old file, not half of one. */
|
|
3090
3273
|
writeMeta(canvas, data) {
|
|
3091
3274
|
const temp = `${this.metaFile(canvas)}.${process.pid}.tmp`;
|
|
3092
|
-
|
|
3093
|
-
|
|
3275
|
+
fs18.writeFileSync(temp, JSON.stringify(data));
|
|
3276
|
+
fs18.renameSync(temp, this.metaFile(canvas));
|
|
3094
3277
|
}
|
|
3095
3278
|
load(canvas) {
|
|
3096
3279
|
const cached = this.meta.get(canvas);
|
|
3097
3280
|
if (cached) return cached;
|
|
3098
3281
|
let data = {};
|
|
3099
3282
|
try {
|
|
3100
|
-
data = JSON.parse(
|
|
3283
|
+
data = JSON.parse(fs18.readFileSync(this.metaFile(canvas), "utf8"));
|
|
3101
3284
|
} catch {
|
|
3102
3285
|
}
|
|
3103
3286
|
this.meta.set(canvas, data);
|
|
@@ -3147,11 +3330,11 @@ var SnapshotStore = class {
|
|
|
3147
3330
|
}
|
|
3148
3331
|
save(canvas, id2, theme2, png, height, source, deps) {
|
|
3149
3332
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) throw new Error("bad snapshot key");
|
|
3150
|
-
const dir =
|
|
3151
|
-
|
|
3152
|
-
const file =
|
|
3153
|
-
|
|
3154
|
-
|
|
3333
|
+
const dir = path18.join(this.dir, canvas);
|
|
3334
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
3335
|
+
const file = path18.join(dir, `${id2}.${theme2}.png`);
|
|
3336
|
+
fs18.writeFileSync(`${file}.${process.pid}.tmp`, png);
|
|
3337
|
+
fs18.renameSync(`${file}.${process.pid}.tmp`, file);
|
|
3155
3338
|
const data = this.load(canvas);
|
|
3156
3339
|
const version = Date.now();
|
|
3157
3340
|
const previous = data[id2];
|
|
@@ -3166,20 +3349,20 @@ var SnapshotStore = class {
|
|
|
3166
3349
|
file(canvas, name) {
|
|
3167
3350
|
const match = /^(@?[a-z0-9][a-z0-9_-]*)\.(light|dark)(\.thumb)?\.png$/i.exec(name);
|
|
3168
3351
|
if (!isSafeSegment(canvas) || !match) return null;
|
|
3169
|
-
const file =
|
|
3170
|
-
if (!
|
|
3352
|
+
const file = path18.join(this.dir, canvas, `${match[1]}.${match[2]}.png`);
|
|
3353
|
+
if (!fs18.existsSync(file)) return null;
|
|
3171
3354
|
return match[3] ? ensureThumb(file) : file;
|
|
3172
3355
|
}
|
|
3173
3356
|
};
|
|
3174
3357
|
|
|
3175
3358
|
// src/capture/build-server.ts
|
|
3176
|
-
import
|
|
3359
|
+
import fs19 from "fs";
|
|
3177
3360
|
import http from "http";
|
|
3178
|
-
import
|
|
3361
|
+
import path19 from "path";
|
|
3179
3362
|
import sirv from "sirv";
|
|
3180
3363
|
async function serveBuild(paths, name, options) {
|
|
3181
|
-
const out =
|
|
3182
|
-
|
|
3364
|
+
const out = path19.join(paths.cache, name);
|
|
3365
|
+
fs19.rmSync(out, { recursive: true, force: true });
|
|
3183
3366
|
await buildSite(paths, out, {
|
|
3184
3367
|
canvases: options.canvases,
|
|
3185
3368
|
includeSystem: options.includeSystem,
|
|
@@ -3207,7 +3390,7 @@ async function serveBuild(paths, name, options) {
|
|
|
3207
3390
|
base: `http://127.0.0.1:${server.address().port}`,
|
|
3208
3391
|
read(rel) {
|
|
3209
3392
|
try {
|
|
3210
|
-
return JSON.parse(
|
|
3393
|
+
return JSON.parse(fs19.readFileSync(path19.join(out, rel), "utf8"));
|
|
3211
3394
|
} catch {
|
|
3212
3395
|
return null;
|
|
3213
3396
|
}
|
|
@@ -3220,7 +3403,7 @@ async function serveBuild(paths, name, options) {
|
|
|
3220
3403
|
}
|
|
3221
3404
|
|
|
3222
3405
|
// src/capture/chrome.ts
|
|
3223
|
-
import
|
|
3406
|
+
import fs20 from "fs";
|
|
3224
3407
|
var CANDIDATES = {
|
|
3225
3408
|
darwin: [
|
|
3226
3409
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -3244,8 +3427,8 @@ var CANDIDATES = {
|
|
|
3244
3427
|
};
|
|
3245
3428
|
function findChrome() {
|
|
3246
3429
|
const configured = process.env.DESIGN_CHROME;
|
|
3247
|
-
if (configured &&
|
|
3248
|
-
for (const candidate of CANDIDATES[process.platform] ?? []) if (
|
|
3430
|
+
if (configured && fs20.existsSync(configured)) return configured;
|
|
3431
|
+
for (const candidate of CANDIDATES[process.platform] ?? []) if (fs20.existsSync(candidate)) return candidate;
|
|
3249
3432
|
return null;
|
|
3250
3433
|
}
|
|
3251
3434
|
async function launchChrome() {
|
|
@@ -3417,7 +3600,7 @@ function staleSnapshots(paths, canvasIds, filter = {}) {
|
|
|
3417
3600
|
for (const job of inLight) {
|
|
3418
3601
|
if (filter.screens && !filter.screens.has(job.id)) continue;
|
|
3419
3602
|
const file = files.get(job.id);
|
|
3420
|
-
if (!file || !
|
|
3603
|
+
if (!file || !fs21.existsSync(file)) continue;
|
|
3421
3604
|
const deps = store.deps(id2, job.id);
|
|
3422
3605
|
const hash = deps ? depsHash(paths.root, deps, hashes2) : sourceHash(file);
|
|
3423
3606
|
const pinned = inDark.get(job.id) === job.theme;
|
|
@@ -3436,12 +3619,12 @@ function staleSnapshots(paths, canvasIds, filter = {}) {
|
|
|
3436
3619
|
}
|
|
3437
3620
|
return stale;
|
|
3438
3621
|
}
|
|
3439
|
-
async function takeSnapshots(paths,
|
|
3440
|
-
if (!
|
|
3622
|
+
async function takeSnapshots(paths, targets2, preview, progress2) {
|
|
3623
|
+
if (!targets2.length) return [];
|
|
3441
3624
|
if (!findChrome()) return ["No Chrome here, so screens keep the snapshots they have."];
|
|
3442
3625
|
const store = new SnapshotStore(paths.snapshots);
|
|
3443
3626
|
const project = new DesignProject(paths, DEV_URLS, store.lookup);
|
|
3444
|
-
const canvases = [...new Set(
|
|
3627
|
+
const canvases = [...new Set(targets2.map((target) => target.canvas))];
|
|
3445
3628
|
progress2.update(`Building ${canvases.join(", ")} to take snapshots from\u2026`);
|
|
3446
3629
|
let url;
|
|
3447
3630
|
let docOf = (id2) => project.canvas(id2).doc;
|
|
@@ -3462,10 +3645,10 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3462
3645
|
const failed = [];
|
|
3463
3646
|
const started = Date.now();
|
|
3464
3647
|
let done = 0;
|
|
3465
|
-
const line = () => `Snapshots ${done}/${
|
|
3648
|
+
const line = () => `Snapshots ${done}/${targets2.length} (both themes, as the viewer and the cloud show them)`;
|
|
3466
3649
|
progress2.update(line());
|
|
3467
3650
|
try {
|
|
3468
|
-
await mapLimit([...
|
|
3651
|
+
await mapLimit([...targets2], TABS, async ({ canvas, screen, theme: theme2, source }) => {
|
|
3469
3652
|
const job = canvasFrames(docOf(canvas), url, theme2, { id: screen })[0];
|
|
3470
3653
|
if (job) {
|
|
3471
3654
|
const page = await browser.newPage({
|
|
@@ -3487,7 +3670,7 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3487
3670
|
const deps = modules ? [
|
|
3488
3671
|
.../* @__PURE__ */ new Set([
|
|
3489
3672
|
...[...loaded].flatMap((rel) => modules[rel] ?? []),
|
|
3490
|
-
...file ? [toPosix(
|
|
3673
|
+
...file ? [toPosix(path20.relative(paths.root, file))] : []
|
|
3491
3674
|
])
|
|
3492
3675
|
].sort() : [];
|
|
3493
3676
|
const hash = deps.length ? depsHash(paths.root, deps, hashes2) : source;
|
|
@@ -3508,7 +3691,7 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3508
3691
|
await stop();
|
|
3509
3692
|
}
|
|
3510
3693
|
const seconds2 = Math.round((Date.now() - started) / 1e3);
|
|
3511
|
-
progress2.done(`Took ${
|
|
3694
|
+
progress2.done(`Took ${targets2.length - failed.length} snapshot${targets2.length === 1 ? "" : "s"} in ${seconds2}s`);
|
|
3512
3695
|
if (!failed.length) return [];
|
|
3513
3696
|
const shown = failed.slice(0, 5).join(", ");
|
|
3514
3697
|
return [
|
|
@@ -3517,73 +3700,73 @@ async function takeSnapshots(paths, targets, preview, progress2) {
|
|
|
3517
3700
|
}
|
|
3518
3701
|
|
|
3519
3702
|
// src/cloud/pull.ts
|
|
3520
|
-
import
|
|
3521
|
-
import
|
|
3703
|
+
import fs25 from "fs";
|
|
3704
|
+
import path24 from "path";
|
|
3522
3705
|
|
|
3523
3706
|
// src/cloud/merge.ts
|
|
3524
|
-
import
|
|
3525
|
-
import
|
|
3707
|
+
import fs23 from "fs";
|
|
3708
|
+
import path22 from "path";
|
|
3526
3709
|
import { merge as diff3 } from "node-diff3";
|
|
3527
3710
|
|
|
3528
3711
|
// src/cloud/write.ts
|
|
3529
|
-
import
|
|
3530
|
-
import
|
|
3712
|
+
import fs22 from "fs";
|
|
3713
|
+
import path21 from "path";
|
|
3531
3714
|
function writeInside(root, rel, data) {
|
|
3532
|
-
const file =
|
|
3715
|
+
const file = path21.join(root, rel);
|
|
3533
3716
|
if (!isInside(root, file)) throw new CliError(`Refusing to write outside ${root}: ${rel}`);
|
|
3534
|
-
|
|
3535
|
-
if (!isInside(
|
|
3717
|
+
fs22.mkdirSync(path21.dirname(file), { recursive: true });
|
|
3718
|
+
if (!isInside(fs22.realpathSync(root), fs22.realpathSync(path21.dirname(file))))
|
|
3536
3719
|
throw new CliError(`Refusing to write through a link out of ${root}: ${rel}`);
|
|
3537
|
-
if (
|
|
3538
|
-
|
|
3720
|
+
if (fs22.existsSync(file) && fs22.lstatSync(file).isSymbolicLink()) fs22.unlinkSync(file);
|
|
3721
|
+
fs22.writeFileSync(file, data);
|
|
3539
3722
|
}
|
|
3540
3723
|
function pruneEmpty(dir, stop) {
|
|
3541
3724
|
let current = dir;
|
|
3542
3725
|
while (isInside(stop, current) && current !== stop) {
|
|
3543
3726
|
try {
|
|
3544
|
-
if (
|
|
3545
|
-
|
|
3727
|
+
if (fs22.readdirSync(current).length) return;
|
|
3728
|
+
fs22.rmdirSync(current);
|
|
3546
3729
|
} catch {
|
|
3547
3730
|
return;
|
|
3548
3731
|
}
|
|
3549
|
-
current =
|
|
3732
|
+
current = path21.dirname(current);
|
|
3550
3733
|
}
|
|
3551
3734
|
}
|
|
3552
3735
|
|
|
3553
3736
|
// src/cloud/merge.ts
|
|
3554
3737
|
var TEXT = /\.(?:[cm]?[jt]sx?|json|css|scss|html?|mdx?|svg|txt|ya?ml|csv|xml|toml)$/i;
|
|
3555
3738
|
var isText = (rel, ...data) => TEXT.test(rel) && data.every((buffer) => !buffer || !buffer.subarray(0, 8192).includes(0));
|
|
3556
|
-
var mergeDir = (paths, key) =>
|
|
3557
|
-
var stateFile = (paths, key) =>
|
|
3558
|
-
var sideFile = (paths, key, rel, side) =>
|
|
3739
|
+
var mergeDir = (paths, key) => path22.join(cloudCache(paths), "merge", ...key.split("/"));
|
|
3740
|
+
var stateFile = (paths, key) => path22.join(mergeDir(paths, key), "state.json");
|
|
3741
|
+
var sideFile = (paths, key, rel, side) => path22.join(mergeDir(paths, key), side, rel);
|
|
3559
3742
|
function readMergeState(paths, key) {
|
|
3560
3743
|
try {
|
|
3561
|
-
return JSON.parse(
|
|
3744
|
+
return JSON.parse(fs23.readFileSync(stateFile(paths, key), "utf8"));
|
|
3562
3745
|
} catch {
|
|
3563
3746
|
return null;
|
|
3564
3747
|
}
|
|
3565
3748
|
}
|
|
3566
3749
|
function saveState(paths, state) {
|
|
3567
|
-
|
|
3568
|
-
|
|
3750
|
+
fs23.mkdirSync(mergeDir(paths, state.unit), { recursive: true });
|
|
3751
|
+
fs23.writeFileSync(stateFile(paths, state.unit), `${JSON.stringify(state, null, 2)}
|
|
3569
3752
|
`);
|
|
3570
3753
|
}
|
|
3571
3754
|
function mergingUnits(paths) {
|
|
3572
|
-
const root =
|
|
3755
|
+
const root = path22.join(cloudCache(paths), "merge");
|
|
3573
3756
|
const found = [];
|
|
3574
3757
|
const visit = (dir, prefix) => {
|
|
3575
|
-
if (
|
|
3758
|
+
if (fs23.existsSync(path22.join(dir, "state.json"))) {
|
|
3576
3759
|
const state = readMergeState(paths, prefix);
|
|
3577
3760
|
if (state) found.push(state);
|
|
3578
3761
|
return;
|
|
3579
3762
|
}
|
|
3580
3763
|
let entries = [];
|
|
3581
3764
|
try {
|
|
3582
|
-
entries =
|
|
3765
|
+
entries = fs23.readdirSync(dir, { withFileTypes: true });
|
|
3583
3766
|
} catch {
|
|
3584
3767
|
}
|
|
3585
3768
|
for (const entry of entries)
|
|
3586
|
-
if (entry.isDirectory()) visit(
|
|
3769
|
+
if (entry.isDirectory()) visit(path22.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
3587
3770
|
};
|
|
3588
3771
|
visit(root, "");
|
|
3589
3772
|
return found;
|
|
@@ -3593,7 +3776,7 @@ function openConflicts(paths, state) {
|
|
|
3593
3776
|
return state.conflicts.filter((conflict) => {
|
|
3594
3777
|
if (conflict.kind !== "text") return true;
|
|
3595
3778
|
try {
|
|
3596
|
-
return MARKER.test(
|
|
3779
|
+
return MARKER.test(fs23.readFileSync(path22.join(paths.design, conflict.path), "utf8"));
|
|
3597
3780
|
} catch {
|
|
3598
3781
|
return false;
|
|
3599
3782
|
}
|
|
@@ -3602,19 +3785,19 @@ function openConflicts(paths, state) {
|
|
|
3602
3785
|
function keep(paths, key, rel, side, data) {
|
|
3603
3786
|
if (!data) return;
|
|
3604
3787
|
const file = sideFile(paths, key, rel, side);
|
|
3605
|
-
|
|
3606
|
-
|
|
3788
|
+
fs23.mkdirSync(path22.dirname(file), { recursive: true });
|
|
3789
|
+
fs23.writeFileSync(file, data);
|
|
3607
3790
|
}
|
|
3608
3791
|
function mergeUnit(paths, key, headRev, base, here, cloud2, blob) {
|
|
3609
3792
|
const files = { added: 0, changed: 0, removed: 0, merged: 0 };
|
|
3610
3793
|
const conflicts = [];
|
|
3611
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3612
|
-
|
|
3613
|
-
const read = (rel) =>
|
|
3794
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path22.dirname(unitRoot(paths, key));
|
|
3795
|
+
fs23.rmSync(mergeDir(paths, key), { recursive: true, force: true });
|
|
3796
|
+
const read = (rel) => fs23.readFileSync(path22.join(paths.design, rel));
|
|
3614
3797
|
const remove = (rel) => {
|
|
3615
|
-
const file =
|
|
3616
|
-
|
|
3617
|
-
pruneEmpty(
|
|
3798
|
+
const file = path22.join(paths.design, rel);
|
|
3799
|
+
fs23.rmSync(file, { force: true });
|
|
3800
|
+
pruneEmpty(path22.dirname(file), stop);
|
|
3618
3801
|
};
|
|
3619
3802
|
for (const rel of [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(here), ...Object.keys(cloud2)])].sort()) {
|
|
3620
3803
|
const b = base[rel];
|
|
@@ -3670,11 +3853,11 @@ function resolveConflict(paths, state, rel, side) {
|
|
|
3670
3853
|
const conflict = state.conflicts.find((item) => item.path === rel);
|
|
3671
3854
|
if (!conflict) return;
|
|
3672
3855
|
const kept = sideFile(paths, state.unit, rel, side);
|
|
3673
|
-
const stop = state.unit === SYSTEM_UNIT ? paths.design :
|
|
3674
|
-
if (
|
|
3856
|
+
const stop = state.unit === SYSTEM_UNIT ? paths.design : path22.dirname(unitRoot(paths, state.unit));
|
|
3857
|
+
if (fs23.existsSync(kept)) writeInside(paths.design, rel, fs23.readFileSync(kept));
|
|
3675
3858
|
else {
|
|
3676
|
-
|
|
3677
|
-
pruneEmpty(
|
|
3859
|
+
fs23.rmSync(path22.join(paths.design, rel), { force: true });
|
|
3860
|
+
pruneEmpty(path22.dirname(path22.join(paths.design, rel)), stop);
|
|
3678
3861
|
}
|
|
3679
3862
|
state.conflicts = state.conflicts.filter((item) => item.path !== rel);
|
|
3680
3863
|
saveState(paths, state);
|
|
@@ -3684,15 +3867,15 @@ function finishMerge(paths, link, key, accept = false) {
|
|
|
3684
3867
|
if (state) {
|
|
3685
3868
|
const open2 = openConflicts(paths, state).filter((conflict) => !(accept && conflict.kind !== "text"));
|
|
3686
3869
|
if (open2.length) return open2;
|
|
3687
|
-
|
|
3870
|
+
fs23.rmSync(mergeDir(paths, key), { recursive: true, force: true });
|
|
3688
3871
|
}
|
|
3689
3872
|
delete link.conflicts[key];
|
|
3690
3873
|
return [];
|
|
3691
3874
|
}
|
|
3692
3875
|
|
|
3693
3876
|
// src/cloud/meta.ts
|
|
3694
|
-
import
|
|
3695
|
-
import
|
|
3877
|
+
import fs24 from "fs";
|
|
3878
|
+
import path23 from "path";
|
|
3696
3879
|
async function syncProjectMeta(paths, client, link) {
|
|
3697
3880
|
const remote = await client.project(link.project);
|
|
3698
3881
|
if (!remote) return [];
|
|
@@ -3711,8 +3894,8 @@ async function syncProjectMeta(paths, client, link) {
|
|
|
3711
3894
|
if (localChanged && !cloudChanged) {
|
|
3712
3895
|
try {
|
|
3713
3896
|
if (file) {
|
|
3714
|
-
const ext =
|
|
3715
|
-
await client.setProjectIcon(link.project, { data:
|
|
3897
|
+
const ext = path23.extname(file).slice(1);
|
|
3898
|
+
await client.setProjectIcon(link.project, { data: fs24.readFileSync(file), type: ICON_TYPES[ext] });
|
|
3716
3899
|
lines.push("The project icon went up to the cloud.");
|
|
3717
3900
|
} else {
|
|
3718
3901
|
await client.setProjectIcon(link.project, null);
|
|
@@ -3747,12 +3930,12 @@ function checkManifest(key, manifest) {
|
|
|
3747
3930
|
}
|
|
3748
3931
|
function applyUnit(paths, key, local, target, blobs) {
|
|
3749
3932
|
const counts = { added: 0, changed: 0, removed: 0 };
|
|
3750
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
3933
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path24.dirname(unitRoot(paths, key));
|
|
3751
3934
|
for (const rel of Object.keys(local)) {
|
|
3752
3935
|
if (rel in target) continue;
|
|
3753
|
-
const file =
|
|
3754
|
-
|
|
3755
|
-
pruneEmpty(
|
|
3936
|
+
const file = path24.join(paths.design, rel);
|
|
3937
|
+
fs25.rmSync(file, { force: true });
|
|
3938
|
+
pruneEmpty(path24.dirname(file), stop);
|
|
3756
3939
|
counts.removed++;
|
|
3757
3940
|
}
|
|
3758
3941
|
for (const [rel, entry] of Object.entries(target)) {
|
|
@@ -3822,7 +4005,7 @@ async function pull(ctx, options) {
|
|
|
3822
4005
|
}
|
|
3823
4006
|
if (s.conflictRev !== void 0) {
|
|
3824
4007
|
finishMerge(paths, link, key, true);
|
|
3825
|
-
|
|
4008
|
+
fs25.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3826
4009
|
}
|
|
3827
4010
|
report.units.push({ ...reportFor("up_to_date"), rev: s.baseRev });
|
|
3828
4011
|
} else if (!s.changed.length) {
|
|
@@ -3851,7 +4034,7 @@ async function pull(ctx, options) {
|
|
|
3851
4034
|
}
|
|
3852
4035
|
const byHash = /* @__PURE__ */ new Map();
|
|
3853
4036
|
for (const unit of scan.units.values())
|
|
3854
|
-
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash,
|
|
4037
|
+
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash, path24.join(paths.design, rel));
|
|
3855
4038
|
const needed = /* @__PURE__ */ new Set();
|
|
3856
4039
|
const bases = /* @__PURE__ */ new Set();
|
|
3857
4040
|
for (const plan of plans) {
|
|
@@ -3872,7 +4055,7 @@ async function pull(ctx, options) {
|
|
|
3872
4055
|
const download = [];
|
|
3873
4056
|
for (const hash of needed) {
|
|
3874
4057
|
const file = byHash.get(hash);
|
|
3875
|
-
if (file) blobs.set(hash,
|
|
4058
|
+
if (file) blobs.set(hash, fs25.readFileSync(file));
|
|
3876
4059
|
else download.push(hash);
|
|
3877
4060
|
}
|
|
3878
4061
|
if (download.length) {
|
|
@@ -3908,7 +4091,7 @@ async function pull(ctx, options) {
|
|
|
3908
4091
|
if (plan.remote) link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
3909
4092
|
else delete link.units[key];
|
|
3910
4093
|
delete link.conflicts[key];
|
|
3911
|
-
|
|
4094
|
+
fs25.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3912
4095
|
} else {
|
|
3913
4096
|
const result = mergeUnit(
|
|
3914
4097
|
paths,
|
|
@@ -3919,7 +4102,7 @@ async function pull(ctx, options) {
|
|
|
3919
4102
|
plan.remote.manifest,
|
|
3920
4103
|
(hash) => blobs.get(hash)
|
|
3921
4104
|
);
|
|
3922
|
-
|
|
4105
|
+
fs25.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3923
4106
|
link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
3924
4107
|
entry.rev = plan.remote.headRev;
|
|
3925
4108
|
entry.files = result.files;
|
|
@@ -3949,8 +4132,8 @@ async function pull(ctx, options) {
|
|
|
3949
4132
|
}
|
|
3950
4133
|
|
|
3951
4134
|
// src/cloud/push.ts
|
|
3952
|
-
import
|
|
3953
|
-
import
|
|
4135
|
+
import fs26 from "fs";
|
|
4136
|
+
import path25 from "path";
|
|
3954
4137
|
function blockedReport(status, message) {
|
|
3955
4138
|
return {
|
|
3956
4139
|
unit: status.key,
|
|
@@ -4066,7 +4249,7 @@ async function push(ctx, options) {
|
|
|
4066
4249
|
for (const key of toPush) {
|
|
4067
4250
|
const s = status(key);
|
|
4068
4251
|
if (s.conflictRev !== void 0) {
|
|
4069
|
-
const dir =
|
|
4252
|
+
const dir = path25.relative(paths.root, incomingDir(paths, key));
|
|
4070
4253
|
blocked.push(
|
|
4071
4254
|
blockedReport(
|
|
4072
4255
|
s,
|
|
@@ -4115,7 +4298,7 @@ async function push(ctx, options) {
|
|
|
4115
4298
|
const files = /* @__PURE__ */ new Map();
|
|
4116
4299
|
const addFiles = (manifest, dir) => {
|
|
4117
4300
|
for (const [rel, entry] of Object.entries(manifest))
|
|
4118
|
-
if (!files.has(entry.hash)) files.set(entry.hash,
|
|
4301
|
+
if (!files.has(entry.hash)) files.set(entry.hash, path25.join(dir, rel));
|
|
4119
4302
|
};
|
|
4120
4303
|
for (const key of pushing) {
|
|
4121
4304
|
const id2 = unitCanvasId(key);
|
|
@@ -4147,7 +4330,7 @@ async function push(ctx, options) {
|
|
|
4147
4330
|
if (missing.length) {
|
|
4148
4331
|
const unknown = missing.find((hash) => !files.has(hash));
|
|
4149
4332
|
if (unknown) throw new CliError(`The cloud asked for ${unknown.slice(0, 12)}, which is not part of this push`);
|
|
4150
|
-
const bytes = missing.reduce((sum, hash) => sum +
|
|
4333
|
+
const bytes = missing.reduce((sum, hash) => sum + fs26.statSync(files.get(hash)).size, 0);
|
|
4151
4334
|
const bar = ctx.progress();
|
|
4152
4335
|
const started = Date.now();
|
|
4153
4336
|
let sentFiles = 0;
|
|
@@ -4156,12 +4339,12 @@ async function push(ctx, options) {
|
|
|
4156
4339
|
bar.update(line());
|
|
4157
4340
|
await mapLimit(missing, TRANSFERS, async (hash) => {
|
|
4158
4341
|
const file = files.get(hash);
|
|
4159
|
-
const data =
|
|
4342
|
+
const data = fs26.readFileSync(file);
|
|
4160
4343
|
try {
|
|
4161
4344
|
await client.putBlob(link.project, hash, data);
|
|
4162
4345
|
} catch (error) {
|
|
4163
4346
|
if (error instanceof CloudError && error.status === 413)
|
|
4164
|
-
throw new CliError(`${
|
|
4347
|
+
throw new CliError(`${path25.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
4165
4348
|
throw error;
|
|
4166
4349
|
}
|
|
4167
4350
|
sentFiles++;
|
|
@@ -4194,7 +4377,7 @@ async function push(ctx, options) {
|
|
|
4194
4377
|
url: webUrl(link, unit.key)
|
|
4195
4378
|
});
|
|
4196
4379
|
}
|
|
4197
|
-
for (const key of resolved)
|
|
4380
|
+
for (const key of resolved) fs26.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
4198
4381
|
writeLink(paths, link);
|
|
4199
4382
|
report.units.sort((a, b) => byUnit(a.unit, b.unit));
|
|
4200
4383
|
return report;
|
|
@@ -4205,7 +4388,7 @@ function refuseLegacyGo(paths, keys) {
|
|
|
4205
4388
|
for (const key of keys) {
|
|
4206
4389
|
if (!key.startsWith("canvas/")) continue;
|
|
4207
4390
|
const id2 = key.slice("canvas/".length);
|
|
4208
|
-
for (const file of canvasCodeFiles(
|
|
4391
|
+
for (const file of canvasCodeFiles(path25.join(paths.canvases, id2), project.canvas(id2)?.screens ?? [])) {
|
|
4209
4392
|
for (const line of legacyGoLines(file)) hits.add(`${relToRoot(paths, file)}:${line}`);
|
|
4210
4393
|
}
|
|
4211
4394
|
}
|
|
@@ -4221,18 +4404,18 @@ Still using go():
|
|
|
4221
4404
|
|
|
4222
4405
|
// src/cli/commands/preview.ts
|
|
4223
4406
|
import { spawn } from "child_process";
|
|
4224
|
-
import
|
|
4407
|
+
import fs30 from "fs";
|
|
4225
4408
|
|
|
4226
4409
|
// src/server/index.ts
|
|
4227
|
-
import
|
|
4410
|
+
import fs29 from "fs";
|
|
4228
4411
|
import http2 from "http";
|
|
4229
|
-
import
|
|
4412
|
+
import path28 from "path";
|
|
4230
4413
|
import sirv2 from "sirv";
|
|
4231
4414
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
4232
4415
|
|
|
4233
4416
|
// src/server/api.ts
|
|
4234
|
-
import
|
|
4235
|
-
import
|
|
4417
|
+
import fs27 from "fs";
|
|
4418
|
+
import path26 from "path";
|
|
4236
4419
|
function sendJson(res, status, body) {
|
|
4237
4420
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
4238
4421
|
res.end(JSON.stringify(body));
|
|
@@ -4283,17 +4466,18 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4283
4466
|
}
|
|
4284
4467
|
if (route2 === "/source") {
|
|
4285
4468
|
const rel = url.searchParams.get("path") ?? "";
|
|
4286
|
-
const file =
|
|
4287
|
-
|
|
4469
|
+
const file = path26.resolve(project.paths.root, rel);
|
|
4470
|
+
const listed = project.system()?.doc.components.some((component) => component.specimen === rel || component.sources.includes(rel));
|
|
4471
|
+
if (!rel || !listed || !isShowableSource(project.paths, file)) {
|
|
4288
4472
|
sendJson(res, 404, { error: "not found" });
|
|
4289
4473
|
return true;
|
|
4290
4474
|
}
|
|
4291
|
-
if (
|
|
4475
|
+
if (fs27.statSync(file).size > 2 * 1024 * 1024) {
|
|
4292
4476
|
sendJson(res, 413, { error: "file too large to show" });
|
|
4293
4477
|
return true;
|
|
4294
4478
|
}
|
|
4295
4479
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
4296
|
-
res.end(
|
|
4480
|
+
res.end(fs27.readFileSync(file, "utf8"));
|
|
4297
4481
|
return true;
|
|
4298
4482
|
}
|
|
4299
4483
|
if (route2 === "/icon" && (method === "PUT" || method === "DELETE")) {
|
|
@@ -4343,7 +4527,7 @@ async function handleApi(ctx, req, res, url) {
|
|
|
4343
4527
|
return true;
|
|
4344
4528
|
}
|
|
4345
4529
|
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=31536000, immutable" });
|
|
4346
|
-
|
|
4530
|
+
fs27.createReadStream(file).pipe(res);
|
|
4347
4531
|
return true;
|
|
4348
4532
|
}
|
|
4349
4533
|
return false;
|
|
@@ -4382,63 +4566,82 @@ var EventHub = class {
|
|
|
4382
4566
|
}
|
|
4383
4567
|
};
|
|
4384
4568
|
|
|
4569
|
+
// src/server/hosts.ts
|
|
4570
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
4571
|
+
var FRAME_HOST = "127.0.0.1";
|
|
4572
|
+
var VIEWER_HOST = "localhost";
|
|
4573
|
+
function parseHost(header) {
|
|
4574
|
+
const match = /^(\[[0-9a-f:.]+\]|[a-z0-9.-]+)(?::(\d{1,5}))?$/i.exec(header ?? "");
|
|
4575
|
+
if (!match) return null;
|
|
4576
|
+
return { name: match[1].toLowerCase(), port: match[2] ? Number(match[2]) : 80 };
|
|
4577
|
+
}
|
|
4578
|
+
function isAllowedHost(header, port, extra) {
|
|
4579
|
+
const host = parseHost(header);
|
|
4580
|
+
if (!host || host.port !== port) return false;
|
|
4581
|
+
return LOOPBACK.has(host.name) || host.name.endsWith(".localhost") || !!extra && host.name === extra.toLowerCase();
|
|
4582
|
+
}
|
|
4583
|
+
function isFrameApi(pathname, method) {
|
|
4584
|
+
if (pathname === "/api/health") return method === "GET";
|
|
4585
|
+
return /^\/api\/snapshots\/[^/]+\/[^/]+$/.test(pathname) && (method === "GET" || method === "POST");
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4385
4588
|
// src/server/state.ts
|
|
4386
|
-
import
|
|
4589
|
+
import fs28 from "fs";
|
|
4387
4590
|
import os4 from "os";
|
|
4388
|
-
import
|
|
4389
|
-
var stateFile2 = (paths) =>
|
|
4390
|
-
var logFile = (paths) =>
|
|
4591
|
+
import path27 from "path";
|
|
4592
|
+
var stateFile2 = (paths) => path27.join(paths.cache, "server.json");
|
|
4593
|
+
var logFile = (paths) => path27.join(paths.cache, "server.log");
|
|
4391
4594
|
function readState(paths) {
|
|
4392
4595
|
try {
|
|
4393
|
-
return JSON.parse(
|
|
4596
|
+
return JSON.parse(fs28.readFileSync(stateFile2(paths), "utf8"));
|
|
4394
4597
|
} catch {
|
|
4395
4598
|
return null;
|
|
4396
4599
|
}
|
|
4397
4600
|
}
|
|
4398
4601
|
function registryDir() {
|
|
4399
|
-
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || process.env.APPDATA ||
|
|
4400
|
-
return
|
|
4602
|
+
const base = process.platform === "win32" ? process.env.LOCALAPPDATA || process.env.APPDATA || path27.join(os4.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME || path27.join(os4.homedir(), ".cache");
|
|
4603
|
+
return path27.join(base, "foss-design", "previews");
|
|
4401
4604
|
}
|
|
4402
|
-
var entryFile = (pid) =>
|
|
4605
|
+
var entryFile = (pid) => path27.join(registryDir(), `${pid}.json`);
|
|
4403
4606
|
function writeState(paths, state) {
|
|
4404
|
-
|
|
4405
|
-
|
|
4607
|
+
fs28.mkdirSync(paths.cache, { recursive: true });
|
|
4608
|
+
fs28.writeFileSync(stateFile2(paths), `${JSON.stringify(state, null, 2)}
|
|
4406
4609
|
`);
|
|
4407
4610
|
try {
|
|
4408
|
-
|
|
4409
|
-
|
|
4611
|
+
fs28.mkdirSync(registryDir(), { recursive: true });
|
|
4612
|
+
fs28.writeFileSync(entryFile(state.pid), `${JSON.stringify(state, null, 2)}
|
|
4410
4613
|
`);
|
|
4411
4614
|
} catch {
|
|
4412
4615
|
}
|
|
4413
4616
|
}
|
|
4414
4617
|
function clearState(paths, pid) {
|
|
4415
4618
|
const state = readState(paths);
|
|
4416
|
-
|
|
4619
|
+
fs28.rmSync(entryFile(pid ?? state?.pid ?? -1), { force: true });
|
|
4417
4620
|
if (pid !== void 0 && state && state.pid !== pid) return;
|
|
4418
|
-
|
|
4621
|
+
fs28.rmSync(stateFile2(paths), { force: true });
|
|
4419
4622
|
}
|
|
4420
4623
|
function listPreviews() {
|
|
4421
4624
|
let files = [];
|
|
4422
4625
|
try {
|
|
4423
|
-
files =
|
|
4626
|
+
files = fs28.readdirSync(registryDir()).filter((name) => name.endsWith(".json"));
|
|
4424
4627
|
} catch {
|
|
4425
4628
|
return [];
|
|
4426
4629
|
}
|
|
4427
4630
|
const found = [];
|
|
4428
4631
|
for (const name of files) {
|
|
4429
|
-
const file =
|
|
4632
|
+
const file = path27.join(registryDir(), name);
|
|
4430
4633
|
let state = null;
|
|
4431
4634
|
try {
|
|
4432
|
-
state = JSON.parse(
|
|
4635
|
+
state = JSON.parse(fs28.readFileSync(file, "utf8"));
|
|
4433
4636
|
} catch {
|
|
4434
4637
|
}
|
|
4435
4638
|
if (!state?.pid || !pidAlive(state.pid)) {
|
|
4436
|
-
|
|
4639
|
+
fs28.rmSync(file, { force: true });
|
|
4437
4640
|
continue;
|
|
4438
4641
|
}
|
|
4439
4642
|
let recorded = null;
|
|
4440
4643
|
try {
|
|
4441
|
-
recorded = JSON.parse(
|
|
4644
|
+
recorded = JSON.parse(fs28.readFileSync(path27.join(state.root, ".design", ".cache", "server.json"), "utf8"));
|
|
4442
4645
|
} catch {
|
|
4443
4646
|
}
|
|
4444
4647
|
found.push({ ...state, tracked: recorded?.pid === state.pid });
|
|
@@ -4452,11 +4655,11 @@ async function stopPreview(entry) {
|
|
|
4452
4655
|
}
|
|
4453
4656
|
const until = Date.now() + 5e3;
|
|
4454
4657
|
while (Date.now() < until && pidAlive(entry.pid)) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4455
|
-
|
|
4456
|
-
const stateAt =
|
|
4658
|
+
fs28.rmSync(entryFile(entry.pid), { force: true });
|
|
4659
|
+
const stateAt = path27.join(entry.root, ".design", ".cache", "server.json");
|
|
4457
4660
|
try {
|
|
4458
|
-
if (JSON.parse(
|
|
4459
|
-
|
|
4661
|
+
if (JSON.parse(fs28.readFileSync(stateAt, "utf8")).pid === entry.pid)
|
|
4662
|
+
fs28.rmSync(stateAt, { force: true });
|
|
4460
4663
|
} catch {
|
|
4461
4664
|
}
|
|
4462
4665
|
}
|
|
@@ -4515,8 +4718,8 @@ function notFoundPage(res, title, text2) {
|
|
|
4515
4718
|
}
|
|
4516
4719
|
async function startDevServer(options) {
|
|
4517
4720
|
const paths = designPaths(options.root);
|
|
4518
|
-
if (!
|
|
4519
|
-
|
|
4721
|
+
if (!fs29.existsSync(paths.design)) throw new Error(`No .design folder in ${options.root}; run \`design init\``);
|
|
4722
|
+
fs29.mkdirSync(paths.cache, { recursive: true });
|
|
4520
4723
|
const snapshots = new SnapshotStore(paths.snapshots);
|
|
4521
4724
|
const project = new DesignProject(paths, DEV_URLS, snapshots.lookup);
|
|
4522
4725
|
const entries = new Entries(project);
|
|
@@ -4524,6 +4727,7 @@ async function startDevServer(options) {
|
|
|
4524
4727
|
const links = linkShippedPackages(paths, project.appDir());
|
|
4525
4728
|
const events = new EventHub();
|
|
4526
4729
|
const httpServer = http2.createServer();
|
|
4730
|
+
const listening = () => httpServer.address()?.port ?? 0;
|
|
4527
4731
|
dropStaleDepCache(paths, project.appDir());
|
|
4528
4732
|
const base = baseConfig(project);
|
|
4529
4733
|
const vite = await createServer({
|
|
@@ -4542,8 +4746,8 @@ async function startDevServer(options) {
|
|
|
4542
4746
|
// `.cache` is ours (server.log, snapshots, builds): a change there must not reload frames. The
|
|
4543
4747
|
// generated entries are modules the frames load, so those stay watched.
|
|
4544
4748
|
ignored: (file) => {
|
|
4545
|
-
const rel = toPosix(
|
|
4546
|
-
const inCache = rel !== "" && !rel.startsWith("..") && !
|
|
4749
|
+
const rel = toPosix(path28.relative(paths.cache, file));
|
|
4750
|
+
const inCache = rel !== "" && !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
4547
4751
|
return inCache && rel !== "entries" && !rel.startsWith("entries/");
|
|
4548
4752
|
}
|
|
4549
4753
|
},
|
|
@@ -4552,7 +4756,7 @@ async function startDevServer(options) {
|
|
|
4552
4756
|
forwardConsole: false
|
|
4553
4757
|
},
|
|
4554
4758
|
optimizeDeps: {
|
|
4555
|
-
entries: entries.files().map((file) => toPosix(
|
|
4759
|
+
entries: entries.files().map((file) => toPosix(path28.relative(paths.design, file)))
|
|
4556
4760
|
}
|
|
4557
4761
|
});
|
|
4558
4762
|
const publicDir = project.publicDir();
|
|
@@ -4564,16 +4768,21 @@ async function startDevServer(options) {
|
|
|
4564
4768
|
return null;
|
|
4565
4769
|
}
|
|
4566
4770
|
};
|
|
4567
|
-
const viewer =
|
|
4568
|
-
const viewerIndex =
|
|
4569
|
-
const serveViewerIndex = (res) => {
|
|
4570
|
-
|
|
4771
|
+
const viewer = fs29.existsSync(PKG.viewer) ? sirv2(PKG.viewer, { dev: true, etag: true }) : null;
|
|
4772
|
+
const viewerIndex = path28.join(PKG.viewer, "index.html");
|
|
4773
|
+
const serveViewerIndex = (req, res, url2) => {
|
|
4774
|
+
if (parseHost(req.headers.host)?.name === FRAME_HOST) {
|
|
4775
|
+
res.writeHead(302, { location: `http://${VIEWER_HOST}:${listening()}${url2.pathname}${url2.search}` });
|
|
4776
|
+
res.end();
|
|
4777
|
+
return;
|
|
4778
|
+
}
|
|
4779
|
+
const html = fs29.readFileSync(viewerIndex, "utf8").replace(/<head>/i, '<head>\n<base href="/">');
|
|
4571
4780
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
4572
4781
|
res.end(html);
|
|
4573
4782
|
};
|
|
4574
4783
|
const ctx = { project, events, snapshots };
|
|
4575
4784
|
const systemCssUrl = `/${relToDesign(paths, entries.systemCssFile())}`;
|
|
4576
|
-
const bootUrl = `/${relToDesign(paths,
|
|
4785
|
+
const bootUrl = `/${relToDesign(paths, path28.join(entries.dir, HTML_BOOT))}`;
|
|
4577
4786
|
const frameHead = (source, extra = {}) => {
|
|
4578
4787
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
4579
4788
|
return {
|
|
@@ -4594,7 +4803,7 @@ async function startDevServer(options) {
|
|
|
4594
4803
|
notFoundPage(res, "Screen not found", `No screen "${id2}" in "${canvas}". Check canvas.json.`);
|
|
4595
4804
|
return;
|
|
4596
4805
|
}
|
|
4597
|
-
if (!
|
|
4806
|
+
if (!fs29.existsSync(entries.entryFile(source))) entries.sync();
|
|
4598
4807
|
const entryUrl = `/${relToDesign(paths, entries.entryFile(source))}`;
|
|
4599
4808
|
const html = await vite.transformIndexHtml(url2.pathname + url2.search, moduleShell(frameHead(source), entryUrl));
|
|
4600
4809
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -4603,8 +4812,8 @@ async function startDevServer(options) {
|
|
|
4603
4812
|
};
|
|
4604
4813
|
const serveHtmlScreen = async (res, url2) => {
|
|
4605
4814
|
const rel = decodeURIComponent(url2.pathname.slice("/_fs/".length));
|
|
4606
|
-
const file =
|
|
4607
|
-
if (!isInside(paths.design, file) || !
|
|
4815
|
+
const file = path28.resolve(paths.design, rel);
|
|
4816
|
+
if (!isInside(paths.design, file) || !fs29.existsSync(file)) {
|
|
4608
4817
|
notFoundPage(res, "Screen not found", `No file .design/${rel}`);
|
|
4609
4818
|
return;
|
|
4610
4819
|
}
|
|
@@ -4614,13 +4823,13 @@ async function startDevServer(options) {
|
|
|
4614
4823
|
key: `${canvas}/${id2}`,
|
|
4615
4824
|
canvas,
|
|
4616
4825
|
id: id2,
|
|
4617
|
-
title:
|
|
4826
|
+
title: path28.basename(file),
|
|
4618
4827
|
file,
|
|
4619
4828
|
format: "html",
|
|
4620
4829
|
props: {},
|
|
4621
4830
|
system: true
|
|
4622
4831
|
};
|
|
4623
|
-
const raw =
|
|
4832
|
+
const raw = fs29.readFileSync(file, "utf8");
|
|
4624
4833
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
4625
4834
|
const html = injectIntoHtml(
|
|
4626
4835
|
raw,
|
|
@@ -4636,8 +4845,16 @@ async function startDevServer(options) {
|
|
|
4636
4845
|
if (res.headersSent) return res.end();
|
|
4637
4846
|
sendJson(res, 500, { error: error?.stack ?? String(error) });
|
|
4638
4847
|
};
|
|
4848
|
+
if (!isAllowedHost(req.headers.host, listening(), options.host)) {
|
|
4849
|
+
sendJson(res, 403, { error: `This preview server answers on localhost:${listening()} only.` });
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4639
4852
|
try {
|
|
4640
4853
|
if (url2.pathname.startsWith("/api/")) {
|
|
4854
|
+
if (parseHost(req.headers.host)?.name === FRAME_HOST && !isFrameApi(url2.pathname, req.method ?? "GET")) {
|
|
4855
|
+
sendJson(res, 403, { error: `Frames' host: open the viewer on http://${VIEWER_HOST}:${listening()}.` });
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4641
4858
|
handleApi(ctx, req, res, url2).then((handled) => {
|
|
4642
4859
|
if (!handled) sendJson(res, 404, { error: "unknown endpoint" });
|
|
4643
4860
|
}).catch(fail);
|
|
@@ -4653,7 +4870,7 @@ async function startDevServer(options) {
|
|
|
4653
4870
|
}
|
|
4654
4871
|
if (url2.pathname.startsWith("/_fs/")) {
|
|
4655
4872
|
const rel = publicRel(url2.pathname.slice("/_fs/".length));
|
|
4656
|
-
if (publicDir && rel !== null && !project.isPublicFile(rel) &&
|
|
4873
|
+
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs29.existsSync(path28.join(publicDir, rel))) {
|
|
4657
4874
|
notFoundPage(res, "Not found", url2.pathname);
|
|
4658
4875
|
return;
|
|
4659
4876
|
}
|
|
@@ -4665,10 +4882,10 @@ async function startDevServer(options) {
|
|
|
4665
4882
|
const rel = publicRel(url2.pathname.slice(1));
|
|
4666
4883
|
if (servePublic && rel !== null && project.isPublicFile(rel))
|
|
4667
4884
|
servePublic(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
4668
|
-
else if (
|
|
4669
|
-
else serveViewerIndex(res);
|
|
4885
|
+
else if (path28.extname(url2.pathname)) notFoundPage(res, "Not found", url2.pathname);
|
|
4886
|
+
else serveViewerIndex(req, res, url2);
|
|
4670
4887
|
};
|
|
4671
|
-
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(res);
|
|
4888
|
+
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(req, res, url2);
|
|
4672
4889
|
else viewer(req, res, notViewer);
|
|
4673
4890
|
} else notFoundPage(res, "Viewer is not built", "Run `pnpm build` in the foss-design package.");
|
|
4674
4891
|
} catch (error) {
|
|
@@ -4693,7 +4910,7 @@ async function startDevServer(options) {
|
|
|
4693
4910
|
};
|
|
4694
4911
|
vite.watcher.on("all", (event, file) => {
|
|
4695
4912
|
if (publicDir && isInside(publicDir, file)) project.invalidatePublic();
|
|
4696
|
-
const rel = toPosix(
|
|
4913
|
+
const rel = toPosix(path28.relative(paths.design, file));
|
|
4697
4914
|
if (rel.startsWith("..") || rel.startsWith(".cache/") || rel.startsWith("node_modules/")) return;
|
|
4698
4915
|
if (rel === "design.json") {
|
|
4699
4916
|
queue("config", () => {
|
|
@@ -4804,8 +5021,8 @@ async function ensureServer(paths, options) {
|
|
|
4804
5021
|
const recorded = readState(paths);
|
|
4805
5022
|
if (recorded && pidAlive(recorded.pid)) await stopPreview(recorded);
|
|
4806
5023
|
for (const other of listPreviews()) if (other.root === paths.root) await stopPreview(other);
|
|
4807
|
-
|
|
4808
|
-
const log =
|
|
5024
|
+
fs30.mkdirSync(paths.cache, { recursive: true });
|
|
5025
|
+
const log = fs30.openSync(logFile(paths), "w");
|
|
4809
5026
|
const args = [PKG.cli, "preview", "--foreground", "--root", paths.root];
|
|
4810
5027
|
if (options.port) args.push("--port", String(options.port));
|
|
4811
5028
|
const child = spawn(process.execPath, args, {
|
|
@@ -4817,7 +5034,7 @@ async function ensureServer(paths, options) {
|
|
|
4817
5034
|
child.unref();
|
|
4818
5035
|
const state = await waitForServer(paths, 4e4);
|
|
4819
5036
|
if (!state) {
|
|
4820
|
-
const tail =
|
|
5037
|
+
const tail = fs30.existsSync(logFile(paths)) ? fs30.readFileSync(logFile(paths), "utf8").split("\n").slice(-25) : [];
|
|
4821
5038
|
throw new CliError(`The preview server did not start.
|
|
4822
5039
|
${tail.join("\n")}`);
|
|
4823
5040
|
}
|
|
@@ -4979,6 +5196,7 @@ function runPull(paths, units, options) {
|
|
|
4979
5196
|
|
|
4980
5197
|
// src/cli/commands/canvases.ts
|
|
4981
5198
|
var publicUrl = (host, publicId) => `${host.replace(/\/+$/, "")}/s/${publicId}`;
|
|
5199
|
+
var screenUrl = (host, publicId) => `${host.replace(/\/+$/, "")}/a/${publicId}`;
|
|
4982
5200
|
function ago(iso) {
|
|
4983
5201
|
const seconds2 = Math.max(0, (Date.now() - Date.parse(iso)) / 1e3);
|
|
4984
5202
|
if (!Number.isFinite(seconds2)) return "";
|
|
@@ -5069,16 +5287,35 @@ async function runUrl(paths, value, options) {
|
|
|
5069
5287
|
const unit = await remoteCanvas(client, link.project, canvas);
|
|
5070
5288
|
const url = webUrl(link, canvasUnit(canvas));
|
|
5071
5289
|
const pub = unit.publicId ? publicUrl(link.host, unit.publicId) : null;
|
|
5072
|
-
|
|
5290
|
+
const screens = unit.screens.map((s) => ({ screen: s.item, publicUrl: screenUrl(link.host, s.publicId) }));
|
|
5291
|
+
if (options.json)
|
|
5292
|
+
return print(JSON.stringify({ canvas, url, publicUrl: pub, screens, archived: unit.archived }, null, 2));
|
|
5073
5293
|
print(url);
|
|
5074
5294
|
if (pub) print(`${dim("public")} ${pub}`);
|
|
5295
|
+
for (const screen of screens) print(`${dim(`public ${screen.screen}`)} ${screen.publicUrl}`);
|
|
5075
5296
|
if (unit.archived) print(dim("archived \u2014 `design unarchive` brings it back"));
|
|
5076
5297
|
}
|
|
5077
5298
|
async function runPublish(paths, value, publish) {
|
|
5078
5299
|
const verb = publish ? "publish" : "unpublish";
|
|
5079
|
-
const
|
|
5300
|
+
const usage = `design ${verb} <canvas>[/<screen>]`;
|
|
5301
|
+
const slash = value?.indexOf("/") ?? -1;
|
|
5302
|
+
const screen = value && slash > 0 ? value.slice(slash + 1) : null;
|
|
5303
|
+
const canvas = canvasArg(value && slash > 0 ? value.slice(0, slash) : value, usage);
|
|
5304
|
+
if (screen === "") throw new CliError(`Usage: ${usage}`);
|
|
5080
5305
|
const { link, client } = cloud(paths);
|
|
5081
|
-
await remoteCanvas(client, link.project, canvas);
|
|
5306
|
+
const unit = await remoteCanvas(client, link.project, canvas);
|
|
5307
|
+
if (screen) {
|
|
5308
|
+
if (!publish && !unit.screens.some((s) => s.item === screen))
|
|
5309
|
+
return print(dim(`${canvas}/${screen} has no public link`));
|
|
5310
|
+
const { publicId: publicId2 } = await client.screenAction(link.project, canvas, screen, verb);
|
|
5311
|
+
if (publish) {
|
|
5312
|
+
print(
|
|
5313
|
+
`${green("\u2713")} Published ${bold(`${canvas}/${screen}`)} on its own: anyone with the link sees this screen alone`
|
|
5314
|
+
);
|
|
5315
|
+
if (publicId2) print(` ${cyan(screenUrl(link.host, publicId2))}`);
|
|
5316
|
+
} else print(`${green("\u2713")} ${bold(`${canvas}/${screen}`)} has no public link now`);
|
|
5317
|
+
return;
|
|
5318
|
+
}
|
|
5082
5319
|
const { publicId } = await client.canvasAction(link.project, canvas, verb);
|
|
5083
5320
|
if (publish) {
|
|
5084
5321
|
const id2 = publicId ?? (await remoteCanvas(client, link.project, canvas)).publicId;
|
|
@@ -5247,11 +5484,11 @@ async function renderFrames(jobs) {
|
|
|
5247
5484
|
await browser.close();
|
|
5248
5485
|
}
|
|
5249
5486
|
}
|
|
5250
|
-
async function runCheck(paths,
|
|
5487
|
+
async function runCheck(paths, targets2, options) {
|
|
5251
5488
|
const project = new DesignProject(paths, DEV_URLS);
|
|
5252
5489
|
const known = project.canvasIds();
|
|
5253
5490
|
const picked = /* @__PURE__ */ new Map();
|
|
5254
|
-
for (const target of
|
|
5491
|
+
for (const target of targets2) {
|
|
5255
5492
|
const [canvasId = "", screenId] = target.split("/");
|
|
5256
5493
|
if (!known.includes(canvasId))
|
|
5257
5494
|
throw new CliError(`No canvas "${canvasId}". Canvases: ${known.join(", ") || "none yet"}`);
|
|
@@ -5416,9 +5653,9 @@ function printLinkIssues(issues, project) {
|
|
|
5416
5653
|
}
|
|
5417
5654
|
|
|
5418
5655
|
// src/cli/commands/cloud.ts
|
|
5419
|
-
import
|
|
5656
|
+
import fs31 from "fs";
|
|
5420
5657
|
import os5 from "os";
|
|
5421
|
-
import
|
|
5658
|
+
import path29 from "path";
|
|
5422
5659
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5423
5660
|
async function runLogin() {
|
|
5424
5661
|
const host = cloudHost();
|
|
@@ -5519,7 +5756,7 @@ async function runLink(paths, wanted, options) {
|
|
|
5519
5756
|
print(`${dim("Already linked to")} ${project.name} ${dim(`(${project.id}, ${project.role})`)}`);
|
|
5520
5757
|
} else {
|
|
5521
5758
|
writeLink(paths, { host, project: project.id, units: {}, conflicts: {} });
|
|
5522
|
-
|
|
5759
|
+
fs31.rmSync(path29.join(cloudCache(paths), "incoming"), { recursive: true, force: true });
|
|
5523
5760
|
const replaced = current ? dim(` (replacing the link to ${current.project})`) : "";
|
|
5524
5761
|
print(`${green("linked")} .design \u2192 ${bold(project.name)} ${dim(`(${project.id}, ${project.role})`)}${replaced}`);
|
|
5525
5762
|
}
|
|
@@ -5546,8 +5783,8 @@ async function runLink(paths, wanted, options) {
|
|
|
5546
5783
|
}
|
|
5547
5784
|
|
|
5548
5785
|
// src/cli/commands/icon.ts
|
|
5549
|
-
import
|
|
5550
|
-
import
|
|
5786
|
+
import fs32 from "fs";
|
|
5787
|
+
import path30 from "path";
|
|
5551
5788
|
async function toCloud(paths, file) {
|
|
5552
5789
|
const link = readLink(paths);
|
|
5553
5790
|
if (!link) return;
|
|
@@ -5558,10 +5795,10 @@ async function toCloud(paths, file) {
|
|
|
5558
5795
|
}
|
|
5559
5796
|
const client = new CloudClient(link.host, credential.token);
|
|
5560
5797
|
try {
|
|
5561
|
-
const ext = file ?
|
|
5798
|
+
const ext = file ? path30.extname(file).slice(1) : null;
|
|
5562
5799
|
await client.setProjectIcon(
|
|
5563
5800
|
link.project,
|
|
5564
|
-
file && ext ? { data:
|
|
5801
|
+
file && ext ? { data: fs32.readFileSync(file), type: ICON_TYPES[ext] } : null
|
|
5565
5802
|
);
|
|
5566
5803
|
link.icon = file ? iconHash(file) : null;
|
|
5567
5804
|
writeLink(paths, link);
|
|
@@ -5574,35 +5811,35 @@ async function runIcon(paths, file, options) {
|
|
|
5574
5811
|
if (options.remove) {
|
|
5575
5812
|
const had = iconFile(paths);
|
|
5576
5813
|
writeIcon(paths, null);
|
|
5577
|
-
print(had ? `${green("Removed")} ${
|
|
5814
|
+
print(had ? `${green("Removed")} ${path30.relative(process.cwd(), had)}` : "The project has no icon.");
|
|
5578
5815
|
await toCloud(paths, null);
|
|
5579
5816
|
return;
|
|
5580
5817
|
}
|
|
5581
5818
|
if (!file) {
|
|
5582
5819
|
const current = iconFile(paths);
|
|
5583
5820
|
if (!current) return print("The project has no icon. Set one with `design icon <file.svg|png|webp>`.");
|
|
5584
|
-
return print(`${
|
|
5821
|
+
return print(`${path30.relative(process.cwd(), current)} ${dim(`${Math.ceil(fs32.statSync(current).size / 1024)} KB`)}`);
|
|
5585
5822
|
}
|
|
5586
5823
|
let data;
|
|
5587
5824
|
try {
|
|
5588
|
-
data =
|
|
5825
|
+
data = fs32.readFileSync(file);
|
|
5589
5826
|
} catch {
|
|
5590
5827
|
throw new CliError(`No file ${file}`);
|
|
5591
5828
|
}
|
|
5592
|
-
const problem = iconProblem(data,
|
|
5829
|
+
const problem = iconProblem(data, path30.extname(file).slice(1).toLowerCase() || void 0);
|
|
5593
5830
|
if (problem) throw new CliError(`${file}: ${problem}`);
|
|
5594
5831
|
const written = writeIcon(paths, data);
|
|
5595
|
-
print(`${green("Set")} the project icon: ${
|
|
5832
|
+
print(`${green("Set")} the project icon: ${path30.relative(process.cwd(), written)}`);
|
|
5596
5833
|
await toCloud(paths, written);
|
|
5597
5834
|
}
|
|
5598
5835
|
|
|
5599
5836
|
// src/cli/commands/import.ts
|
|
5600
|
-
import
|
|
5601
|
-
import
|
|
5837
|
+
import fs34 from "fs";
|
|
5838
|
+
import path32 from "path";
|
|
5602
5839
|
|
|
5603
5840
|
// src/import/claude-design.ts
|
|
5604
|
-
import
|
|
5605
|
-
import
|
|
5841
|
+
import fs33 from "fs";
|
|
5842
|
+
import path31 from "path";
|
|
5606
5843
|
|
|
5607
5844
|
// src/import/markup.ts
|
|
5608
5845
|
var VOID = /* @__PURE__ */ new Set([
|
|
@@ -6210,11 +6447,11 @@ function boardId(file, used) {
|
|
|
6210
6447
|
return id2;
|
|
6211
6448
|
}
|
|
6212
6449
|
function importClaudeDesign(dir, options = {}) {
|
|
6213
|
-
const canvasFile =
|
|
6214
|
-
if (!
|
|
6450
|
+
const canvasFile = path31.join(dir, "canvas.json");
|
|
6451
|
+
if (!fs33.existsSync(canvasFile))
|
|
6215
6452
|
throw new Error(`No canvas.json in ${dir}: point at the exported Claude Design project folder`);
|
|
6216
|
-
const source = JSON.parse(
|
|
6217
|
-
const templates =
|
|
6453
|
+
const source = JSON.parse(fs33.readFileSync(canvasFile, "utf8"));
|
|
6454
|
+
const templates = fs33.readdirSync(dir).filter((file) => file.endsWith(EXT));
|
|
6218
6455
|
if (!templates.length) throw new Error(`No ${EXT} files in ${dir}`);
|
|
6219
6456
|
const known = new Set(templates.map((file) => file.slice(0, -EXT.length)));
|
|
6220
6457
|
const warnings = /* @__PURE__ */ new Map();
|
|
@@ -6241,7 +6478,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
6241
6478
|
try {
|
|
6242
6479
|
files.set(
|
|
6243
6480
|
`${name}.jsx`,
|
|
6244
|
-
convertFile(name,
|
|
6481
|
+
convertFile(name, fs33.readFileSync(path31.join(dir, file), "utf8"), { known, routes, warn: warn2 })
|
|
6245
6482
|
);
|
|
6246
6483
|
} catch (error) {
|
|
6247
6484
|
warn2(`could not be converted: ${error.message}`);
|
|
@@ -6289,7 +6526,7 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
6289
6526
|
screens++;
|
|
6290
6527
|
}
|
|
6291
6528
|
const canvas = {
|
|
6292
|
-
title: options.title ?? source.title ??
|
|
6529
|
+
title: options.title ?? source.title ?? path31.basename(path31.resolve(dir)),
|
|
6293
6530
|
description: "Imported from Claude Design.",
|
|
6294
6531
|
// The templates bring their own styles; the design system's reset would restyle them.
|
|
6295
6532
|
system: false,
|
|
@@ -6301,21 +6538,21 @@ function importClaudeDesign(dir, options = {}) {
|
|
|
6301
6538
|
// src/cli/commands/import.ts
|
|
6302
6539
|
var slug = (text2) => text2.normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-").toLowerCase();
|
|
6303
6540
|
function runImport(paths, from, options) {
|
|
6304
|
-
const dir =
|
|
6305
|
-
if (!
|
|
6541
|
+
const dir = path32.resolve(from);
|
|
6542
|
+
if (!fs34.existsSync(dir) || !fs34.statSync(dir).isDirectory()) throw new CliError(`No folder ${from}`);
|
|
6306
6543
|
let result;
|
|
6307
6544
|
try {
|
|
6308
6545
|
result = importClaudeDesign(dir, { title: options.title });
|
|
6309
6546
|
} catch (error) {
|
|
6310
6547
|
throw new CliError(error.message);
|
|
6311
6548
|
}
|
|
6312
|
-
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(
|
|
6549
|
+
const id2 = options.canvas ?? (slug(String(result.canvas.title)) || slug(path32.basename(dir)) || "imported");
|
|
6313
6550
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is no canvas id; pass one with --canvas <id>`);
|
|
6314
|
-
const out =
|
|
6315
|
-
if (
|
|
6316
|
-
|
|
6317
|
-
for (const [name, content] of result.files)
|
|
6318
|
-
|
|
6551
|
+
const out = path32.join(paths.canvases, id2);
|
|
6552
|
+
if (fs34.existsSync(out)) throw new CliError(`.design/canvas/${id2} exists; pick another id with --canvas <id>`);
|
|
6553
|
+
fs34.mkdirSync(path32.join(out, "screens"), { recursive: true });
|
|
6554
|
+
for (const [name, content] of result.files) fs34.writeFileSync(path32.join(out, "screens", name), content);
|
|
6555
|
+
fs34.writeFileSync(path32.join(out, "canvas.json"), `${JSON.stringify(result.canvas, null, 2)}
|
|
6319
6556
|
`);
|
|
6320
6557
|
print(`${green("Imported")} ${bold(String(result.canvas.title))} \u2192 .design/canvas/${id2}`);
|
|
6321
6558
|
print(
|
|
@@ -6335,7 +6572,7 @@ function runImport(paths, from, options) {
|
|
|
6335
6572
|
|
|
6336
6573
|
// src/cli/commands/merge.ts
|
|
6337
6574
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6338
|
-
import
|
|
6575
|
+
import path33 from "path";
|
|
6339
6576
|
import { createInterface } from "readline/promises";
|
|
6340
6577
|
var KIND = {
|
|
6341
6578
|
text: "both sides changed lines, the markers show them",
|
|
@@ -6343,15 +6580,15 @@ var KIND = {
|
|
|
6343
6580
|
deleted_here: "deleted here, changed in the cloud; the cloud version is in place",
|
|
6344
6581
|
deleted_in_cloud: "deleted in the cloud, changed here; yours is in place"
|
|
6345
6582
|
};
|
|
6346
|
-
function matchTargets(paths, states,
|
|
6583
|
+
function matchTargets(paths, states, targets2) {
|
|
6347
6584
|
const picked = [];
|
|
6348
|
-
for (const target of
|
|
6585
|
+
for (const target of targets2) {
|
|
6349
6586
|
const unit = states.find((state) => state.unit === unitArg(target));
|
|
6350
6587
|
if (unit) {
|
|
6351
6588
|
for (const conflict of openConflicts(paths, unit)) picked.push({ state: unit, conflict });
|
|
6352
6589
|
continue;
|
|
6353
6590
|
}
|
|
6354
|
-
const rel = [
|
|
6591
|
+
const rel = [path33.relative(paths.design, path33.resolve(target)), target.replace(/^\.design\//, "")];
|
|
6355
6592
|
const found = states.flatMap(
|
|
6356
6593
|
(state) => state.conflicts.filter((item) => rel.includes(item.path)).map((conflict) => ({ state, conflict }))
|
|
6357
6594
|
);
|
|
@@ -6367,12 +6604,12 @@ function printOpen(paths, states) {
|
|
|
6367
6604
|
print(`${bold(state.unit)} ${dim(`merged with cloud r${state.headRev}`)}`);
|
|
6368
6605
|
for (const conflict of open2) {
|
|
6369
6606
|
print(` ${red("\u2717")} ${conflict.path} ${dim(KIND[conflict.kind])}`);
|
|
6370
|
-
const sides = ["here", "cloud", "base"].map((side) => `${side}: ${
|
|
6607
|
+
const sides = ["here", "cloud", "base"].map((side) => `${side}: ${path33.relative(process.cwd(), sideFile(paths, state.unit, conflict.path, side))}`).join(" \xB7 ");
|
|
6371
6608
|
print(` ${dim(sides)}`);
|
|
6372
6609
|
}
|
|
6373
6610
|
}
|
|
6374
6611
|
}
|
|
6375
|
-
async function runMerge(paths,
|
|
6612
|
+
async function runMerge(paths, targets2, options) {
|
|
6376
6613
|
if (options.here && options.cloud) throw new CliError("Pick one side: --here or --cloud");
|
|
6377
6614
|
const link = requireLink(paths);
|
|
6378
6615
|
const states = mergingUnits(paths);
|
|
@@ -6388,7 +6625,7 @@ async function runMerge(paths, targets, options) {
|
|
|
6388
6625
|
};
|
|
6389
6626
|
if (options.here || options.cloud) {
|
|
6390
6627
|
const side = options.here ? "here" : "cloud";
|
|
6391
|
-
const picked =
|
|
6628
|
+
const picked = targets2.length ? matchTargets(paths, states, targets2) : states.flatMap((state) => openConflicts(paths, state).map((conflict) => ({ state, conflict })));
|
|
6392
6629
|
for (const { state, conflict } of picked) {
|
|
6393
6630
|
resolveConflict(paths, state, conflict.path, side);
|
|
6394
6631
|
print(`${green("\u2713")} ${conflict.path} ${dim(`(${side === "here" ? "yours" : "the cloud's"})`)}`);
|
|
@@ -6397,7 +6634,7 @@ async function runMerge(paths, targets, options) {
|
|
|
6397
6634
|
return;
|
|
6398
6635
|
}
|
|
6399
6636
|
if (options.done) {
|
|
6400
|
-
const only =
|
|
6637
|
+
const only = targets2.length ? targets2.map(unitArg) : void 0;
|
|
6401
6638
|
close(true, only);
|
|
6402
6639
|
const left2 = states.filter(
|
|
6403
6640
|
(state) => (!only || only.includes(state.unit)) && openConflicts(paths, state).some((c) => c.kind === "text")
|
|
@@ -6448,7 +6685,7 @@ async function runMerge(paths, targets, options) {
|
|
|
6448
6685
|
if (answer === "s") break;
|
|
6449
6686
|
if (answer === "e" && edit) {
|
|
6450
6687
|
const editor = process.env.VISUAL || process.env.EDITOR || "vi";
|
|
6451
|
-
spawnSync2(editor, [
|
|
6688
|
+
spawnSync2(editor, [path33.join(paths.design, conflict.path)], { stdio: "inherit", shell: true });
|
|
6452
6689
|
if (!openConflicts(paths, state).some((item) => item.path === conflict.path)) break;
|
|
6453
6690
|
print(yellow(" Conflict markers are still there."));
|
|
6454
6691
|
}
|
|
@@ -6467,7 +6704,7 @@ async function runMerge(paths, targets, options) {
|
|
|
6467
6704
|
}
|
|
6468
6705
|
|
|
6469
6706
|
// src/cli/commands/rename.ts
|
|
6470
|
-
import
|
|
6707
|
+
import fs35 from "fs";
|
|
6471
6708
|
async function runRename(paths, name) {
|
|
6472
6709
|
const trimmed = name.trim();
|
|
6473
6710
|
if (!trimmed) throw new CliError("Usage: design rename <name>");
|
|
@@ -6482,17 +6719,17 @@ async function runRename(paths, name) {
|
|
|
6482
6719
|
}
|
|
6483
6720
|
let config = {};
|
|
6484
6721
|
try {
|
|
6485
|
-
config = JSON.parse(
|
|
6722
|
+
config = JSON.parse(fs35.readFileSync(paths.config, "utf8"));
|
|
6486
6723
|
} catch {
|
|
6487
6724
|
}
|
|
6488
|
-
|
|
6725
|
+
fs35.writeFileSync(paths.config, `${JSON.stringify({ ...config, name: trimmed }, null, 2)}
|
|
6489
6726
|
`);
|
|
6490
6727
|
print(`${green("Renamed")} the project to ${trimmed} (.design/design.json)`);
|
|
6491
6728
|
}
|
|
6492
6729
|
|
|
6493
6730
|
// src/cli/commands/scaffold.ts
|
|
6494
|
-
import
|
|
6495
|
-
import
|
|
6731
|
+
import fs36 from "fs";
|
|
6732
|
+
import path34 from "path";
|
|
6496
6733
|
|
|
6497
6734
|
// src/cli/templates.ts
|
|
6498
6735
|
var TOKENS_CSS = `/*
|
|
@@ -6703,40 +6940,40 @@ function mainScreen(canvasId) {
|
|
|
6703
6940
|
|
|
6704
6941
|
// src/cli/commands/scaffold.ts
|
|
6705
6942
|
function write(file, content, root) {
|
|
6706
|
-
if (
|
|
6707
|
-
print(` ${dim("exists ")} ${
|
|
6943
|
+
if (fs36.existsSync(file)) {
|
|
6944
|
+
print(` ${dim("exists ")} ${path34.relative(root, file)}`);
|
|
6708
6945
|
return false;
|
|
6709
6946
|
}
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
print(` ${green("created")} ${
|
|
6947
|
+
fs36.mkdirSync(path34.dirname(file), { recursive: true });
|
|
6948
|
+
fs36.writeFileSync(file, content);
|
|
6949
|
+
print(` ${green("created")} ${path34.relative(root, file)}`);
|
|
6713
6950
|
return true;
|
|
6714
6951
|
}
|
|
6715
6952
|
function ensureGitignored(root) {
|
|
6716
|
-
const file =
|
|
6717
|
-
const current =
|
|
6953
|
+
const file = path34.join(root, ".gitignore");
|
|
6954
|
+
const current = fs36.existsSync(file) ? fs36.readFileSync(file, "utf8") : "";
|
|
6718
6955
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
6719
6956
|
if (covered) return "present";
|
|
6720
6957
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
6721
|
-
|
|
6958
|
+
fs36.writeFileSync(file, `${current}${prefix}${current ? "\n" : ""}# Design canvases (foss-design)
|
|
6722
6959
|
.design/
|
|
6723
6960
|
`);
|
|
6724
6961
|
return "added";
|
|
6725
6962
|
}
|
|
6726
6963
|
function projectName(root) {
|
|
6727
6964
|
try {
|
|
6728
|
-
const manifest = JSON.parse(
|
|
6965
|
+
const manifest = JSON.parse(fs36.readFileSync(path34.join(root, "package.json"), "utf8"));
|
|
6729
6966
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
6730
6967
|
} catch {
|
|
6731
6968
|
}
|
|
6732
|
-
return
|
|
6969
|
+
return path34.basename(root);
|
|
6733
6970
|
}
|
|
6734
6971
|
function runInit(root, options) {
|
|
6735
6972
|
const paths = designPaths(root);
|
|
6736
6973
|
print(`Setting up ${DESIGN_DIR} in ${root}`);
|
|
6737
6974
|
write(paths.config, `${JSON.stringify({ name: options.name ?? projectName(root) }, null, 2)}
|
|
6738
6975
|
`, root);
|
|
6739
|
-
|
|
6976
|
+
fs36.mkdirSync(paths.canvases, { recursive: true });
|
|
6740
6977
|
if (options.gitignore) {
|
|
6741
6978
|
const result = ensureGitignored(root);
|
|
6742
6979
|
print(` ${result === "added" ? green("ignored") : dim("ignored")} .design/ in .gitignore`);
|
|
@@ -6750,44 +6987,44 @@ function runSystemInit(root, options) {
|
|
|
6750
6987
|
const paths = designPaths(root);
|
|
6751
6988
|
let base = projectName(root);
|
|
6752
6989
|
try {
|
|
6753
|
-
base = JSON.parse(
|
|
6990
|
+
base = JSON.parse(fs36.readFileSync(paths.config, "utf8")).name || base;
|
|
6754
6991
|
} catch {
|
|
6755
6992
|
}
|
|
6756
6993
|
const name = options.name ?? `${base} design system`;
|
|
6757
|
-
print(`Scaffolding the design system in ${
|
|
6758
|
-
write(
|
|
6994
|
+
print(`Scaffolding the design system in ${path34.relative(root, paths.system)}`);
|
|
6995
|
+
write(path34.join(paths.system, "system.json"), `${JSON.stringify({ name }, null, 2)}
|
|
6759
6996
|
`, root);
|
|
6760
|
-
write(
|
|
6997
|
+
write(path34.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
6761
6998
|
if (!options.empty) {
|
|
6762
|
-
write(
|
|
6763
|
-
write(
|
|
6764
|
-
write(
|
|
6999
|
+
write(path34.join(paths.system, "guidelines", "01-overview.md"), OVERVIEW_MD, root);
|
|
7000
|
+
write(path34.join(paths.system, "components", "button.tsx"), BUTTON_TSX, root);
|
|
7001
|
+
write(path34.join(paths.system, "specimens", "button.tsx"), BUTTON_SPECIMEN_TSX, root);
|
|
6765
7002
|
}
|
|
6766
7003
|
}
|
|
6767
7004
|
function runNew(root, id2, options) {
|
|
6768
7005
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
6769
7006
|
const paths = designPaths(root);
|
|
6770
|
-
const dir =
|
|
6771
|
-
if (
|
|
7007
|
+
const dir = path34.join(paths.canvases, id2);
|
|
7008
|
+
if (fs36.existsSync(path34.join(dir, "canvas.json"))) throw new CliError(`Canvas "${id2}" already exists in ${dir}`);
|
|
6772
7009
|
print(`Creating canvas "${id2}"`);
|
|
6773
7010
|
const title = options.title ?? titleize(id2);
|
|
6774
7011
|
if (options.empty) {
|
|
6775
7012
|
write(
|
|
6776
|
-
|
|
7013
|
+
path34.join(dir, "canvas.json"),
|
|
6777
7014
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
6778
7015
|
`,
|
|
6779
7016
|
root
|
|
6780
7017
|
);
|
|
6781
|
-
|
|
7018
|
+
fs36.mkdirSync(path34.join(dir, "screens"), { recursive: true });
|
|
6782
7019
|
} else {
|
|
6783
|
-
write(
|
|
6784
|
-
write(
|
|
7020
|
+
write(path34.join(dir, "canvas.json"), canvasJson(title), root);
|
|
7021
|
+
write(path34.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
6785
7022
|
}
|
|
6786
7023
|
}
|
|
6787
7024
|
|
|
6788
7025
|
// src/cli/commands/shot.ts
|
|
6789
|
-
import
|
|
6790
|
-
import
|
|
7026
|
+
import fs37 from "fs";
|
|
7027
|
+
import path35 from "path";
|
|
6791
7028
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
6792
7029
|
async function runShot(paths, target, options) {
|
|
6793
7030
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -6803,15 +7040,15 @@ async function runShot(paths, target, options) {
|
|
|
6803
7040
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
6804
7041
|
}
|
|
6805
7042
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
6806
|
-
const outBase =
|
|
6807
|
-
const outDir =
|
|
6808
|
-
|
|
7043
|
+
const outBase = path35.resolve(options.out ?? paths.shots);
|
|
7044
|
+
const outDir = path35.join(outBase, canvasId);
|
|
7045
|
+
fs37.mkdirSync(outDir, { recursive: true });
|
|
6809
7046
|
const base = await serverBase(paths);
|
|
6810
7047
|
const browser = await launchChrome();
|
|
6811
7048
|
let failed = false;
|
|
6812
7049
|
try {
|
|
6813
7050
|
if (options.overview) {
|
|
6814
|
-
if (!
|
|
7051
|
+
if (!fs37.existsSync(PKG.viewer)) throw new CliError("The viewer is not built, so there is no canvas to shoot.");
|
|
6815
7052
|
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);
|
|
6816
7053
|
for (const pageId of pages) {
|
|
6817
7054
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1200 } });
|
|
@@ -6827,7 +7064,7 @@ async function runShot(paths, target, options) {
|
|
|
6827
7064
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
6828
7065
|
}
|
|
6829
7066
|
await page.waitForTimeout(400);
|
|
6830
|
-
const file =
|
|
7067
|
+
const file = path35.join(outDir, `${pageId}.overview.png`);
|
|
6831
7068
|
await page.screenshot({ path: file });
|
|
6832
7069
|
await page.close();
|
|
6833
7070
|
print(file);
|
|
@@ -6856,7 +7093,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
6856
7093
|
let failed = false;
|
|
6857
7094
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
6858
7095
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
6859
|
-
const file =
|
|
7096
|
+
const file = path35.join(outDir, `${job.id}.${job.theme}.png`);
|
|
6860
7097
|
try {
|
|
6861
7098
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
6862
7099
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -6888,8 +7125,8 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
6888
7125
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
6889
7126
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
6890
7127
|
}
|
|
6891
|
-
const outDir =
|
|
6892
|
-
|
|
7128
|
+
const outDir = path35.join(path35.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
7129
|
+
fs37.mkdirSync(outDir, { recursive: true });
|
|
6893
7130
|
const base = await serverBase(paths);
|
|
6894
7131
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
6895
7132
|
const browser = await launchChrome();
|
|
@@ -6901,10 +7138,10 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
6901
7138
|
}
|
|
6902
7139
|
|
|
6903
7140
|
// src/cli/commands/snapshots.ts
|
|
6904
|
-
async function runSnapshots(paths,
|
|
7141
|
+
async function runSnapshots(paths, targets2, options) {
|
|
6905
7142
|
const known = new DesignProject(paths, DEV_URLS).canvasIds();
|
|
6906
7143
|
const picked = /* @__PURE__ */ new Map();
|
|
6907
|
-
for (const target of
|
|
7144
|
+
for (const target of targets2) {
|
|
6908
7145
|
const [canvas = "", screen] = target.split("/");
|
|
6909
7146
|
if (!known.includes(canvas))
|
|
6910
7147
|
throw new CliError(`No canvas "${canvas}". Canvases: ${known.join(", ") || "none yet"}`);
|
|
@@ -6939,7 +7176,7 @@ async function runSnapshots(paths, targets, options) {
|
|
|
6939
7176
|
}
|
|
6940
7177
|
|
|
6941
7178
|
// src/cli/commands/cloud-status.ts
|
|
6942
|
-
import
|
|
7179
|
+
import path36 from "path";
|
|
6943
7180
|
var STATE_TEXT = {
|
|
6944
7181
|
in_sync: green,
|
|
6945
7182
|
local_changes: yellow,
|
|
@@ -6962,7 +7199,7 @@ function detail(paths, status) {
|
|
|
6962
7199
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
6963
7200
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
6964
7201
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
6965
|
-
if (status.conflictRev !== void 0) parts.push(`merge ${
|
|
7202
|
+
if (status.conflictRev !== void 0) parts.push(`merge ${path36.relative(paths.root, incomingDir(paths, status.key))}`);
|
|
6966
7203
|
return parts.join(" \xB7 ");
|
|
6967
7204
|
}
|
|
6968
7205
|
async function printCloudStatus(paths, label) {
|
|
@@ -7138,12 +7375,15 @@ ${bold("Cloud")} ${dim("units: system (design.json + system/) and canvas/<id>;
|
|
|
7138
7375
|
merge [<unit|file>\u2026] [--here|--cloud] Conflicts a merge left: lists them (in a terminal, walks
|
|
7139
7376
|
[--done] [--json] through them); --here / --cloud settles files or units;
|
|
7140
7377
|
--done closes a merge once no markers are left
|
|
7141
|
-
url <canvas> [--json] The canvas in the web app, and its public
|
|
7378
|
+
url <canvas> [--json] The canvas in the web app, and its public links: the canvas's
|
|
7379
|
+
and its screens' published on their own
|
|
7142
7380
|
history <canvas|system> [--json] Stored revisions, newest first: when, who, screens, size
|
|
7143
7381
|
rollback <canvas> <rev> Make an old revision current again in the cloud (a new
|
|
7144
7382
|
revision), then pull it here
|
|
7145
|
-
publish <canvas>
|
|
7146
|
-
|
|
7383
|
+
publish <canvas>[/<screen>] Give the canvas a public link anyone can open (owner only);
|
|
7384
|
+
with a screen, a link to that screen alone, which names
|
|
7385
|
+
nothing of the project and opens no other screen
|
|
7386
|
+
unpublish <canvas>[/<screen>] Turn the public link off
|
|
7147
7387
|
archive <canvas> Archive the canvas in the cloud: out of the list and pushes,
|
|
7148
7388
|
history kept
|
|
7149
7389
|
unarchive <canvas> Bring an archived canvas back
|
|
@@ -7157,7 +7397,7 @@ ${bold("Environment")}
|
|
|
7157
7397
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
7158
7398
|
`;
|
|
7159
7399
|
function projectPaths(rootOption, requireDesign = true) {
|
|
7160
|
-
const found = rootOption ?
|
|
7400
|
+
const found = rootOption ? path37.resolve(rootOption) : findProjectRoot();
|
|
7161
7401
|
if (!found) {
|
|
7162
7402
|
if (!requireDesign) return designPaths(process.cwd());
|
|
7163
7403
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -7210,7 +7450,7 @@ async function main(argv) {
|
|
|
7210
7450
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
7211
7451
|
switch (command) {
|
|
7212
7452
|
case "init":
|
|
7213
|
-
return runInit(values.root ?
|
|
7453
|
+
return runInit(values.root ? path37.resolve(values.root) : process.cwd(), {
|
|
7214
7454
|
name: values.name,
|
|
7215
7455
|
gitignore: !values["no-gitignore"]
|
|
7216
7456
|
});
|
|
@@ -7317,12 +7557,12 @@ function relaunchFromRealPath() {
|
|
|
7317
7557
|
let realScript;
|
|
7318
7558
|
let realCwd;
|
|
7319
7559
|
try {
|
|
7320
|
-
realScript =
|
|
7321
|
-
realCwd =
|
|
7560
|
+
realScript = fs38.realpathSync.native(script);
|
|
7561
|
+
realCwd = fs38.realpathSync.native(process.cwd());
|
|
7322
7562
|
} catch {
|
|
7323
7563
|
return false;
|
|
7324
7564
|
}
|
|
7325
|
-
const same = (a, b) =>
|
|
7565
|
+
const same = (a, b) => path37.resolve(a).toLowerCase() === path37.resolve(b).toLowerCase();
|
|
7326
7566
|
if (same(realScript, script) && same(realCwd, process.cwd())) return false;
|
|
7327
7567
|
const result = spawnSync3(process.execPath, [...process.execArgv, realScript, ...process.argv.slice(2)], {
|
|
7328
7568
|
stdio: "inherit",
|