@nebutra/design-sync 0.1.0 → 0.1.2
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/LICENSE +21 -676
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +189 -0
- package/dist/detect.d.ts +21 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +81 -0
- package/dist/factory.d.ts +42 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +94 -0
- package/dist/figma-config/index.d.ts +37 -0
- package/dist/figma-config/index.d.ts.map +1 -0
- package/dist/figma-config/index.js +12 -0
- package/dist/figma-config/tokens-studio.config.json +34 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/io.d.ts +26 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +119 -0
- package/dist/providers/design-md.d.ts +49 -0
- package/dist/providers/design-md.d.ts.map +1 -0
- package/dist/providers/design-md.js +264 -0
- package/dist/providers/figma.d.ts +21 -0
- package/dist/providers/figma.d.ts.map +1 -0
- package/dist/providers/figma.js +179 -0
- package/dist/providers/git-only.d.ts +11 -0
- package/dist/providers/git-only.d.ts.map +1 -0
- package/dist/providers/git-only.js +104 -0
- package/dist/providers/memory.d.ts +17 -0
- package/dist/providers/memory.d.ts.map +1 -0
- package/dist/providers/memory.js +65 -0
- package/dist/providers/penpot.d.ts +21 -0
- package/dist/providers/penpot.d.ts.map +1 -0
- package/dist/providers/penpot.js +137 -0
- package/dist/serialize/from-design-md.d.ts +80 -0
- package/dist/serialize/from-design-md.d.ts.map +1 -0
- package/dist/serialize/from-design-md.js +1329 -0
- package/dist/serialize/to-brand-package.d.ts +37 -0
- package/dist/serialize/to-brand-package.d.ts.map +1 -0
- package/dist/serialize/to-brand-package.js +87 -0
- package/dist/serialize/to-design-md.d.ts +42 -0
- package/dist/serialize/to-design-md.d.ts.map +1 -0
- package/dist/serialize/to-design-md.js +114 -0
- package/dist/serialize/to-design-md.prose.d.ts +55 -0
- package/dist/serialize/to-design-md.prose.d.ts.map +1 -0
- package/dist/serialize/to-design-md.prose.js +127 -0
- package/dist/serialize/to-design-md.resolve.d.ts +36 -0
- package/dist/serialize/to-design-md.resolve.d.ts.map +1 -0
- package/dist/serialize/to-design-md.resolve.js +248 -0
- package/dist/serialize/to-preview-html.d.ts +42 -0
- package/dist/serialize/to-preview-html.d.ts.map +1 -0
- package/dist/serialize/to-preview-html.js +250 -0
- package/dist/serialize/to-preview-html.template.d.ts +75 -0
- package/dist/serialize/to-preview-html.template.d.ts.map +1 -0
- package/dist/serialize/to-preview-html.template.js +267 -0
- package/dist/types.d.ts +191 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/package.json +22 -7
- package/src/cli/index.ts +36 -3
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { logger } from "@nebutra/logger";
|
|
3
|
+
import { defaultTokensDir, defaultTokensStudioDir, readTokenSets, validateDtcgTree, writeTokenSet, } from "../io";
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Git-Only Provider — DTCG-only, no design tool
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// This is the zero-config default for indie hackers, AI-driven dev workflows,
|
|
8
|
+
// and any team that does NOT use a design tool. It treats the local
|
|
9
|
+
// `packages/design/design-tokens/tokens` directory as the single source of truth.
|
|
10
|
+
//
|
|
11
|
+
// `pull` simply re-reads the DTCG files (a no-op refresh).
|
|
12
|
+
// `push` validates + re-serialises (formats) the DTCG files in place.
|
|
13
|
+
// =============================================================================
|
|
14
|
+
export class GitOnlyProvider {
|
|
15
|
+
name = "git-only";
|
|
16
|
+
tokensDir;
|
|
17
|
+
tokensStudioDir;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.tokensDir = config.tokensDir ?? defaultTokensDir();
|
|
20
|
+
this.tokensStudioDir = config.tokensStudioDir ?? defaultTokensStudioDir();
|
|
21
|
+
logger.info("[design-sync:git-only] Provider initialised", {
|
|
22
|
+
tokensDir: this.tokensDir,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async pull(options = {}) {
|
|
26
|
+
const sets = await readTokenSets(this.tokensDir);
|
|
27
|
+
const filtered = filterSets(sets, options.themes);
|
|
28
|
+
return {
|
|
29
|
+
sets: filtered,
|
|
30
|
+
written: false,
|
|
31
|
+
provider: "git-only",
|
|
32
|
+
pulledAt: new Date().toISOString(),
|
|
33
|
+
summary: `Read ${filtered.length} DTCG token set(s) from ${this.tokensDir}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async push(options = {}) {
|
|
37
|
+
const all = await readTokenSets(this.tokensDir);
|
|
38
|
+
const sets = filterSets(all, options.themes);
|
|
39
|
+
// Validate every set before re-serialising — fail closed on bad DTCG.
|
|
40
|
+
for (const set of sets) {
|
|
41
|
+
const errors = validateDtcgTree(set.tokens);
|
|
42
|
+
if (errors.length > 0) {
|
|
43
|
+
throw new Error(`[design-sync:git-only] DTCG validation failed for ${set.relativePath}:\n - ${errors.join("\n - ")}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const dryRun = options.dryRun ?? false;
|
|
47
|
+
if (!dryRun) {
|
|
48
|
+
for (const set of sets) {
|
|
49
|
+
await writeTokenSet(this.tokensDir, set);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
pushed: !dryRun,
|
|
54
|
+
sets: sets.map((s) => s.relativePath),
|
|
55
|
+
provider: "git-only",
|
|
56
|
+
pushedAt: new Date().toISOString(),
|
|
57
|
+
summary: dryRun
|
|
58
|
+
? `Validated ${sets.length} DTCG file(s) (dry-run, no files modified)`
|
|
59
|
+
: `Reformatted ${sets.length} DTCG file(s) under ${this.tokensDir}`,
|
|
60
|
+
dryRun,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async healthcheck() {
|
|
64
|
+
const detected = [];
|
|
65
|
+
const missing = [];
|
|
66
|
+
try {
|
|
67
|
+
const info = await stat(this.tokensDir);
|
|
68
|
+
if (info.isDirectory()) {
|
|
69
|
+
detected.push("tokensDir");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
missing.push("tokensDir (not a directory)");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
missing.push("tokensDir (does not exist)");
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const info = await stat(this.tokensStudioDir);
|
|
80
|
+
if (info.isDirectory()) {
|
|
81
|
+
detected.push("tokensStudioDir");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// tokens-studio dir is optional for git-only mode
|
|
86
|
+
}
|
|
87
|
+
const ok = missing.length === 0;
|
|
88
|
+
return {
|
|
89
|
+
ok,
|
|
90
|
+
provider: "git-only",
|
|
91
|
+
message: ok
|
|
92
|
+
? `git-only ready — DTCG source dir found at ${this.tokensDir}`
|
|
93
|
+
: `git-only misconfigured — missing: ${missing.join(", ")}`,
|
|
94
|
+
detectedEnv: detected,
|
|
95
|
+
missingEnv: missing,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function filterSets(sets, themes) {
|
|
100
|
+
if (!themes || themes.length === 0)
|
|
101
|
+
return sets;
|
|
102
|
+
const wanted = new Set(themes);
|
|
103
|
+
return sets.filter((s) => wanted.has(s.name) || wanted.has(s.relativePath));
|
|
104
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DesignSyncProvider, DesignTokenSet, HealthStatus, MemoryProviderConfig, PullOptions, PullResult, PushOptions, PushResult } from "../types";
|
|
2
|
+
export declare class MemoryProvider implements DesignSyncProvider {
|
|
3
|
+
readonly name: "memory";
|
|
4
|
+
private sets;
|
|
5
|
+
readonly pullCalls: PullOptions[];
|
|
6
|
+
readonly pushCalls: PushOptions[];
|
|
7
|
+
constructor(config: MemoryProviderConfig);
|
|
8
|
+
pull(options?: PullOptions): Promise<PullResult>;
|
|
9
|
+
push(options?: PushOptions): Promise<PushResult>;
|
|
10
|
+
healthcheck(): Promise<HealthStatus>;
|
|
11
|
+
/**
|
|
12
|
+
* Replace the in-memory set list — useful in tests for asserting how
|
|
13
|
+
* the provider responds to different remote states.
|
|
14
|
+
*/
|
|
15
|
+
replaceSets(sets: DesignTokenSet[]): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/providers/memory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACX,MAAM,UAAU,CAAC;AAWlB,qBAAa,cAAe,YAAW,kBAAkB;IACvD,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAElC,OAAO,CAAC,IAAI,CAAmB;IAC/B,SAAgB,SAAS,EAAE,WAAW,EAAE,CAAM;IAC9C,SAAgB,SAAS,EAAE,WAAW,EAAE,CAAM;gBAElC,MAAM,EAAE,oBAAoB;IAKlC,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IAYpD,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IAcpD,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC;IAU1C;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,IAAI;CAG1C"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { logger } from "@nebutra/logger";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Memory Provider — in-memory test fixture
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Used in unit tests and CI dry-runs where touching the filesystem or
|
|
6
|
+
// hitting a remote API would be wasteful. Tracks `pull`/`push` calls so
|
|
7
|
+
// tests can assert behaviour.
|
|
8
|
+
// NOT for production.
|
|
9
|
+
// =============================================================================
|
|
10
|
+
export class MemoryProvider {
|
|
11
|
+
name = "memory";
|
|
12
|
+
sets;
|
|
13
|
+
pullCalls = [];
|
|
14
|
+
pushCalls = [];
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.sets = config.initialSets ?? [];
|
|
17
|
+
logger.info("[design-sync:memory] Provider initialised (test fixture only)");
|
|
18
|
+
}
|
|
19
|
+
async pull(options = {}) {
|
|
20
|
+
this.pullCalls.push(options);
|
|
21
|
+
const sets = filterSets(this.sets, options.themes);
|
|
22
|
+
return {
|
|
23
|
+
sets,
|
|
24
|
+
written: false,
|
|
25
|
+
provider: "memory",
|
|
26
|
+
pulledAt: new Date().toISOString(),
|
|
27
|
+
summary: `memory: returned ${sets.length} in-memory token set(s)`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async push(options = {}) {
|
|
31
|
+
this.pushCalls.push(options);
|
|
32
|
+
const filtered = filterSets(this.sets, options.themes);
|
|
33
|
+
const dryRun = options.dryRun ?? false;
|
|
34
|
+
return {
|
|
35
|
+
pushed: !dryRun,
|
|
36
|
+
sets: filtered.map((s) => s.relativePath),
|
|
37
|
+
provider: "memory",
|
|
38
|
+
pushedAt: new Date().toISOString(),
|
|
39
|
+
summary: `memory: ${dryRun ? "dry-run" : "applied"} ${filtered.length} set(s)`,
|
|
40
|
+
dryRun,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async healthcheck() {
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
provider: "memory",
|
|
47
|
+
message: "memory provider always healthy (test fixture)",
|
|
48
|
+
detectedEnv: [],
|
|
49
|
+
missingEnv: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Replace the in-memory set list — useful in tests for asserting how
|
|
54
|
+
* the provider responds to different remote states.
|
|
55
|
+
*/
|
|
56
|
+
replaceSets(sets) {
|
|
57
|
+
this.sets = [...sets];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function filterSets(sets, themes) {
|
|
61
|
+
if (!themes || themes.length === 0)
|
|
62
|
+
return sets;
|
|
63
|
+
const wanted = new Set(themes);
|
|
64
|
+
return sets.filter((s) => wanted.has(s.name) || wanted.has(s.relativePath));
|
|
65
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DesignSyncProvider, HealthStatus, PenpotProviderConfig, PullOptions, PullResult, PushOptions, PushResult } from "../types";
|
|
2
|
+
export declare class PenpotProvider implements DesignSyncProvider {
|
|
3
|
+
readonly name: "penpot";
|
|
4
|
+
private readonly tokensDir;
|
|
5
|
+
private readonly tokensStudioDir;
|
|
6
|
+
private readonly apiUrl;
|
|
7
|
+
private readonly token;
|
|
8
|
+
private readonly fileId;
|
|
9
|
+
private readonly teamId;
|
|
10
|
+
constructor(config: PenpotProviderConfig);
|
|
11
|
+
pull(options?: PullOptions): Promise<PullResult>;
|
|
12
|
+
push(options?: PushOptions): Promise<PushResult>;
|
|
13
|
+
healthcheck(): Promise<HealthStatus>;
|
|
14
|
+
/**
|
|
15
|
+
* Reserved for future use — exposes the configured tokens-studio directory
|
|
16
|
+
* so consumers (e.g. CI workflows) can attach Penpot-side metadata mirrors.
|
|
17
|
+
*/
|
|
18
|
+
getTokensStudioDir(): string;
|
|
19
|
+
private credsReady;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=penpot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"penpot.d.ts","sourceRoot":"","sources":["../../src/providers/penpot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACX,MAAM,UAAU,CAAC;AAmBlB,qBAAa,cAAe,YAAW,kBAAkB;IACvD,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,EAAE,oBAAoB;IAelC,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BpD,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IA8CpD,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC;IAyB1C;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAI5B,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { logger } from "@nebutra/logger";
|
|
2
|
+
import { defaultTokensDir, defaultTokensStudioDir, readTokenSets, validateDtcgTree } from "../io";
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Penpot Provider — China-friendly + self-hostable Figma alternative
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// Penpot speaks DTCG natively (one of its differentiators) and exposes a
|
|
7
|
+
// public REST API. The provider is wired with full surface area so customers
|
|
8
|
+
// can swap providers without changing application code, but real network
|
|
9
|
+
// calls stay behind a `dryRun || !credsReady` guard until the operator
|
|
10
|
+
// explicitly provisions PENPOT_TOKEN.
|
|
11
|
+
//
|
|
12
|
+
// Why this matters:
|
|
13
|
+
// - Penpot can be self-hosted, sidesteps the Figma block in mainland China.
|
|
14
|
+
// - DTCG-native means we do not need a translation step like with Figma.
|
|
15
|
+
// - Tokens Studio plugin also targets Penpot — designers keep the same UX.
|
|
16
|
+
// =============================================================================
|
|
17
|
+
const DEFAULT_PENPOT_API = "https://design.penpot.app/api";
|
|
18
|
+
export class PenpotProvider {
|
|
19
|
+
name = "penpot";
|
|
20
|
+
tokensDir;
|
|
21
|
+
tokensStudioDir;
|
|
22
|
+
apiUrl;
|
|
23
|
+
token;
|
|
24
|
+
fileId;
|
|
25
|
+
teamId;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.tokensDir = config.tokensDir ?? defaultTokensDir();
|
|
28
|
+
this.tokensStudioDir = config.tokensStudioDir ?? defaultTokensStudioDir();
|
|
29
|
+
this.apiUrl = config.apiUrl ?? process.env.PENPOT_API_URL ?? DEFAULT_PENPOT_API;
|
|
30
|
+
this.token = config.token ?? process.env.PENPOT_TOKEN ?? undefined;
|
|
31
|
+
this.fileId = config.fileId ?? process.env.PENPOT_FILE_ID ?? undefined;
|
|
32
|
+
this.teamId = config.teamId ?? process.env.PENPOT_TEAM_ID ?? undefined;
|
|
33
|
+
logger.info("[design-sync:penpot] Provider initialised", {
|
|
34
|
+
apiUrl: this.apiUrl,
|
|
35
|
+
hasToken: Boolean(this.token),
|
|
36
|
+
hasFileId: Boolean(this.fileId),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async pull(options = {}) {
|
|
40
|
+
if (!this.credsReady()) {
|
|
41
|
+
// No credentials → fall through to local DTCG, mirroring git-only.
|
|
42
|
+
const sets = await readTokenSets(this.tokensDir);
|
|
43
|
+
const filtered = filterSets(sets, options.themes);
|
|
44
|
+
logger.warn("[design-sync:penpot] pull falling back to local DTCG (no PENPOT_TOKEN)");
|
|
45
|
+
return {
|
|
46
|
+
sets: filtered,
|
|
47
|
+
written: false,
|
|
48
|
+
provider: "penpot",
|
|
49
|
+
pulledAt: new Date().toISOString(),
|
|
50
|
+
summary: `penpot: PENPOT_TOKEN missing — returned ${filtered.length} local DTCG set(s)`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Real pull placeholder — when wired up, this calls:
|
|
54
|
+
// GET {apiUrl}/rpc/command/get-file?id={fileId}
|
|
55
|
+
// Authorization: Token {token}
|
|
56
|
+
// and projects the design-tokens collection back into DTCG sets.
|
|
57
|
+
// See https://design.penpot.app/api/docs for the full RPC catalogue.
|
|
58
|
+
throw new Error("[design-sync:penpot] live pull is not yet implemented. " +
|
|
59
|
+
"Unset PENPOT_TOKEN to fall back to local DTCG, or open packages/design/design-sync/DESIGN.md for the rollout plan.");
|
|
60
|
+
}
|
|
61
|
+
async push(options = {}) {
|
|
62
|
+
const all = await readTokenSets(this.tokensDir);
|
|
63
|
+
const sets = filterSets(all, options.themes);
|
|
64
|
+
for (const set of sets) {
|
|
65
|
+
const errors = validateDtcgTree(set.tokens);
|
|
66
|
+
if (errors.length > 0) {
|
|
67
|
+
throw new Error(`[design-sync:penpot] DTCG validation failed for ${set.relativePath}:\n - ${errors.join("\n - ")}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const credsReady = this.credsReady();
|
|
71
|
+
const explicitDryRun = options.dryRun ?? false;
|
|
72
|
+
const dryRun = explicitDryRun || !credsReady;
|
|
73
|
+
if (dryRun) {
|
|
74
|
+
logger.warn("[design-sync:penpot] push skipped (dry-run scaffold)", {
|
|
75
|
+
reason: credsReady ? "explicit dryRun" : "missing credentials",
|
|
76
|
+
sets: sets.length,
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
pushed: false,
|
|
80
|
+
sets: sets.map((s) => s.relativePath),
|
|
81
|
+
provider: "penpot",
|
|
82
|
+
pushedAt: new Date().toISOString(),
|
|
83
|
+
summary: credsReady
|
|
84
|
+
? `penpot: dry-run — would POST ${sets.length} DTCG set(s) to ${this.apiUrl}/rpc/command/update-file-design-tokens`
|
|
85
|
+
: "penpot: dry-run — PENPOT_TOKEN or PENPOT_FILE_ID missing",
|
|
86
|
+
dryRun: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Real push placeholder — when wired up, this calls:
|
|
90
|
+
// POST {apiUrl}/rpc/command/update-file-design-tokens
|
|
91
|
+
// Authorization: Token {token}
|
|
92
|
+
// body: { fileId, tokens: dtcgTree }
|
|
93
|
+
throw new Error("[design-sync:penpot] live push is not yet implemented. " +
|
|
94
|
+
"Use { dryRun: true } until the Penpot RPC integration is wired up.");
|
|
95
|
+
}
|
|
96
|
+
async healthcheck() {
|
|
97
|
+
const detected = [];
|
|
98
|
+
const missing = [];
|
|
99
|
+
if (this.token)
|
|
100
|
+
detected.push("PENPOT_TOKEN");
|
|
101
|
+
else
|
|
102
|
+
missing.push("PENPOT_TOKEN");
|
|
103
|
+
if (this.fileId)
|
|
104
|
+
detected.push("PENPOT_FILE_ID");
|
|
105
|
+
else
|
|
106
|
+
missing.push("PENPOT_FILE_ID");
|
|
107
|
+
if (this.teamId)
|
|
108
|
+
detected.push("PENPOT_TEAM_ID");
|
|
109
|
+
detected.push(`PENPOT_API_URL=${this.apiUrl}`);
|
|
110
|
+
const ok = missing.length === 0;
|
|
111
|
+
return {
|
|
112
|
+
ok,
|
|
113
|
+
provider: "penpot",
|
|
114
|
+
message: ok
|
|
115
|
+
? `penpot: credentials present (api=${this.apiUrl})`
|
|
116
|
+
: `penpot: not ready — missing ${missing.join(", ")}`,
|
|
117
|
+
detectedEnv: detected,
|
|
118
|
+
missingEnv: missing,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Reserved for future use — exposes the configured tokens-studio directory
|
|
123
|
+
* so consumers (e.g. CI workflows) can attach Penpot-side metadata mirrors.
|
|
124
|
+
*/
|
|
125
|
+
getTokensStudioDir() {
|
|
126
|
+
return this.tokensStudioDir;
|
|
127
|
+
}
|
|
128
|
+
credsReady() {
|
|
129
|
+
return Boolean(this.token && this.fileId);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function filterSets(sets, themes) {
|
|
133
|
+
if (!themes || themes.length === 0)
|
|
134
|
+
return sets;
|
|
135
|
+
const wanted = new Set(themes);
|
|
136
|
+
return sets.filter((s) => wanted.has(s.name) || wanted.has(s.relativePath));
|
|
137
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DESIGN.md → DTCG theme importer
|
|
3
|
+
*
|
|
4
|
+
* Parse approach: programmatic via `lint()` from `@google/design.md/linter`.
|
|
5
|
+
* The `lint()` function parses the DESIGN.md content synchronously and returns
|
|
6
|
+
* a `DesignSystemState` with typed Maps for colors, rounded, spacing, and
|
|
7
|
+
* typography. We reshape that state into our DTCG naming convention:
|
|
8
|
+
* - colors.<x> → color.<x> ($type: "color", $value: hex string)
|
|
9
|
+
* - rounded.<x> → radius.<x> ($type: "dimension", $value: "<n><unit>" string)
|
|
10
|
+
* - spacing.<x> → spacing.<x> ($type: "dimension", $value: "<n><unit>" string)
|
|
11
|
+
* - body font → fontFamily.sans ($type: "fontFamily", $value: string)
|
|
12
|
+
*
|
|
13
|
+
* PROSE-COLOR FALLBACK: When @google/design.md extracts zero colors (e.g. the
|
|
14
|
+
* VoltAgent extended DESIGN.md format where colors are written as markdown prose
|
|
15
|
+
* rather than YAML front-matter), `extractColorsFromProse` is called to scrape
|
|
16
|
+
* labeled color literals of the form `**Label** (\`<value>\`)`.
|
|
17
|
+
*
|
|
18
|
+
* NEVER import `@google/design.md` (main entry — auto-runs CLI). Only the
|
|
19
|
+
* `@google/design.md/linter` subpath export is used here.
|
|
20
|
+
*
|
|
21
|
+
* All @google/design.md coupling is isolated in THIS file.
|
|
22
|
+
*/
|
|
23
|
+
import type { DesignTokenSet } from "../types";
|
|
24
|
+
export interface ImportReport {
|
|
25
|
+
/** Human-readable strings noting content that has no structured DTCG equivalent. */
|
|
26
|
+
unmapped: string[];
|
|
27
|
+
/** Registry-required token keys absent from the produced tree (dot-path format). */
|
|
28
|
+
missingRequired: string[];
|
|
29
|
+
/** Non-fatal warnings encountered during import. */
|
|
30
|
+
warnings: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface ImportResult {
|
|
33
|
+
set: DesignTokenSet;
|
|
34
|
+
report: ImportReport;
|
|
35
|
+
}
|
|
36
|
+
export interface ImportFromDesignMdOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Override the slug used for `relativePath` and `name`.
|
|
39
|
+
* Takes precedence over the `name` field in the DESIGN.md front matter.
|
|
40
|
+
*/
|
|
41
|
+
brandName?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract color entries from DESIGN.md prose content (VoltAgent extended format).
|
|
45
|
+
*
|
|
46
|
+
* Looks for labeled color literals matching: `**Label** (\`<value>\`)`
|
|
47
|
+
* where `<value>` is a valid CSS color (hex, rgb, rgba, hsl, hsla, oklch).
|
|
48
|
+
*
|
|
49
|
+
* Also handles the `{colors.xxx} — #hex` variant (backtick-quoted or unquoted hex).
|
|
50
|
+
*
|
|
51
|
+
* Scoping: if a heading matching `/^#{1,3}\s+.*colou?r/i` exists in the
|
|
52
|
+
* document, extraction is limited to the content under that heading (up to
|
|
53
|
+
* the next same-or-higher heading). Otherwise the whole document is scanned.
|
|
54
|
+
*
|
|
55
|
+
* Effect labels (shadow, overlay, glow, gradient, elevation, blur, scrim) are
|
|
56
|
+
* excluded regardless of scoping — they describe visual effects, not palette colors.
|
|
57
|
+
*
|
|
58
|
+
* Token naming: labels are kebab-cased (`"Spotify Green"` → `"spotify-green"`).
|
|
59
|
+
* De-duplicates by token name (first wins). Capped at 48 entries.
|
|
60
|
+
*
|
|
61
|
+
* Returns a plain `Record<string, string>` mapping slug → raw CSS color string.
|
|
62
|
+
*
|
|
63
|
+
* @public — exported so tests can unit-test the helper directly.
|
|
64
|
+
*/
|
|
65
|
+
export declare function extractColorsFromProse(content: string): Record<string, string>;
|
|
66
|
+
/**
|
|
67
|
+
* Parse a DESIGN.md document and produce a DTCG-compliant `DesignTokenSet`
|
|
68
|
+
* scoped to the `themes/` bucket (SSOT-safe — never overwrites core or semantic).
|
|
69
|
+
*
|
|
70
|
+
* The function is SYNCHRONOUS; it never reads from or writes to the filesystem.
|
|
71
|
+
*
|
|
72
|
+
* @param content - Raw DESIGN.md content (markdown + YAML front matter).
|
|
73
|
+
* @param options - Optional: `brandName` to override the slug.
|
|
74
|
+
* @returns `ImportResult` containing the token set and a diagnostic report.
|
|
75
|
+
* @throws If `@google/design.md` fails to parse the content, or if the produced
|
|
76
|
+
* DTCG tree is invalid (invalid leaves). Empty input produces an
|
|
77
|
+
* empty-but-valid tree and does NOT throw.
|
|
78
|
+
*/
|
|
79
|
+
export declare function importFromDesignMd(content: string, options?: ImportFromDesignMdOptions): ImportResult;
|
|
80
|
+
//# sourceMappingURL=from-design-md.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-design-md.d.ts","sourceRoot":"","sources":["../../src/serialize/from-design-md.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,UAAU,CAAC;AAIhE,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oFAAoF;IACpF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAg5BD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO9E;AAsKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,yBAAyB,GAClC,YAAY,CA0Td"}
|