pixelkiln 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +48 -0
- package/NAMING.md +15 -15
- package/PROVIDERS.md +13 -13
- package/README.md +22 -9
- package/dist/cli.d.ts +18 -1
- package/dist/cli.js +588 -142
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +291 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -1
- package/dist/index.d.ts +168 -1
- package/dist/index.js +280 -21
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +1 -1
- package/docs/ARCHITECTURE.md +4 -2
- package/docs/CLI.md +47 -0
- package/docs/ENDPOINTS.md +39 -38
- package/docs/GENERATORS.md +1 -1
- package/docs/RECOVERY.md +44 -0
- package/docs/TILES.md +1 -1
- package/examples/minimal/README.md +2 -2
- package/package.json +1 -1
- package/schema/workspace.schema.json +54 -0
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,8 @@ __export(index_exports, {
|
|
|
45
45
|
PixelLabProvider: () => PixelLabProvider,
|
|
46
46
|
StyleSchema: () => StyleSchema,
|
|
47
47
|
UnsupportedCapabilityError: () => UnsupportedCapabilityError,
|
|
48
|
+
WorkspaceProjectSchema: () => WorkspaceProjectSchema,
|
|
49
|
+
WorkspaceSchema: () => WorkspaceSchema,
|
|
48
50
|
adopt: () => adopt,
|
|
49
51
|
applyTags: () => applyTags,
|
|
50
52
|
auditStyle: () => auditStyle,
|
|
@@ -75,6 +77,8 @@ __export(index_exports, {
|
|
|
75
77
|
loadClaims: () => loadClaims,
|
|
76
78
|
loadLock: () => loadLock,
|
|
77
79
|
loadManifest: () => loadManifest,
|
|
80
|
+
loadSiblingManifests: () => loadSiblingManifests,
|
|
81
|
+
loadWorkspace: () => loadWorkspace,
|
|
78
82
|
lockKey: () => lockKey,
|
|
79
83
|
matchOrphanStyle: () => matchOrphanStyle,
|
|
80
84
|
measureBalanceChange: () => measureBalanceChange,
|
|
@@ -89,6 +93,7 @@ __export(index_exports, {
|
|
|
89
93
|
packStyle: () => packStyle,
|
|
90
94
|
paletteDistance: () => paletteDistance,
|
|
91
95
|
parseLock: () => parseLock,
|
|
96
|
+
parseWorkspace: () => parseWorkspace,
|
|
92
97
|
pngSize: () => pngSize,
|
|
93
98
|
poll: () => poll,
|
|
94
99
|
portableOutputPath: () => portableOutputPath,
|
|
@@ -102,6 +107,7 @@ __export(index_exports, {
|
|
|
102
107
|
resolveEntryOutputs: () => resolveEntryOutputs,
|
|
103
108
|
resolveOutputPath: () => resolveOutputPath,
|
|
104
109
|
resolvePackInputs: () => resolvePackInputs,
|
|
110
|
+
resolveProject: () => resolveProject,
|
|
105
111
|
resolveSpecEntryOutputs: () => resolveSpecEntryOutputs,
|
|
106
112
|
resolveSpecOutputs: () => resolveSpecOutputs,
|
|
107
113
|
resolveSpecs: () => resolveSpecs,
|
|
@@ -111,6 +117,7 @@ __export(index_exports, {
|
|
|
111
117
|
runPicker: () => runPicker,
|
|
112
118
|
runSalvage: () => runSalvage,
|
|
113
119
|
saveLock: () => saveLock,
|
|
120
|
+
saveWorkspace: () => saveWorkspace,
|
|
114
121
|
scanAssets: () => scanAssets,
|
|
115
122
|
selectEntryOutput: () => selectEntryOutput,
|
|
116
123
|
sha256: () => sha256,
|
|
@@ -126,11 +133,15 @@ __export(index_exports, {
|
|
|
126
133
|
tileFeatureOutputCount: () => tileFeatureOutputCount,
|
|
127
134
|
tileVariationCount: () => tileVariationCount,
|
|
128
135
|
tilesCost: () => tilesCost,
|
|
136
|
+
toPortablePath: () => toPortablePath,
|
|
129
137
|
totalSpend: () => totalSpend,
|
|
130
138
|
upsert: () => upsert,
|
|
131
139
|
validateCostEstimate: () => validateCostEstimate,
|
|
140
|
+
validateWorkspace: () => validateWorkspace,
|
|
132
141
|
verifyArtifactBundle: () => verifyArtifactBundle,
|
|
133
142
|
withArtifactManifest: () => withArtifactManifest,
|
|
143
|
+
workspaceClaims: () => workspaceClaims,
|
|
144
|
+
workspaceStatus: () => workspaceStatus,
|
|
134
145
|
writeArtifactBundle: () => writeArtifactBundle,
|
|
135
146
|
writeManagedArtifactBundle: () => writeManagedArtifactBundle
|
|
136
147
|
});
|
|
@@ -547,11 +558,11 @@ var PixelLabClient = class {
|
|
|
547
558
|
* common than the failure mode of retrying (a duplicate object), and a
|
|
548
559
|
* duplicate is visible and free to delete whereas a silent gap is neither.
|
|
549
560
|
*/
|
|
550
|
-
async request(
|
|
561
|
+
async request(path17, init, attempt = 0) {
|
|
551
562
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
552
563
|
let res;
|
|
553
564
|
try {
|
|
554
|
-
res = await fetch(`${BASE}${
|
|
565
|
+
res = await fetch(`${BASE}${path17}`, {
|
|
555
566
|
...init,
|
|
556
567
|
signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs),
|
|
557
568
|
headers: {
|
|
@@ -563,24 +574,24 @@ var PixelLabClient = class {
|
|
|
563
574
|
} catch (err) {
|
|
564
575
|
if (attempt < MAX_RETRIES) {
|
|
565
576
|
await sleep(backoffMs(attempt));
|
|
566
|
-
return this.request(
|
|
577
|
+
return this.request(path17, init, attempt + 1);
|
|
567
578
|
}
|
|
568
579
|
throw err;
|
|
569
580
|
}
|
|
570
581
|
if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
|
|
571
582
|
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
572
583
|
await sleep(waitMs);
|
|
573
|
-
return this.request(
|
|
584
|
+
return this.request(path17, init, attempt + 1);
|
|
574
585
|
}
|
|
575
586
|
const text = await res.text();
|
|
576
587
|
if (!res.ok) {
|
|
577
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
588
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path17} \u2192 ${res.status}`, res.status, text);
|
|
578
589
|
}
|
|
579
590
|
if (!text) return {};
|
|
580
591
|
try {
|
|
581
592
|
return JSON.parse(text);
|
|
582
593
|
} catch {
|
|
583
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
594
|
+
throw new Error(`${init?.method ?? "GET"} ${path17} returned invalid JSON`);
|
|
584
595
|
}
|
|
585
596
|
}
|
|
586
597
|
async balance() {
|
|
@@ -1598,8 +1609,8 @@ var import_promises = require("fs/promises");
|
|
|
1598
1609
|
function sha256(data) {
|
|
1599
1610
|
return (0, import_node_crypto3.createHash)("sha256").update(data).digest("hex");
|
|
1600
1611
|
}
|
|
1601
|
-
async function sha256File(
|
|
1602
|
-
return sha256(await (0, import_promises.readFile)(
|
|
1612
|
+
async function sha256File(path17) {
|
|
1613
|
+
return sha256(await (0, import_promises.readFile)(path17));
|
|
1603
1614
|
}
|
|
1604
1615
|
function specHash(spec, styleImageHashes) {
|
|
1605
1616
|
return sha256(
|
|
@@ -2843,10 +2854,10 @@ function isSha256Hash(value) {
|
|
|
2843
2854
|
function parseCache(value) {
|
|
2844
2855
|
return HashCacheSchema.parse(value);
|
|
2845
2856
|
}
|
|
2846
|
-
async function loadCache(
|
|
2847
|
-
if (!(0, import_node_fs6.existsSync)(
|
|
2857
|
+
async function loadCache(path17) {
|
|
2858
|
+
if (!(0, import_node_fs6.existsSync)(path17)) return { version: 1, hashes: {} };
|
|
2848
2859
|
try {
|
|
2849
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises6.readFile)(
|
|
2860
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises6.readFile)(path17, "utf8")));
|
|
2850
2861
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
2851
2862
|
return {
|
|
2852
2863
|
version: 1,
|
|
@@ -2858,18 +2869,18 @@ async function loadCache(path15) {
|
|
|
2858
2869
|
return { version: 1, hashes: {} };
|
|
2859
2870
|
}
|
|
2860
2871
|
}
|
|
2861
|
-
async function saveCache(
|
|
2872
|
+
async function saveCache(path17, cache) {
|
|
2862
2873
|
const sorted = {};
|
|
2863
2874
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
2864
2875
|
const hash = cache.hashes[key];
|
|
2865
2876
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
2866
2877
|
sorted[key] = hash;
|
|
2867
2878
|
}
|
|
2868
|
-
await (0, import_promises6.mkdir)(import_node_path8.default.dirname(import_node_path8.default.resolve(
|
|
2869
|
-
const tmp = `${
|
|
2879
|
+
await (0, import_promises6.mkdir)(import_node_path8.default.dirname(import_node_path8.default.resolve(path17)), { recursive: true });
|
|
2880
|
+
const tmp = `${path17}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
2870
2881
|
try {
|
|
2871
2882
|
await (0, import_promises6.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
2872
|
-
await (0, import_promises6.rename)(tmp,
|
|
2883
|
+
await (0, import_promises6.rename)(tmp, path17);
|
|
2873
2884
|
} finally {
|
|
2874
2885
|
await (0, import_promises6.rm)(tmp, { force: true });
|
|
2875
2886
|
}
|
|
@@ -4602,6 +4613,7 @@ function mode(values) {
|
|
|
4602
4613
|
// src/pipeline/salvage.ts
|
|
4603
4614
|
var import_promises12 = require("fs/promises");
|
|
4604
4615
|
var import_node_fs13 = require("fs");
|
|
4616
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
4605
4617
|
async function loadClaims(lockPaths) {
|
|
4606
4618
|
const claimed = /* @__PURE__ */ new Set();
|
|
4607
4619
|
for (const p of lockPaths) {
|
|
@@ -4654,6 +4666,26 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
4654
4666
|
if (styleIds.length <= 1) return styleIds[0] ?? null;
|
|
4655
4667
|
return matchStyleByPattern(prompt, manifest);
|
|
4656
4668
|
}
|
|
4669
|
+
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
4670
|
+
const own = import_node_path15.default.resolve(ownManifestPath);
|
|
4671
|
+
const siblingManifestPaths = [
|
|
4672
|
+
.../* @__PURE__ */ new Set([
|
|
4673
|
+
...workspaceManifestPaths,
|
|
4674
|
+
...claimPaths.map((c) => import_node_path15.default.join(import_node_path15.default.dirname(import_node_path15.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
4675
|
+
])
|
|
4676
|
+
];
|
|
4677
|
+
const siblings = [];
|
|
4678
|
+
for (const siblingManifestPath of siblingManifestPaths) {
|
|
4679
|
+
if (import_node_path15.default.resolve(siblingManifestPath) === own) continue;
|
|
4680
|
+
if (!(0, import_node_fs13.existsSync)(siblingManifestPath)) continue;
|
|
4681
|
+
try {
|
|
4682
|
+
const { manifest } = await loadManifest(siblingManifestPath);
|
|
4683
|
+
siblings.push({ label: import_node_path15.default.basename(import_node_path15.default.dirname(siblingManifestPath)), manifest });
|
|
4684
|
+
} catch {
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return siblings;
|
|
4688
|
+
}
|
|
4657
4689
|
function groupOrphansByStyle(orphans, manifest, siblings = []) {
|
|
4658
4690
|
const matched = /* @__PURE__ */ new Map();
|
|
4659
4691
|
const elsewhere = /* @__PURE__ */ new Map();
|
|
@@ -4765,7 +4797,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
4765
4797
|
|
|
4766
4798
|
// src/pick/salvage-server.ts
|
|
4767
4799
|
var import_promises13 = require("fs/promises");
|
|
4768
|
-
var
|
|
4800
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
4769
4801
|
|
|
4770
4802
|
// src/pick/salvage-sheet.ts
|
|
4771
4803
|
var escapeHtml2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -4930,7 +4962,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
4930
4962
|
});
|
|
4931
4963
|
const html = renderSalvageSheet(orphans, {
|
|
4932
4964
|
styleId: ctx.styleId,
|
|
4933
|
-
importDir:
|
|
4965
|
+
importDir: import_node_path16.default.relative(process.cwd(), ctx.importDir) || "."
|
|
4934
4966
|
});
|
|
4935
4967
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
4936
4968
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -4959,9 +4991,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
4959
4991
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE3)) throw new Error("not a PNG");
|
|
4960
4992
|
decodePng(buf);
|
|
4961
4993
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
4962
|
-
const rel =
|
|
4963
|
-
const outFile =
|
|
4964
|
-
await (0, import_promises13.mkdir)(
|
|
4994
|
+
const rel = import_node_path16.default.join("_salvaged", `${assetId}.png`);
|
|
4995
|
+
const outFile = import_node_path16.default.resolve(ctx.importDir, rel);
|
|
4996
|
+
await (0, import_promises13.mkdir)(import_node_path16.default.dirname(outFile), { recursive: true });
|
|
4965
4997
|
await (0, import_promises13.writeFile)(outFile, buf);
|
|
4966
4998
|
ctx.manifest.assets[assetId] = {
|
|
4967
4999
|
prompt: orphan.prompt,
|
|
@@ -4988,7 +5020,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
4988
5020
|
error: null,
|
|
4989
5021
|
sourceUrl: orphan.previewUrl,
|
|
4990
5022
|
outputs: [{
|
|
4991
|
-
path: portableOutputPath(outFile,
|
|
5023
|
+
path: portableOutputPath(outFile, import_node_path16.default.dirname(ctx.manifestPath)),
|
|
4992
5024
|
sha256: sha256(buf)
|
|
4993
5025
|
}],
|
|
4994
5026
|
submittedAt: orphan.createdAt,
|
|
@@ -5020,6 +5052,233 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5020
5052
|
}
|
|
5021
5053
|
});
|
|
5022
5054
|
}
|
|
5055
|
+
|
|
5056
|
+
// src/workspace.ts
|
|
5057
|
+
var import_promises14 = require("fs/promises");
|
|
5058
|
+
var import_node_fs14 = require("fs");
|
|
5059
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
5060
|
+
var import_zod4 = require("zod");
|
|
5061
|
+
var WorkspaceProjectSchema = import_zod4.z.object({
|
|
5062
|
+
id: import_zod4.z.string().min(1),
|
|
5063
|
+
/** Manifest path, relative to the catalog file's own directory. */
|
|
5064
|
+
manifest: import_zod4.z.string().min(1),
|
|
5065
|
+
/** Lockfile path, relative to the catalog file's own directory. */
|
|
5066
|
+
lock: import_zod4.z.string().min(1),
|
|
5067
|
+
provider: import_zod4.z.string().min(1).default("pixellab"),
|
|
5068
|
+
/** Free-form label for a shared account, e.g. distinguishing sandboxes. */
|
|
5069
|
+
account: import_zod4.z.string().optional()
|
|
5070
|
+
}).strict();
|
|
5071
|
+
var WorkspaceSchema = import_zod4.z.object({
|
|
5072
|
+
version: import_zod4.z.literal(1),
|
|
5073
|
+
projects: import_zod4.z.array(WorkspaceProjectSchema).default([])
|
|
5074
|
+
}).strict();
|
|
5075
|
+
function parseWorkspace(raw) {
|
|
5076
|
+
const parsed = WorkspaceSchema.safeParse(raw);
|
|
5077
|
+
if (parsed.success) return parsed.data;
|
|
5078
|
+
throw new Error(
|
|
5079
|
+
`Workspace catalog is not valid v1:
|
|
5080
|
+
${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
5081
|
+
);
|
|
5082
|
+
}
|
|
5083
|
+
async function loadWorkspace(workspacePath) {
|
|
5084
|
+
if (!(0, import_node_fs14.existsSync)(workspacePath)) return { version: 1, projects: [] };
|
|
5085
|
+
let raw;
|
|
5086
|
+
try {
|
|
5087
|
+
raw = JSON.parse(await (0, import_promises14.readFile)(workspacePath, "utf8"));
|
|
5088
|
+
} catch (err) {
|
|
5089
|
+
throw new Error(
|
|
5090
|
+
`Workspace catalog at ${workspacePath} is malformed:
|
|
5091
|
+
${err instanceof Error ? err.message : String(err)}`
|
|
5092
|
+
);
|
|
5093
|
+
}
|
|
5094
|
+
return parseWorkspace(raw);
|
|
5095
|
+
}
|
|
5096
|
+
async function saveWorkspace(workspacePath, ws) {
|
|
5097
|
+
const sorted = {
|
|
5098
|
+
version: 1,
|
|
5099
|
+
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
5100
|
+
};
|
|
5101
|
+
await (0, import_promises14.mkdir)(import_node_path17.default.dirname(import_node_path17.default.resolve(workspacePath)), { recursive: true });
|
|
5102
|
+
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
5103
|
+
try {
|
|
5104
|
+
await (0, import_promises14.writeFile)(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
5105
|
+
await (0, import_promises14.rename)(tmp, workspacePath);
|
|
5106
|
+
} finally {
|
|
5107
|
+
await (0, import_promises14.rm)(tmp, { force: true });
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
function toPortablePath(dir, absolute) {
|
|
5111
|
+
return import_node_path17.default.relative(dir, absolute).split(import_node_path17.default.sep).join("/");
|
|
5112
|
+
}
|
|
5113
|
+
function resolveProject(dir, project) {
|
|
5114
|
+
return {
|
|
5115
|
+
manifestPath: import_node_path17.default.resolve(dir, project.manifest.split("/").join(import_node_path17.default.sep)),
|
|
5116
|
+
lockPath: import_node_path17.default.resolve(dir, project.lock.split("/").join(import_node_path17.default.sep))
|
|
5117
|
+
};
|
|
5118
|
+
}
|
|
5119
|
+
function validateWorkspace(ws, dir) {
|
|
5120
|
+
const diagnostics = [];
|
|
5121
|
+
const idCounts = /* @__PURE__ */ new Map();
|
|
5122
|
+
const lockOwners = /* @__PURE__ */ new Map();
|
|
5123
|
+
const manifestOwners = /* @__PURE__ */ new Map();
|
|
5124
|
+
for (const project of ws.projects) {
|
|
5125
|
+
idCounts.set(project.id, (idCounts.get(project.id) ?? 0) + 1);
|
|
5126
|
+
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
5127
|
+
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
5128
|
+
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
5129
|
+
if (import_node_path17.default.isAbsolute(project.manifest) || import_node_path17.default.isAbsolute(project.lock)) {
|
|
5130
|
+
diagnostics.push({
|
|
5131
|
+
id: "absolute-path",
|
|
5132
|
+
level: "warning",
|
|
5133
|
+
message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
5134
|
+
});
|
|
5135
|
+
}
|
|
5136
|
+
if (!(0, import_node_fs14.existsSync)(manifestPath)) {
|
|
5137
|
+
diagnostics.push({
|
|
5138
|
+
id: "missing-manifest",
|
|
5139
|
+
level: "error",
|
|
5140
|
+
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
5141
|
+
});
|
|
5142
|
+
}
|
|
5143
|
+
if (!(0, import_node_fs14.existsSync)(lockPath)) {
|
|
5144
|
+
diagnostics.push({
|
|
5145
|
+
id: "missing-lock",
|
|
5146
|
+
level: "error",
|
|
5147
|
+
message: `project "${project.id}" lockfile not found: ${lockPath}`
|
|
5148
|
+
});
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
for (const [id, count] of idCounts) {
|
|
5152
|
+
if (count > 1) {
|
|
5153
|
+
diagnostics.push({
|
|
5154
|
+
id: "duplicate-id",
|
|
5155
|
+
level: "error",
|
|
5156
|
+
message: `project id "${id}" is registered ${count} times`
|
|
5157
|
+
});
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
for (const [lockPath, ids] of lockOwners) {
|
|
5161
|
+
if (ids.length > 1) {
|
|
5162
|
+
diagnostics.push({
|
|
5163
|
+
id: "duplicate-lock",
|
|
5164
|
+
level: "error",
|
|
5165
|
+
message: `${ids.join(", ")} all register the same lockfile: ${lockPath}`
|
|
5166
|
+
});
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
for (const [manifestPath, ids] of manifestOwners) {
|
|
5170
|
+
if (ids.length > 1) {
|
|
5171
|
+
diagnostics.push({
|
|
5172
|
+
id: "duplicate-manifest",
|
|
5173
|
+
level: "warning",
|
|
5174
|
+
message: `${ids.join(", ")} share manifest ${manifestPath} \u2014 expected only when they are variant lockfiles beside one manifest`
|
|
5175
|
+
});
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
const providers = new Set(ws.projects.map((p) => p.provider));
|
|
5179
|
+
if (providers.size > 1) {
|
|
5180
|
+
diagnostics.push({
|
|
5181
|
+
id: "mixed-provider",
|
|
5182
|
+
level: "warning",
|
|
5183
|
+
message: `registered projects use different providers: ${[...providers].sort().join(", ")} \u2014 spend totals are kept separate per unit, but confirm this is intentional`
|
|
5184
|
+
});
|
|
5185
|
+
}
|
|
5186
|
+
return diagnostics;
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
// src/pipeline/workspace.ts
|
|
5190
|
+
async function workspaceClaims(ws, dir) {
|
|
5191
|
+
const lockPaths = [];
|
|
5192
|
+
const byProject = {};
|
|
5193
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
5194
|
+
for (const project of ws.projects) {
|
|
5195
|
+
const { lockPath } = resolveProject(dir, project);
|
|
5196
|
+
lockPaths.push(lockPath);
|
|
5197
|
+
let projectClaims;
|
|
5198
|
+
try {
|
|
5199
|
+
projectClaims = await loadClaims([lockPath]);
|
|
5200
|
+
} catch (err) {
|
|
5201
|
+
throw new Error(
|
|
5202
|
+
`Project "${project.id}" lockfile is unreadable: ${err instanceof Error ? err.message : String(err)}`
|
|
5203
|
+
);
|
|
5204
|
+
}
|
|
5205
|
+
byProject[project.id] = projectClaims.size;
|
|
5206
|
+
for (const id of projectClaims) claimed.add(id);
|
|
5207
|
+
}
|
|
5208
|
+
return { claimed, byProject, lockPaths };
|
|
5209
|
+
}
|
|
5210
|
+
function emptyStateCounts() {
|
|
5211
|
+
return {
|
|
5212
|
+
ok: 0,
|
|
5213
|
+
missing: 0,
|
|
5214
|
+
untracked: 0,
|
|
5215
|
+
stale: 0,
|
|
5216
|
+
orphaned: 0,
|
|
5217
|
+
"in-flight": 0,
|
|
5218
|
+
recoverable: 0,
|
|
5219
|
+
failed: 0
|
|
5220
|
+
};
|
|
5221
|
+
}
|
|
5222
|
+
async function workspaceStatus(ws, dir) {
|
|
5223
|
+
const diagnostics = validateWorkspace(ws, dir);
|
|
5224
|
+
const provider = PixelLabProvider.forOffline();
|
|
5225
|
+
const projects = [];
|
|
5226
|
+
const totalsByState = emptyStateCounts();
|
|
5227
|
+
const totalsSpend = { generations: 0, usd: 0, free: 0 };
|
|
5228
|
+
for (const project of ws.projects) {
|
|
5229
|
+
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
5230
|
+
const base = {
|
|
5231
|
+
id: project.id,
|
|
5232
|
+
provider: project.provider,
|
|
5233
|
+
account: project.account ?? null,
|
|
5234
|
+
manifest: manifestPath,
|
|
5235
|
+
lock: lockPath
|
|
5236
|
+
};
|
|
5237
|
+
try {
|
|
5238
|
+
const loaded = await loadManifest(manifestPath);
|
|
5239
|
+
const specs = await resolveSpecs(loaded, { provider });
|
|
5240
|
+
const lock = await loadLock(lockPath);
|
|
5241
|
+
normalizeLockOutputPaths(lock, specs);
|
|
5242
|
+
const plan = await buildPlan(specs, lock);
|
|
5243
|
+
const byState = summarize(plan);
|
|
5244
|
+
const spend = spendByUnit(lock);
|
|
5245
|
+
for (const state of Object.keys(byState)) {
|
|
5246
|
+
totalsByState[state] += byState[state];
|
|
5247
|
+
}
|
|
5248
|
+
for (const unit of Object.keys(spend)) {
|
|
5249
|
+
totalsSpend[unit] += spend[unit];
|
|
5250
|
+
}
|
|
5251
|
+
projects.push({
|
|
5252
|
+
...base,
|
|
5253
|
+
entries: Object.keys(lock.entries).length,
|
|
5254
|
+
byState,
|
|
5255
|
+
spendByUnit: spend,
|
|
5256
|
+
error: null
|
|
5257
|
+
});
|
|
5258
|
+
} catch (err) {
|
|
5259
|
+
projects.push({
|
|
5260
|
+
...base,
|
|
5261
|
+
entries: 0,
|
|
5262
|
+
byState: emptyStateCounts(),
|
|
5263
|
+
spendByUnit: { generations: 0, usd: 0, free: 0 },
|
|
5264
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5265
|
+
});
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
let claims = 0;
|
|
5269
|
+
try {
|
|
5270
|
+
claims = (await workspaceClaims(ws, dir)).claimed.size;
|
|
5271
|
+
} catch {
|
|
5272
|
+
}
|
|
5273
|
+
return {
|
|
5274
|
+
version: 1,
|
|
5275
|
+
safe: !diagnostics.some((d) => d.level === "error") && projects.every((p) => !p.error),
|
|
5276
|
+
dir,
|
|
5277
|
+
projects,
|
|
5278
|
+
totals: { byState: totalsByState, spendByUnit: totalsSpend, claims },
|
|
5279
|
+
diagnostics
|
|
5280
|
+
};
|
|
5281
|
+
}
|
|
5023
5282
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5024
5283
|
0 && (module.exports = {
|
|
5025
5284
|
AssetSchema,
|
|
@@ -5037,6 +5296,8 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5037
5296
|
PixelLabProvider,
|
|
5038
5297
|
StyleSchema,
|
|
5039
5298
|
UnsupportedCapabilityError,
|
|
5299
|
+
WorkspaceProjectSchema,
|
|
5300
|
+
WorkspaceSchema,
|
|
5040
5301
|
adopt,
|
|
5041
5302
|
applyTags,
|
|
5042
5303
|
auditStyle,
|
|
@@ -5067,6 +5328,8 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5067
5328
|
loadClaims,
|
|
5068
5329
|
loadLock,
|
|
5069
5330
|
loadManifest,
|
|
5331
|
+
loadSiblingManifests,
|
|
5332
|
+
loadWorkspace,
|
|
5070
5333
|
lockKey,
|
|
5071
5334
|
matchOrphanStyle,
|
|
5072
5335
|
measureBalanceChange,
|
|
@@ -5081,6 +5344,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5081
5344
|
packStyle,
|
|
5082
5345
|
paletteDistance,
|
|
5083
5346
|
parseLock,
|
|
5347
|
+
parseWorkspace,
|
|
5084
5348
|
pngSize,
|
|
5085
5349
|
poll,
|
|
5086
5350
|
portableOutputPath,
|
|
@@ -5094,6 +5358,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5094
5358
|
resolveEntryOutputs,
|
|
5095
5359
|
resolveOutputPath,
|
|
5096
5360
|
resolvePackInputs,
|
|
5361
|
+
resolveProject,
|
|
5097
5362
|
resolveSpecEntryOutputs,
|
|
5098
5363
|
resolveSpecOutputs,
|
|
5099
5364
|
resolveSpecs,
|
|
@@ -5103,6 +5368,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5103
5368
|
runPicker,
|
|
5104
5369
|
runSalvage,
|
|
5105
5370
|
saveLock,
|
|
5371
|
+
saveWorkspace,
|
|
5106
5372
|
scanAssets,
|
|
5107
5373
|
selectEntryOutput,
|
|
5108
5374
|
sha256,
|
|
@@ -5118,11 +5384,15 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
5118
5384
|
tileFeatureOutputCount,
|
|
5119
5385
|
tileVariationCount,
|
|
5120
5386
|
tilesCost,
|
|
5387
|
+
toPortablePath,
|
|
5121
5388
|
totalSpend,
|
|
5122
5389
|
upsert,
|
|
5123
5390
|
validateCostEstimate,
|
|
5391
|
+
validateWorkspace,
|
|
5124
5392
|
verifyArtifactBundle,
|
|
5125
5393
|
withArtifactManifest,
|
|
5394
|
+
workspaceClaims,
|
|
5395
|
+
workspaceStatus,
|
|
5126
5396
|
writeArtifactBundle,
|
|
5127
5397
|
writeManagedArtifactBundle
|
|
5128
5398
|
});
|