mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
feat(live): v2 sidecar upgrade + preserve per-project config on build
Unify the design-system panel's data shape around DESIGN.md frontmatter
as the primary source of truth; the sidecar carries only what Stitch's
frontmatter schema can't (extensions + live component HTML + narrative).
Also fix a long-standing build bug that destroyed per-project config.
Shape changes:
- Server /design-system.json now returns { parsed, sidecar, hasMd,
hasSidecar, mdNewerThanJson, parseError?, sidecarError? }. No more
mode switching; both layers ship when present and the panel merges.
- Panel consolidates renderSidecarVisual + renderParsedMdVisual into a
single renderDesignVisual that merges frontmatter primitives with
sidecar extensions.colorMeta / typographyMeta. Helpers for color,
typography, radii model-building. Parsed-md narrative synthesis
survives as a fallback when no sidecar.
- DESIGN.json rewritten at schemaVersion 2: extensions.{colorMeta,
typographyMeta, shadows, motion, breakpoints} + components (with
refersTo pointing back to frontmatter component keys) + narrative.
Token primitives no longer duplicated in the sidecar.
Build fix:
- scripts/build.js:634 wiped .claude/skills/ (and every other harness
dir) on each rebuild, then recopied from dist. After commit b0feed0
unbundled per-project config.json from dist, the sync destroyed the
user's live-mode config on every build without replacing it.
- Added stashPerProjectArtifacts / restorePerProjectArtifacts in
scripts/lib/utils.js. Hoisted PER_PROJECT_SCRIPT_ARTIFACTS to a
module-level export so build.js and readSourceFiles share one
source of truth. Build now preserves config.json across the sync.
Verified in browser: panel renders 10 colors, 9 typography roles, 3
shadows, 6 grouped components, 9 rules, 25 do/don't items, all merged
correctly from frontmatter + v2 sidecar with zero console errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a5cd7bf859
commit
c160ffc38d
+6
-1
@@ -18,7 +18,7 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { readSourceFiles, readPatterns } from './lib/utils.js';
|
||||
import { readSourceFiles, readPatterns, stashPerProjectArtifacts, restorePerProjectArtifacts } from './lib/utils.js';
|
||||
import { createTransformer, PROVIDERS } from './lib/transformers/index.js';
|
||||
import { createAllZips } from './lib/zip.js';
|
||||
import { generateSubPages } from './build-sub-pages.js';
|
||||
@@ -631,8 +631,13 @@ async function build() {
|
||||
const skillsDest = path.join(ROOT_DIR, configDir, 'skills');
|
||||
|
||||
if (fs.existsSync(skillsSrc)) {
|
||||
// Preserve per-project script artifacts (e.g. live-mode config.json)
|
||||
// across the rm + recopy. The build intentionally doesn't ship them,
|
||||
// so without this the sync destroys local state on every rebuild.
|
||||
const stashed = stashPerProjectArtifacts(skillsDest);
|
||||
if (fs.existsSync(skillsDest)) fs.rmSync(skillsDest, { recursive: true });
|
||||
copyDirSync(skillsSrc, skillsDest);
|
||||
restorePerProjectArtifacts(skillsDest, stashed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-10
@@ -1,6 +1,45 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Per-project artifacts live inside `scripts/` of an installed skill but
|
||||
// belong to the consuming project, not the distributable skill. The build
|
||||
// excludes them from dist, and the harness-sync step preserves them across
|
||||
// the rm+recopy so local state isn't destroyed on every rebuild.
|
||||
// - config.json: live-mode inject target list for the current project.
|
||||
// Written by the agent at first /impeccable live; tied to the project's
|
||||
// filesystem layout. Losing it resets the user's glob + exclusions.
|
||||
export const PER_PROJECT_SCRIPT_ARTIFACTS = new Set(['config.json']);
|
||||
|
||||
// Walk the harness-dir skill tree and return any per-project script
|
||||
// artifacts found, ready for restoration after a full sync rm+recopy.
|
||||
// Returns [{ relPath, content: Buffer }], where relPath is relative to
|
||||
// the passed-in rootDir (typically `<configDir>/skills`).
|
||||
export function stashPerProjectArtifacts(rootDir) {
|
||||
if (!fs.existsSync(rootDir)) return [];
|
||||
const out = [];
|
||||
const walk = (dir) => {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) { walk(p); continue; }
|
||||
// Only preserve files inside a skill's scripts/ directory.
|
||||
if (path.basename(path.dirname(p)) !== 'scripts') continue;
|
||||
if (PER_PROJECT_SCRIPT_ARTIFACTS.has(entry.name)) {
|
||||
out.push({ relPath: path.relative(rootDir, p), content: fs.readFileSync(p) });
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(rootDir);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function restorePerProjectArtifacts(rootDir, stashed) {
|
||||
for (const { relPath, content } of stashed) {
|
||||
const target = path.join(rootDir, relPath);
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
fs.writeFileSync(target, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse frontmatter from markdown content
|
||||
* Returns { frontmatter: object, body: string }
|
||||
@@ -147,20 +186,14 @@ export function readSourceFiles(rootDir) {
|
||||
}
|
||||
}
|
||||
|
||||
// Read script files if they exist.
|
||||
//
|
||||
// Per-project artifacts (state files that belong to the consuming
|
||||
// project, not the distributable skill) must be excluded here so
|
||||
// the build never bundles them into the skill that ships to users.
|
||||
// - config.json: the live-mode inject-target list for the current
|
||||
// project. Written by the agent at first /impeccable live; tied
|
||||
// to the project's filesystem layout.
|
||||
const PER_PROJECT_ARTIFACTS = new Set(['config.json']);
|
||||
// Read script files if they exist. PER_PROJECT_SCRIPT_ARTIFACTS
|
||||
// (defined at module top) are excluded from the distributable skill
|
||||
// so the build never bundles one project's state into another's.
|
||||
const scripts = [];
|
||||
const scriptsDir = path.join(entryPath, 'scripts');
|
||||
if (fs.existsSync(scriptsDir)) {
|
||||
const scriptFiles = fs.readdirSync(scriptsDir).filter(f => {
|
||||
if (PER_PROJECT_ARTIFACTS.has(f)) return false;
|
||||
if (PER_PROJECT_SCRIPT_ARTIFACTS.has(f)) return false;
|
||||
return fs.statSync(path.join(scriptsDir, f)).isFile();
|
||||
});
|
||||
for (const scriptFile of scriptFiles) {
|
||||
|
||||
Reference in New Issue
Block a user