anhedral 0.4.2 → 0.6.1
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/README.md +35 -265
- package/assets/anhedral-cli-init-technical.svg +322 -0
- package/assets/anhedral-cli-init.svg +257 -173
- package/dist/bin.js +28 -191
- package/dist/dependencies.d.ts +7 -57
- package/dist/dependencies.js +96 -308
- package/dist/index.d.ts +2 -7
- package/dist/index.js +1 -4
- package/dist/platforms/desktop.js +401 -0
- package/dist/platforms/extension.js +401 -0
- package/dist/platforms/mobile.js +232 -0
- package/dist/project.d.ts +0 -26
- package/dist/standard-products.d.ts +2 -0
- package/dist/standard-products.js +6 -0
- package/dist/standard.d.ts +18 -0
- package/dist/standard.js +595 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/application-stack-standard.md +700 -0
- package/package.json +29 -51
- package/CHANGELOG.md +0 -178
- package/CONTRIBUTING.md +0 -77
- package/SECURITY.md +0 -37
- package/SKILL.md +0 -339
- package/assets/images/svg/ably-symbol-color.svg +0 -8
- package/bin/anhedral.js +0 -2
- package/dist/architecture/contributions.d.ts +0 -28
- package/dist/architecture/contributions.js +0 -125
- package/dist/architecture/index.d.ts +0 -12
- package/dist/architecture/index.js +0 -6
- package/dist/architecture/manifest.d.ts +0 -53
- package/dist/architecture/manifest.js +0 -283
- package/dist/architecture/modules.d.ts +0 -41
- package/dist/architecture/modules.js +0 -187
- package/dist/architecture/plan.d.ts +0 -58
- package/dist/architecture/plan.js +0 -145
- package/dist/architecture/templates.d.ts +0 -10
- package/dist/architecture/templates.js +0 -23
- package/dist/cli.d.ts +0 -32
- package/dist/cli.js +0 -281
- package/dist/composers/root.d.ts +0 -6
- package/dist/composers/root.js +0 -35
- package/dist/print.d.ts +0 -8
- package/dist/print.js +0 -48
- package/dist/prompts.d.ts +0 -6
- package/dist/prompts.js +0 -41
- package/dist/scaffold.d.ts +0 -64
- package/dist/scaffold.js +0 -2102
- package/dist/template-source.d.ts +0 -20
- package/dist/template-source.js +0 -180
- package/dist/templates/api.d.ts +0 -2
- package/dist/templates/api.js +0 -3187
- package/dist/templates/assets-private-proxy.d.ts +0 -3
- package/dist/templates/assets-private-proxy.js +0 -277
- package/dist/templates/desktop.js +0 -792
- package/dist/templates/electron-updater.d.ts +0 -3
- package/dist/templates/electron-updater.js +0 -358
- package/dist/templates/extension.js +0 -856
- package/dist/templates/first-run.d.ts +0 -2
- package/dist/templates/first-run.js +0 -167
- package/dist/templates/mobile.js +0 -861
- package/dist/templates/shared.d.ts +0 -2
- package/dist/templates/shared.js +0 -577
- package/dist/templates/web.d.ts +0 -2
- package/dist/templates/web.js +0 -569
- package/dist/toolchain.d.ts +0 -3
- package/dist/toolchain.js +0 -8
- package/dist/ui.d.ts +0 -33
- package/dist/ui.js +0 -199
- package/dist/workspace-config.d.ts +0 -11
- package/dist/workspace-config.js +0 -372
- package/docs/NORTHSTAR.md +0 -114
- package/docs/conventions.md +0 -94
- package/docs/engineering-principles.md +0 -88
- package/docs/master-stack-map.md +0 -199
- package/docs/output-tree-contract.md +0 -493
- package/docs/references/manual-scaffolding.md +0 -421
- package/docs/references/provisioning.md +0 -577
- package/scripts/render-master-stack-map.mjs +0 -510
- package/templates/api-fastify/apps/api/tsconfig.json +0 -20
- package/templates/catalog.json +0 -23
- package/templates/db-drizzle/packages/db/tsconfig.json +0 -14
- package/templates/desktop-electron/apps/desktop/tsconfig.json +0 -37
- package/templates/extension-wxt/apps/extension/tsconfig.json +0 -41
- package/templates/mobile-expo/apps/mobile/expo-env.d.ts +0 -1
- package/templates/web-next/apps/web/next-env.d.ts +0 -6
- /package/dist/{templates → platforms}/desktop.d.ts +0 -0
- /package/dist/{templates → platforms}/extension.d.ts +0 -0
- /package/dist/{templates → platforms}/mobile.d.ts +0 -0
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import {
|
|
5
|
-
siAppstore,
|
|
6
|
-
siClerk,
|
|
7
|
-
siCloudflare,
|
|
8
|
-
siCloudflareworkers,
|
|
9
|
-
siDrizzle,
|
|
10
|
-
siElectron,
|
|
11
|
-
siExpo,
|
|
12
|
-
siFastify,
|
|
13
|
-
siGithub,
|
|
14
|
-
siNeon,
|
|
15
|
-
siNextdotjs,
|
|
16
|
-
siReact,
|
|
17
|
-
siRevenuecat,
|
|
18
|
-
siShadcnui,
|
|
19
|
-
siStripe,
|
|
20
|
-
siTailwindcss,
|
|
21
|
-
siTypescript,
|
|
22
|
-
siVercel,
|
|
23
|
-
siWxt,
|
|
24
|
-
siZod,
|
|
25
|
-
} from 'simple-icons';
|
|
26
|
-
|
|
27
|
-
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
|
28
|
-
const repositoryRoot = resolve(scriptDirectory, '..');
|
|
29
|
-
const outputFlag = process.argv.indexOf('--output');
|
|
30
|
-
const outputPath = resolve(outputFlag >= 0 && process.argv[outputFlag + 1]
|
|
31
|
-
? process.argv[outputFlag + 1]
|
|
32
|
-
: resolve(repositoryRoot, 'assets/anhedral-cli-init.svg'));
|
|
33
|
-
const anhedralMarkPath = resolve(repositoryRoot, 'assets/images/svg/logo-white-subtract.svg');
|
|
34
|
-
const anhedralMarkSource = await readFile(anhedralMarkPath, 'utf8');
|
|
35
|
-
const ablySymbolPath = resolve(repositoryRoot, 'assets/images/svg/ably-symbol-color.svg');
|
|
36
|
-
const ablySymbolSource = await readFile(ablySymbolPath, 'utf8');
|
|
37
|
-
const [, ablySymbolContent = ''] = ablySymbolSource.match(/<svg\b[^>]*>([\s\S]*?)<\/svg>/) ?? [];
|
|
38
|
-
const [, markViewBox = '0 0 1820 2199'] = anhedralMarkSource.match(/viewBox="([^"]+)"/) ?? [];
|
|
39
|
-
const [, , markWidth = '1820', markHeight = '2199'] = markViewBox.split(/\s+/).map(Number);
|
|
40
|
-
const markPaths = [...anhedralMarkSource.matchAll(/<path\b[^>]*\bd="([^"]+)"[^>]*>/g)].map((match) => match[1]);
|
|
41
|
-
|
|
42
|
-
const W = 1920;
|
|
43
|
-
const H = 1180;
|
|
44
|
-
const palette = {
|
|
45
|
-
background: '#0c1117',
|
|
46
|
-
panel: '#0a1724',
|
|
47
|
-
panelEnd: '#07121d',
|
|
48
|
-
border: '#73777b',
|
|
49
|
-
divider: '#34424e',
|
|
50
|
-
text: '#f7f5f0',
|
|
51
|
-
muted: '#c4c7ca',
|
|
52
|
-
yellow: '#e9f600',
|
|
53
|
-
lime: '#b9f500',
|
|
54
|
-
cyan: '#51dce9',
|
|
55
|
-
orange: '#f48120',
|
|
56
|
-
red: '#ff3b3f',
|
|
57
|
-
blue: '#2585f9',
|
|
58
|
-
neon: '#35d6a8',
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const svg = [];
|
|
62
|
-
const escapeXml = (value) => String(value)
|
|
63
|
-
.replaceAll('&', '&')
|
|
64
|
-
.replaceAll('<', '<')
|
|
65
|
-
.replaceAll('>', '>')
|
|
66
|
-
.replaceAll('"', '"');
|
|
67
|
-
|
|
68
|
-
function text(x, y, value, {
|
|
69
|
-
size = 18,
|
|
70
|
-
fill = palette.text,
|
|
71
|
-
weight = 400,
|
|
72
|
-
anchor = 'start',
|
|
73
|
-
spacing = 0,
|
|
74
|
-
italic = false,
|
|
75
|
-
} = {}) {
|
|
76
|
-
svg.push(`<text x="${x}" y="${y}" fill="${fill}" font-family="Inter, Helvetica Neue, Arial, sans-serif" font-size="${size}" font-weight="${weight}" text-anchor="${anchor}" letter-spacing="${spacing}"${italic ? ' font-style="italic"' : ''}>${escapeXml(value)}</text>`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function rect(x, y, width, height, {
|
|
80
|
-
fill = 'url(#card-fill)', stroke = palette.border, strokeWidth = 1.15, radius = 12,
|
|
81
|
-
} = {}) {
|
|
82
|
-
svg.push(`<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}"/>`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function line(x1, y1, x2, y2, {
|
|
86
|
-
stroke = palette.divider, width = 1, dash = '', marker = '',
|
|
87
|
-
} = {}) {
|
|
88
|
-
svg.push(`<path d="M ${x1} ${y1} L ${x2} ${y2}" fill="none" stroke="${stroke}" stroke-width="${width}" stroke-linecap="round"${dash ? ` stroke-dasharray="${dash}"` : ''}${marker ? ` marker-end="url(#${marker})"` : ''}/>`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function path(d, {
|
|
92
|
-
stroke = palette.text, width = 3, dash = '', marker = 'arrow-white',
|
|
93
|
-
} = {}) {
|
|
94
|
-
svg.push(`<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${width}" stroke-linecap="round" stroke-linejoin="round"${dash ? ` stroke-dasharray="${dash}"` : ''}${marker ? ` marker-end="url(#${marker})"` : ''}/>`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function smoothConnector(x1, y1, x2, y2, {
|
|
98
|
-
curvature = 0.46,
|
|
99
|
-
...pathOptions
|
|
100
|
-
} = {}) {
|
|
101
|
-
const horizontalDistance = Math.abs(x2 - x1);
|
|
102
|
-
const direction = Math.sign(x2 - x1) || 1;
|
|
103
|
-
const controlOffset = Math.min(
|
|
104
|
-
horizontalDistance / 2,
|
|
105
|
-
Math.max(36, horizontalDistance * curvature),
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
path(
|
|
109
|
-
`M ${x1} ${y1} C ${x1 + direction * controlOffset} ${y1} ${x2 - direction * controlOffset} ${y2} ${x2} ${y2}`,
|
|
110
|
-
pathOptions,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function logo(icon, x, y, size, fill) {
|
|
115
|
-
const scale = size / 24;
|
|
116
|
-
svg.push(`<g transform="translate(${x} ${y}) scale(${scale})"><path d="${icon.path}" fill="${fill}"/></g>`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function anhedralMark(x, y, height) {
|
|
120
|
-
const scale = height / markHeight;
|
|
121
|
-
svg.push(`<g transform="translate(${x} ${y}) scale(${scale})">`);
|
|
122
|
-
for (const d of markPaths) svg.push(`<path d="${d}" fill="${palette.text}"/>`);
|
|
123
|
-
svg.push('</g>');
|
|
124
|
-
return markWidth * scale;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function ablySymbol(x, y, width) {
|
|
128
|
-
const scale = width / 78;
|
|
129
|
-
svg.push(`<g transform="translate(${x} ${y}) scale(${scale})">${ablySymbolContent}</g>`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function iconBox(x, y, size = 70) {
|
|
133
|
-
rect(x, y, size, size, { fill: '#07121d', stroke: palette.divider, radius: 11 });
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function customGlobe(x, y, size, stroke = palette.text) {
|
|
137
|
-
const cx = x + size / 2;
|
|
138
|
-
const cy = y + size / 2;
|
|
139
|
-
const r = size * 0.34;
|
|
140
|
-
svg.push(`<g fill="none" stroke="${stroke}" stroke-width="3">
|
|
141
|
-
<circle cx="${cx}" cy="${cy}" r="${r}"/>
|
|
142
|
-
<ellipse cx="${cx}" cy="${cy}" rx="${r * 0.45}" ry="${r}"/>
|
|
143
|
-
<path d="M ${cx - r} ${cy} H ${cx + r} M ${cx - r * 0.84} ${cy - r * 0.5} H ${cx + r * 0.84} M ${cx - r * 0.84} ${cy + r * 0.5} H ${cx + r * 0.84}"/>
|
|
144
|
-
</g>`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function customDesktop(x, y, size, stroke = palette.text) {
|
|
148
|
-
svg.push(`<g fill="none" stroke="${stroke}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
|
149
|
-
<rect x="${x + 9}" y="${y + 9}" width="${size - 18}" height="${size - 27}" rx="3"/>
|
|
150
|
-
<path d="M ${x + size / 2} ${y + size - 18} V ${y + size - 8} M ${x + size * 0.28} ${y + size - 8} H ${x + size * 0.72}"/>
|
|
151
|
-
</g>`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function customAppStore(x, y, size) {
|
|
155
|
-
const badgeSize = size - 16;
|
|
156
|
-
const badgeX = x + 8;
|
|
157
|
-
const badgeY = y + 8;
|
|
158
|
-
svg.push(`<rect x="${badgeX}" y="${badgeY}" width="${badgeSize}" height="${badgeSize}" rx="11" fill="url(#app-store-fill)"/>`);
|
|
159
|
-
logo(siAppstore, badgeX + 9, badgeY + 9, badgeSize - 18, palette.text);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function customGooglePlay(x, y, size) {
|
|
163
|
-
const scale = (size - 8) / 64;
|
|
164
|
-
svg.push(`<g transform="translate(${x + 4} ${y + 4}) scale(${scale})" stroke-linejoin="round">
|
|
165
|
-
<path d="M 9 6 L 39 32 L 9 58 Z" fill="#23d7f2"/>
|
|
166
|
-
<path d="M 9 6 L 44 24.5 L 39 32 Z" fill="#2bd66f"/>
|
|
167
|
-
<path d="M 39 32 L 44 24.5 L 56 30.5 Q 59 32 56 33.5 L 44 39.5 Z" fill="#ffd43b"/>
|
|
168
|
-
<path d="M 9 58 L 44 39.5 L 39 32 Z" fill="#f04b55"/>
|
|
169
|
-
</g>`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function customChrome(x, y, size) {
|
|
173
|
-
const badgeSize = size - 10;
|
|
174
|
-
const badgeX = x + 5;
|
|
175
|
-
const badgeY = y + 5;
|
|
176
|
-
const cx = badgeX + badgeSize / 2;
|
|
177
|
-
const cy = badgeY + badgeSize / 2;
|
|
178
|
-
const r = badgeSize * 0.36;
|
|
179
|
-
const halfR = r * 0.5;
|
|
180
|
-
const highR = r * Math.sqrt(3) / 2;
|
|
181
|
-
|
|
182
|
-
svg.push(`<rect x="${badgeX}" y="${badgeY}" width="${badgeSize}" height="${badgeSize}" rx="11" fill="${palette.text}"/>`);
|
|
183
|
-
svg.push(`<path d="M ${cx} ${cy} L ${cx - halfR} ${cy - highR} A ${r} ${r} 0 0 1 ${cx + r} ${cy} Z" fill="#ea4335"/>`);
|
|
184
|
-
svg.push(`<path d="M ${cx} ${cy} L ${cx + r} ${cy} A ${r} ${r} 0 0 1 ${cx - halfR} ${cy + highR} Z" fill="#fbbc04"/>`);
|
|
185
|
-
svg.push(`<path d="M ${cx} ${cy} L ${cx - halfR} ${cy + highR} A ${r} ${r} 0 0 1 ${cx - halfR} ${cy - highR} Z" fill="#34a853"/>`);
|
|
186
|
-
svg.push(`<circle cx="${cx}" cy="${cy}" r="${r * 0.48}" fill="${palette.text}"/>`);
|
|
187
|
-
svg.push(`<circle cx="${cx}" cy="${cy}" r="${r * 0.36}" fill="#4285f4"/>`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function card(x, y, width, height, {
|
|
191
|
-
title,
|
|
192
|
-
subtitle,
|
|
193
|
-
detail = '',
|
|
194
|
-
icon,
|
|
195
|
-
iconColor = palette.text,
|
|
196
|
-
secondIcon,
|
|
197
|
-
secondIconColor = palette.text,
|
|
198
|
-
secondEmbeddedIcon,
|
|
199
|
-
customIcon,
|
|
200
|
-
cornerIcon,
|
|
201
|
-
cornerIconColor = palette.text,
|
|
202
|
-
iconScale = 52,
|
|
203
|
-
compact = false,
|
|
204
|
-
}) {
|
|
205
|
-
rect(x, y, width, height);
|
|
206
|
-
const boxSize = height - 24;
|
|
207
|
-
const boxX = x + 16;
|
|
208
|
-
const boxY = y + 12;
|
|
209
|
-
iconBox(boxX, boxY, boxSize);
|
|
210
|
-
|
|
211
|
-
if (customIcon === 'globe') customGlobe(boxX, boxY, boxSize, iconColor);
|
|
212
|
-
else if (customIcon === 'desktop') customDesktop(boxX, boxY, boxSize, iconColor);
|
|
213
|
-
else if (customIcon === 'app-store') customAppStore(boxX, boxY, boxSize);
|
|
214
|
-
else if (customIcon === 'google-play') customGooglePlay(boxX, boxY, boxSize);
|
|
215
|
-
else if (customIcon === 'chrome') customChrome(boxX, boxY, boxSize);
|
|
216
|
-
else if (customIcon === 'ably') {
|
|
217
|
-
const ablyWidth = boxSize - 12;
|
|
218
|
-
ablySymbol(boxX + 6, boxY + (boxSize - ablyWidth * 64 / 78) / 2, ablyWidth);
|
|
219
|
-
}
|
|
220
|
-
else if (icon) {
|
|
221
|
-
if (secondIcon || secondEmbeddedIcon) {
|
|
222
|
-
const pairedSize = compact ? 26 : 38;
|
|
223
|
-
const pairedInset = compact ? 5 : 6;
|
|
224
|
-
logo(icon, boxX + pairedInset, boxY + (boxSize - pairedSize) / 2, pairedSize, iconColor);
|
|
225
|
-
if (secondEmbeddedIcon === 'ably') {
|
|
226
|
-
ablySymbol(boxX + boxSize - pairedSize - 4, boxY + (boxSize - pairedSize * 64 / 78) / 2, pairedSize);
|
|
227
|
-
} else {
|
|
228
|
-
logo(
|
|
229
|
-
secondIcon,
|
|
230
|
-
boxX + boxSize - pairedSize - pairedInset,
|
|
231
|
-
boxY + (boxSize - pairedSize) / 2,
|
|
232
|
-
pairedSize,
|
|
233
|
-
secondIconColor,
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
const renderedIconScale = compact ? Math.min(iconScale, boxSize - 8) : iconScale;
|
|
238
|
-
logo(
|
|
239
|
-
icon,
|
|
240
|
-
boxX + (boxSize - renderedIconScale) / 2,
|
|
241
|
-
boxY + (boxSize - renderedIconScale) / 2,
|
|
242
|
-
renderedIconScale,
|
|
243
|
-
iconColor,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const dividerX = boxX + boxSize + (compact ? 12 : 10);
|
|
249
|
-
const textX = dividerX + (compact ? 20 : 24);
|
|
250
|
-
line(dividerX, y + 14, dividerX, y + height - 14);
|
|
251
|
-
text(textX, y + (compact ? 31 : 42), title, { size: compact ? 18 : 22, weight: 500 });
|
|
252
|
-
text(textX, y + (compact ? 55 : 70), subtitle, { size: compact ? 14 : 16, fill: palette.muted });
|
|
253
|
-
if (detail) text(textX, y + (compact ? 75 : 91), detail, { size: compact ? 12 : 13, fill: palette.muted });
|
|
254
|
-
if (cornerIcon) logo(cornerIcon, x + width - 32, y + 15, 17, cornerIconColor);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function heading(x, y, width, value) {
|
|
258
|
-
text(x + width / 2, y, value, {
|
|
259
|
-
size: 17, fill: palette.yellow, weight: 600, anchor: 'middle', spacing: 3.2,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function endpoint(x, y) {
|
|
264
|
-
svg.push(`<circle cx="${x}" cy="${y}" r="8.5" fill="${palette.lime}" filter="url(#endpoint-glow)"/>`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function dxTool(x, y, {
|
|
268
|
-
icon,
|
|
269
|
-
label,
|
|
270
|
-
color = palette.text,
|
|
271
|
-
anhedral = false,
|
|
272
|
-
}) {
|
|
273
|
-
const width = 88;
|
|
274
|
-
const height = 92;
|
|
275
|
-
rect(x, y, width, height, { fill: '#07121d', stroke: palette.divider, radius: 11 });
|
|
276
|
-
if (anhedral) {
|
|
277
|
-
const renderedWidth = markWidth / markHeight * 34;
|
|
278
|
-
anhedralMark(x + (width - renderedWidth) / 2, y + 14, 34);
|
|
279
|
-
} else {
|
|
280
|
-
logo(icon, x + 27, y + 14, 34, color);
|
|
281
|
-
}
|
|
282
|
-
text(x + width / 2, y + 72, label, {
|
|
283
|
-
size: 11,
|
|
284
|
-
fill: palette.muted,
|
|
285
|
-
weight: 500,
|
|
286
|
-
anchor: 'middle',
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
svg.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" role="img" aria-labelledby="title description">`);
|
|
291
|
-
svg.push('<title id="title">Anhedral Init Stack</title>');
|
|
292
|
-
svg.push('<desc id="description">A simplified architecture diagram showing DX tools, deployment destinations, four generated client surfaces, their shared Fastify API, RevenueCat and Stripe billing, Ably realtime, backend services, and a dedicated private R2 updater Worker.</desc>');
|
|
293
|
-
svg.push(`<defs>
|
|
294
|
-
<linearGradient id="card-fill" x1="0" y1="0" x2="1" y2="1">
|
|
295
|
-
<stop offset="0" stop-color="#0d1b29"/>
|
|
296
|
-
<stop offset="1" stop-color="#07121d"/>
|
|
297
|
-
</linearGradient>
|
|
298
|
-
<linearGradient id="app-store-fill" x1="0" y1="0" x2="0" y2="1">
|
|
299
|
-
<stop offset="0" stop-color="#24c7fa"/>
|
|
300
|
-
<stop offset="1" stop-color="#0878f9"/>
|
|
301
|
-
</linearGradient>
|
|
302
|
-
<filter id="endpoint-glow" x="-100%" y="-100%" width="300%" height="300%">
|
|
303
|
-
<feGaussianBlur in="SourceGraphic" stdDeviation="1.7"/>
|
|
304
|
-
</filter>
|
|
305
|
-
<marker id="arrow-white" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="${palette.text}"/></marker>
|
|
306
|
-
<marker id="arrow-yellow" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 Z" fill="${palette.yellow}"/></marker>
|
|
307
|
-
</defs>`);
|
|
308
|
-
svg.push(`<rect width="${W}" height="${H}" fill="${palette.background}"/>`);
|
|
309
|
-
|
|
310
|
-
// Official Anhedral asset plus wordmark.
|
|
311
|
-
const renderedMarkWidth = anhedralMark(28, 34, 58);
|
|
312
|
-
const brandRuleX = 28 + renderedMarkWidth + 17;
|
|
313
|
-
line(brandRuleX, 26, brandRuleX, 104, { stroke: palette.border, width: 1.5 });
|
|
314
|
-
text(brandRuleX + 22, 80, 'ANHEDRAL', { size: 27, weight: 500, spacing: 1.3 });
|
|
315
|
-
[
|
|
316
|
-
{ x: 32, value: 'Anhedral', underlineEnd: 366 },
|
|
317
|
-
{ x: 392, value: 'Init', underlineEnd: 506 },
|
|
318
|
-
{ x: 532, value: 'Stack', underlineEnd: 747 },
|
|
319
|
-
].forEach(({ x, value, underlineEnd }) => {
|
|
320
|
-
text(x, 176, value, { size: 78, weight: 700 });
|
|
321
|
-
line(x + 1, 195, underlineEnd, 195, { stroke: palette.yellow, width: 6 });
|
|
322
|
-
});
|
|
323
|
-
text(34, 240, 'One init flow for web, native apps, extensions, backend,', { size: 24 });
|
|
324
|
-
text(34, 274, 'storage, auth, subscriptions, and seamless desktop updates.', { size: 24 });
|
|
325
|
-
|
|
326
|
-
const dxToolStartX = 1187;
|
|
327
|
-
const dxToolY = 76;
|
|
328
|
-
const dxToolGap = 98;
|
|
329
|
-
heading(dxToolStartX, 52, 676, 'DX TOOLS');
|
|
330
|
-
[
|
|
331
|
-
{ icon: siTailwindcss, label: 'Tailwind', color: '#06b6d4' },
|
|
332
|
-
{ icon: siShadcnui, label: 'shadcn/ui', color: palette.text },
|
|
333
|
-
{ icon: siReact, label: 'RN Reusables', color: '#61dafb' },
|
|
334
|
-
{ icon: siTypescript, label: 'TypeScript', color: '#3178c6' },
|
|
335
|
-
{ icon: siZod, label: 'Zod', color: '#3e67b1' },
|
|
336
|
-
{ icon: siGithub, label: 'GitHub', color: palette.text },
|
|
337
|
-
{ label: 'Anhedral Skills', anhedral: true },
|
|
338
|
-
].forEach((tool, index) => dxTool(dxToolStartX + index * dxToolGap, dxToolY, tool));
|
|
339
|
-
|
|
340
|
-
const deployX = 54;
|
|
341
|
-
const deployW = 396;
|
|
342
|
-
const clientX = 620;
|
|
343
|
-
const clientW = 370;
|
|
344
|
-
const apiX = 1130;
|
|
345
|
-
const apiW = 208;
|
|
346
|
-
const serviceX = 1435;
|
|
347
|
-
const serviceW = 430;
|
|
348
|
-
|
|
349
|
-
heading(deployX, 318, deployW, 'DEPLOY & DISTRIBUTE');
|
|
350
|
-
heading(clientX, 318, clientW, 'CLIENT SURFACES');
|
|
351
|
-
heading(apiX, 318, serviceX + serviceW - apiX, 'BACKEND + SERVICES');
|
|
352
|
-
|
|
353
|
-
const deployRows = [342, 456, 570, 684, 798];
|
|
354
|
-
[
|
|
355
|
-
{ title: 'Web', subtitle: 'Vercel preview + production', detail: 'hosts web + API', customIcon: 'globe' },
|
|
356
|
-
{ title: 'App Store', subtitle: 'iOS distribution', detail: 'EAS signed release', customIcon: 'app-store' },
|
|
357
|
-
{ title: 'Google Play', subtitle: 'Android distribution', detail: 'EAS signed release', customIcon: 'google-play' },
|
|
358
|
-
{ title: 'Browser Web Stores', subtitle: 'extension distribution', detail: 'reviewed WXT ZIP', customIcon: 'chrome' },
|
|
359
|
-
{ title: 'Desktop Releases', subtitle: 'macOS · Windows · Linux', detail: 'signed installers + seamless updates', customIcon: 'desktop', iconColor: palette.text },
|
|
360
|
-
].forEach((item, index) => card(deployX, deployRows[index], deployW, 102, item));
|
|
361
|
-
|
|
362
|
-
const clientRows = [342, 494, 646, 798];
|
|
363
|
-
[
|
|
364
|
-
{
|
|
365
|
-
title: 'Next.js',
|
|
366
|
-
subtitle: 'shadcn/ui',
|
|
367
|
-
detail: 'web product + server routes',
|
|
368
|
-
icon: siNextdotjs,
|
|
369
|
-
iconColor: palette.text,
|
|
370
|
-
iconScale: 58,
|
|
371
|
-
cornerIcon: siVercel,
|
|
372
|
-
},
|
|
373
|
-
{ title: 'Expo Native', subtitle: 'React Native Reusables', detail: 'iOS + Android', icon: siExpo, iconColor: palette.text, iconScale: 58 },
|
|
374
|
-
{ title: 'WXT Extension', subtitle: 'Chrome · Firefox · Edge', detail: 'MV3 background + side panel', icon: siWxt, iconColor: palette.lime, iconScale: 58 },
|
|
375
|
-
{ title: 'Electron Desktop', subtitle: 'macOS · Windows · Linux', detail: 'electron-updater enabled', icon: siElectron, iconColor: palette.cyan, iconScale: 58 },
|
|
376
|
-
].forEach((item, index) => card(clientX, clientRows[index], clientW, 112, item));
|
|
377
|
-
|
|
378
|
-
// One central API, matching the supplied simplified architecture.
|
|
379
|
-
rect(apiX, 474, apiW, 296, { radius: 14 });
|
|
380
|
-
const apiIconBoxSize = 88;
|
|
381
|
-
const apiIconBoxX = apiX + (apiW - apiIconBoxSize) / 2;
|
|
382
|
-
const apiIconBoxY = 490;
|
|
383
|
-
iconBox(apiIconBoxX, apiIconBoxY, apiIconBoxSize);
|
|
384
|
-
logo(siFastify, apiIconBoxX + 10, apiIconBoxY + 10, 68, palette.text);
|
|
385
|
-
logo(siVercel, apiX + apiW - 32, 490, 17, palette.text);
|
|
386
|
-
line(apiX + 18, 602, apiX + apiW - 18, 602, { stroke: palette.divider });
|
|
387
|
-
text(apiX + apiW / 2, 643, 'Fastify Backend', { size: 25, weight: 500, anchor: 'middle' });
|
|
388
|
-
text(apiX + apiW / 2, 675, 'API + business logic', { size: 17, fill: palette.muted, anchor: 'middle' });
|
|
389
|
-
|
|
390
|
-
const serviceRows = [342, 438, 534, 630, 726, 822, 918];
|
|
391
|
-
const serviceCardHeight = 88;
|
|
392
|
-
[
|
|
393
|
-
{
|
|
394
|
-
title: 'Neon + Drizzle',
|
|
395
|
-
subtitle: 'Postgres + ORM',
|
|
396
|
-
detail: 'reviewed migrations',
|
|
397
|
-
icon: siNeon,
|
|
398
|
-
iconColor: palette.neon,
|
|
399
|
-
secondIcon: siDrizzle,
|
|
400
|
-
secondIconColor: palette.yellow,
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
title: 'Clerk Auth',
|
|
404
|
-
subtitle: 'identity + sessions',
|
|
405
|
-
detail: 'verified server-side',
|
|
406
|
-
icon: siClerk,
|
|
407
|
-
iconColor: palette.text,
|
|
408
|
-
iconScale: 56,
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
title: 'RevenueCat + Stripe',
|
|
412
|
-
subtitle: 'subscriptions + payments',
|
|
413
|
-
detail: 'billing integration + shared entitlements',
|
|
414
|
-
icon: siRevenuecat,
|
|
415
|
-
iconColor: palette.red,
|
|
416
|
-
secondIcon: siStripe,
|
|
417
|
-
secondIconColor: '#635bff',
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
title: 'Ably Realtime',
|
|
421
|
-
subtitle: 'scoped invalidations',
|
|
422
|
-
detail: 'outbox → event → client refetch',
|
|
423
|
-
customIcon: 'ably',
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
title: 'Assets Worker',
|
|
427
|
-
subtitle: 'PRIVATE R2 assets binding',
|
|
428
|
-
detail: 'assets.example.com · authorized reads',
|
|
429
|
-
icon: siCloudflareworkers,
|
|
430
|
-
iconColor: palette.orange,
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
title: 'Updater Worker',
|
|
434
|
-
subtitle: 'PRIVATE R2 updates binding',
|
|
435
|
-
detail: 'updates.example.com → electron-updater',
|
|
436
|
-
icon: siCloudflareworkers,
|
|
437
|
-
iconColor: palette.orange,
|
|
438
|
-
secondIcon: siElectron,
|
|
439
|
-
secondIconColor: palette.cyan,
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
title: 'Cloudflare DNS + Vercel',
|
|
443
|
-
subtitle: 'app domain: DNS-only → Vercel',
|
|
444
|
-
detail: 'DNSSEC · assets/updates Worker domains',
|
|
445
|
-
icon: siCloudflare,
|
|
446
|
-
iconColor: palette.orange,
|
|
447
|
-
secondIcon: siVercel,
|
|
448
|
-
secondIconColor: palette.text,
|
|
449
|
-
},
|
|
450
|
-
].forEach((item, index) => card(serviceX, serviceRows[index], serviceW, serviceCardHeight, { ...item, compact: true }));
|
|
451
|
-
|
|
452
|
-
// Publish connections are mapped by product surface rather than by row. Expo
|
|
453
|
-
// intentionally fans out to both native stores; every lane meets both card
|
|
454
|
-
// boundaries, even when their vertical centers differ.
|
|
455
|
-
const publishConnections = [
|
|
456
|
-
{ clientIndex: 0, deployIndex: 0 },
|
|
457
|
-
{ clientIndex: 1, deployIndex: 1 },
|
|
458
|
-
{ clientIndex: 1, deployIndex: 2 },
|
|
459
|
-
{ clientIndex: 2, deployIndex: 3 },
|
|
460
|
-
{ clientIndex: 3, deployIndex: 4 },
|
|
461
|
-
];
|
|
462
|
-
|
|
463
|
-
publishConnections.forEach(({ clientIndex, deployIndex }) => {
|
|
464
|
-
smoothConnector(
|
|
465
|
-
clientX,
|
|
466
|
-
clientRows[clientIndex] + 56,
|
|
467
|
-
deployX + deployW,
|
|
468
|
-
deployRows[deployIndex] + 51,
|
|
469
|
-
{
|
|
470
|
-
stroke: palette.yellow,
|
|
471
|
-
dash: '10 8',
|
|
472
|
-
marker: 'arrow-yellow',
|
|
473
|
-
},
|
|
474
|
-
);
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
// Client-to-API runtime lines.
|
|
478
|
-
const apiTargets = [520, 590, 660, 730];
|
|
479
|
-
clientRows.forEach((row, index) => {
|
|
480
|
-
const sourceY = row + 56;
|
|
481
|
-
const targetY = apiTargets[index];
|
|
482
|
-
smoothConnector(clientX + clientW, sourceY, apiX, targetY);
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
// API-to-service lines. Billing and realtime remain separate downstream
|
|
486
|
-
// services, and the assets API keeps its own fifth connection.
|
|
487
|
-
const serviceApiSources = [500, 555, 610, 665, 720];
|
|
488
|
-
serviceRows.slice(0, 5).forEach((row, index) => {
|
|
489
|
-
const sourceY = serviceApiSources[index];
|
|
490
|
-
const targetY = row + serviceCardHeight / 2;
|
|
491
|
-
smoothConnector(apiX + apiW, sourceY, serviceX, targetY, { marker: '' });
|
|
492
|
-
endpoint(serviceX, targetY);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// One compact legend; no command, agent, or updater panels below the architecture.
|
|
496
|
-
const legendY = 1050;
|
|
497
|
-
const legendLineY = legendY + 43;
|
|
498
|
-
rect(126, legendY, 1668, 86, { radius: 12 });
|
|
499
|
-
line(246, legendLineY, 350, legendLineY, { stroke: palette.text, width: 3, marker: 'arrow-white' });
|
|
500
|
-
text(374, legendLineY + 7, 'runtime API flow', { size: 17, fill: palette.muted });
|
|
501
|
-
line(720, legendLineY, 824, legendLineY, { stroke: palette.yellow, width: 3, dash: '10 8', marker: 'arrow-yellow' });
|
|
502
|
-
text(848, legendLineY + 7, 'deploy / publish', { size: 17, fill: palette.muted });
|
|
503
|
-
line(1198, legendLineY, 1302, legendLineY, { stroke: palette.text, width: 3 });
|
|
504
|
-
endpoint(1302, legendLineY);
|
|
505
|
-
text(1332, legendLineY + 7, 'backend service connection', { size: 17, fill: palette.muted });
|
|
506
|
-
|
|
507
|
-
svg.push('</svg>');
|
|
508
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
509
|
-
await writeFile(outputPath, `${svg.join('\n')}\n`, 'utf8');
|
|
510
|
-
console.log(`Rendered ${outputPath}`);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"noUncheckedIndexedAccess": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"noEmit": true,
|
|
10
|
-
"types": [
|
|
11
|
-
"node"
|
|
12
|
-
],
|
|
13
|
-
"verbatimModuleSyntax": true,
|
|
14
|
-
"isolatedModules": true
|
|
15
|
-
},
|
|
16
|
-
"include": [
|
|
17
|
-
"src/**/*",
|
|
18
|
-
"tests/**/*"
|
|
19
|
-
]
|
|
20
|
-
}
|
package/templates/catalog.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"templates": {
|
|
4
|
-
"api-fastify": {
|
|
5
|
-
"sha256": "7ec1b5fffa1a53405fc55238d09c013ace048986afcefe3a653322f060688e51"
|
|
6
|
-
},
|
|
7
|
-
"db-drizzle": {
|
|
8
|
-
"sha256": "5d141a4982d7a97aba5ffc0977c871761b15507ec1694f6e841a53416e1bdbd1"
|
|
9
|
-
},
|
|
10
|
-
"desktop-electron": {
|
|
11
|
-
"sha256": "2aa217fa7ce1031aea3e87f5d522628998d7bca90e11c74c18064209f6e479e1"
|
|
12
|
-
},
|
|
13
|
-
"extension-wxt": {
|
|
14
|
-
"sha256": "1832bd1c9d83b74326c70448f88da29eb8f1cfd5863eb51f9b9d6e3efd81a75b"
|
|
15
|
-
},
|
|
16
|
-
"mobile-expo": {
|
|
17
|
-
"sha256": "d63c4645953d4dbd60bd6ea82f43053a1ba644544eaed6145754fb0fc873c4fc"
|
|
18
|
-
},
|
|
19
|
-
"web-next": {
|
|
20
|
-
"sha256": "96b9d5decd26c7a417d08fec33a62cce42040b68f45633c731da13eb9b29def6"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"lib": [
|
|
6
|
-
"DOM",
|
|
7
|
-
"DOM.Iterable",
|
|
8
|
-
"ES2022"
|
|
9
|
-
],
|
|
10
|
-
"allowJs": false,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"esModuleInterop": true,
|
|
13
|
-
"allowSyntheticDefaultImports": true,
|
|
14
|
-
"strict": true,
|
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
|
16
|
-
"module": "ESNext",
|
|
17
|
-
"moduleResolution": "Bundler",
|
|
18
|
-
"resolveJsonModule": true,
|
|
19
|
-
"isolatedModules": true,
|
|
20
|
-
"noEmit": true,
|
|
21
|
-
"jsx": "react-jsx",
|
|
22
|
-
"types": [
|
|
23
|
-
"node",
|
|
24
|
-
"vite/client"
|
|
25
|
-
],
|
|
26
|
-
"paths": {
|
|
27
|
-
"@/*": [
|
|
28
|
-
"./src/renderer/*"
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"include": [
|
|
33
|
-
"src/**/*",
|
|
34
|
-
"vite.config.ts"
|
|
35
|
-
],
|
|
36
|
-
"references": []
|
|
37
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"isolatedModules": true,
|
|
12
|
-
"allowImportingTsExtensions": true,
|
|
13
|
-
"noEmit": true,
|
|
14
|
-
"jsx": "react-jsx",
|
|
15
|
-
"lib": [
|
|
16
|
-
"DOM",
|
|
17
|
-
"DOM.Iterable",
|
|
18
|
-
"ESNext"
|
|
19
|
-
],
|
|
20
|
-
"types": [
|
|
21
|
-
"wxt/browser",
|
|
22
|
-
"@wxt-dev/module-react",
|
|
23
|
-
"chrome"
|
|
24
|
-
],
|
|
25
|
-
"paths": {
|
|
26
|
-
"@/*": [
|
|
27
|
-
"./src/*"
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"include": [
|
|
32
|
-
"**/*.ts",
|
|
33
|
-
"**/*.tsx",
|
|
34
|
-
".wxt/wxt.d.ts"
|
|
35
|
-
],
|
|
36
|
-
"exclude": [
|
|
37
|
-
"node_modules",
|
|
38
|
-
".output",
|
|
39
|
-
"dist"
|
|
40
|
-
]
|
|
41
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="expo/types" />
|
|
File without changes
|
|
File without changes
|
|
File without changes
|