@open-press/core 0.8.0 → 1.0.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/README.md +6 -3
- package/engine/cli.mjs +8 -8
- package/engine/commands/_shared.mjs +37 -15
- package/engine/commands/image.mjs +29 -0
- package/engine/commands/skills-sync.mjs +71 -0
- package/engine/commands/typecheck.mjs +63 -1
- package/engine/commands/upgrade.mjs +3 -3
- package/engine/document-export.mjs +1 -1
- package/engine/output/chrome-pdf.mjs +92 -0
- package/engine/output/static-server.mjs +48 -9
- package/engine/react/comment-marker.mjs +13 -13
- package/engine/react/document-entry.mjs +35 -28
- package/engine/react/document-export.mjs +309 -170
- package/engine/react/mdx-compile.mjs +30 -0
- package/engine/react/measurement-css.mjs +21 -0
- package/engine/react/object-entities.mjs +85 -0
- package/engine/react/pagination/allocator.mjs +48 -3
- package/engine/react/pagination.mjs +1 -1
- package/engine/react/pipeline/allocate.mjs +31 -65
- package/engine/react/pipeline/frame-measurement.mjs +4 -0
- package/engine/react/press-tree-inspection.mjs +172 -0
- package/engine/react/sources/mdx-resolver.mjs +1 -1
- package/engine/react/style-discovery.mjs +22 -4
- package/engine/runtime/config.d.mts +8 -0
- package/engine/runtime/config.mjs +57 -60
- package/engine/runtime/file-utils.mjs +9 -1
- package/engine/runtime/page-geometry.mjs +131 -0
- package/engine/runtime/source-text-tools.mjs +1 -1
- package/engine/runtime/source-workspace.mjs +12 -3
- package/engine/runtime/validation.mjs +19 -10
- package/package.json +3 -5
- package/src/openpress/app/OpenPressApp.tsx +173 -17
- package/src/openpress/app/OpenPressRuntime.tsx +10 -2
- package/src/openpress/app/WorkspaceGalleryPage.tsx +219 -0
- package/src/openpress/core/Frame.tsx +20 -7
- package/src/openpress/core/FrameContext.tsx +2 -0
- package/src/openpress/core/Press.tsx +25 -4
- package/src/openpress/core/Workspace.tsx +36 -0
- package/src/openpress/core/index.tsx +10 -3
- package/src/openpress/core/primitives.tsx +48 -1
- package/src/openpress/core/types.ts +86 -41
- package/src/openpress/core/useSource.ts +1 -1
- package/src/openpress/document-model/documentTypes.ts +9 -0
- package/src/openpress/document-model/index.ts +1 -0
- package/src/openpress/document-model/objectEntityModel.ts +4 -0
- package/src/openpress/document-model/workspaceManifestModel.ts +57 -0
- package/src/openpress/mdx/index.ts +15 -7
- package/src/openpress/reader/PageThumbnailsPanel.tsx +168 -0
- package/src/openpress/reader/index.ts +1 -0
- package/src/openpress/workbench/Workbench.tsx +120 -21
- package/src/openpress/workbench/actions/ExportImageControl.tsx +96 -0
- package/src/openpress/workbench/actions/SearchControl.tsx +3 -3
- package/src/openpress/workbench/actions/index.ts +1 -0
- package/src/openpress/workbench/inspector/useInspectorComments.ts +7 -1
- package/src/openpress/workbench/panels/PendingCommentsPanel.tsx +5 -1
- package/src/openpress/workbench/project/ProjectEntryPanel.tsx +4 -2
- package/src/openpress/workbench/workbenchFormatters.ts +2 -2
- package/src/styles/openpress/reader-runtime.css +9 -0
- package/src/styles/openpress/workbench-panels.css +113 -0
- package/src/styles/openpress/workspace-gallery.css +300 -0
- package/src/styles/openpress.css +1 -0
- package/tsconfig.json +1 -1
- package/engine/commands/init.mjs +0 -24
- package/engine/init.mjs +0 -90
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/* Workspace gallery — the reader's landing page for multi-Press
|
|
2
|
+
workspaces. Shows a card per <Press> child; clicking enters that
|
|
3
|
+
document's reader. Single-Press workspaces skip the gallery and
|
|
4
|
+
load the document directly, so this CSS is dormant until users
|
|
5
|
+
add a second <Press> to their <Workspace>.
|
|
6
|
+
|
|
7
|
+
Layout intent: Figma-style file grid — uniform card width, fixed
|
|
8
|
+
thumbnail aspect ratio (4:3), filename + meta below. Each card
|
|
9
|
+
loads its first page asynchronously and renders it scaled-down
|
|
10
|
+
inside the thumbnail slot. */
|
|
11
|
+
|
|
12
|
+
.openpress-workspace-gallery {
|
|
13
|
+
--workspace-bg: #10110f;
|
|
14
|
+
--workspace-bg-soft: #171813;
|
|
15
|
+
--workspace-ink: #f4f1e8;
|
|
16
|
+
--workspace-muted: rgba(244, 241, 232, 0.52);
|
|
17
|
+
--workspace-line: rgba(244, 241, 232, 0.12);
|
|
18
|
+
--workspace-card: #f7f5ee;
|
|
19
|
+
--workspace-card-ink: #141411;
|
|
20
|
+
--workspace-card-muted: #65635d;
|
|
21
|
+
--workspace-card-line: rgba(20, 20, 17, 0.1);
|
|
22
|
+
--workspace-card-stage: #e8e5dc;
|
|
23
|
+
display: grid;
|
|
24
|
+
gap: 2rem;
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
max-width: none;
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 3.6rem clamp(2rem, 4vw, 4.5rem) 6rem;
|
|
29
|
+
font-family: var(--openpress-font-body, system-ui, sans-serif);
|
|
30
|
+
color: var(--workspace-ink);
|
|
31
|
+
background:
|
|
32
|
+
linear-gradient(180deg, var(--workspace-bg-soft), var(--workspace-bg) 42rem),
|
|
33
|
+
var(--workspace-bg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.openpress-workspace-gallery__header {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
39
|
+
align-items: end;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
gap: 2.5rem;
|
|
42
|
+
padding: 0 0 1.45rem;
|
|
43
|
+
border-bottom: 1px solid var(--workspace-line);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.openpress-workspace-gallery__headline {
|
|
47
|
+
display: grid;
|
|
48
|
+
gap: 0.75rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.openpress-workspace-gallery__eyebrow {
|
|
52
|
+
margin: 0;
|
|
53
|
+
color: var(--workspace-muted);
|
|
54
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
55
|
+
font-size: 0.68rem;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
letter-spacing: 0.16em;
|
|
58
|
+
text-transform: uppercase;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.openpress-workspace-gallery__header h1 {
|
|
62
|
+
margin: 0;
|
|
63
|
+
font-family: var(--openpress-font-display, var(--openpress-font-body, system-ui));
|
|
64
|
+
font-size: clamp(2.6rem, 5.4vw, 5.2rem);
|
|
65
|
+
font-weight: 720;
|
|
66
|
+
line-height: 0.94;
|
|
67
|
+
letter-spacing: -0.035em;
|
|
68
|
+
color: var(--workspace-ink);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.openpress-workspace-gallery__count {
|
|
72
|
+
margin: 0;
|
|
73
|
+
display: grid;
|
|
74
|
+
justify-items: end;
|
|
75
|
+
gap: 0.25rem;
|
|
76
|
+
min-width: 4.5rem;
|
|
77
|
+
color: var(--workspace-ink);
|
|
78
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
79
|
+
line-height: 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.openpress-workspace-gallery__count span {
|
|
83
|
+
color: var(--workspace-ink);
|
|
84
|
+
font-size: 2rem;
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
letter-spacing: -0.04em;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.openpress-workspace-gallery__count small {
|
|
90
|
+
color: var(--workspace-muted);
|
|
91
|
+
font-size: 0.62rem;
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
letter-spacing: 0.14em;
|
|
94
|
+
text-transform: uppercase;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.openpress-workspace-gallery__grid {
|
|
98
|
+
list-style: none;
|
|
99
|
+
margin: 0;
|
|
100
|
+
padding: 0;
|
|
101
|
+
display: grid;
|
|
102
|
+
align-items: start;
|
|
103
|
+
/* Uniform card size — Figma-style. Outer thumb is fixed 4:3, the
|
|
104
|
+
inner page letterboxes to its own geometry. */
|
|
105
|
+
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
|
106
|
+
gap: 1.5rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.openpress-workspace-gallery__item {
|
|
110
|
+
display: flex;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.openpress-workspace-gallery__card {
|
|
114
|
+
appearance: none;
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-rows: auto minmax(6.75rem, auto);
|
|
117
|
+
align-self: start;
|
|
118
|
+
width: 100%;
|
|
119
|
+
padding: 0;
|
|
120
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
background: var(--workspace-card);
|
|
123
|
+
color: var(--workspace-card-ink);
|
|
124
|
+
text-align: left;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
transition:
|
|
128
|
+
transform 160ms ease,
|
|
129
|
+
box-shadow 160ms ease,
|
|
130
|
+
border-color 160ms ease;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.openpress-workspace-gallery__card:hover,
|
|
134
|
+
.openpress-workspace-gallery__card:focus-visible {
|
|
135
|
+
border-color: rgba(255, 255, 255, 0.28);
|
|
136
|
+
transform: translateY(-2px);
|
|
137
|
+
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
|
|
138
|
+
outline: none;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.openpress-workspace-gallery__thumb {
|
|
142
|
+
position: relative;
|
|
143
|
+
display: block;
|
|
144
|
+
width: 100%;
|
|
145
|
+
/* Uniform 4:3 outer slot across every card. The page itself
|
|
146
|
+
letterboxes inside via centered scale, so each Press shows at its
|
|
147
|
+
own true aspect against the gradient background. */
|
|
148
|
+
aspect-ratio: 4 / 3;
|
|
149
|
+
background:
|
|
150
|
+
linear-gradient(
|
|
151
|
+
135deg,
|
|
152
|
+
color-mix(in srgb, var(--workspace-card-ink) 5%, var(--workspace-card-stage)),
|
|
153
|
+
var(--workspace-card-stage)
|
|
154
|
+
);
|
|
155
|
+
border-bottom: 1px solid var(--workspace-card-line);
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.openpress-workspace-gallery__thumb::before {
|
|
160
|
+
content: "";
|
|
161
|
+
position: absolute;
|
|
162
|
+
inset: 0;
|
|
163
|
+
pointer-events: none;
|
|
164
|
+
background-image:
|
|
165
|
+
linear-gradient(rgba(20, 20, 17, 0.05) 1px, transparent 1px),
|
|
166
|
+
linear-gradient(90deg, rgba(20, 20, 17, 0.05) 1px, transparent 1px);
|
|
167
|
+
background-size: 24px 24px;
|
|
168
|
+
opacity: 0.5;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.openpress-workspace-gallery__thumb-stage {
|
|
172
|
+
position: absolute;
|
|
173
|
+
inset: clamp(0.85rem, 6%, 1.45rem);
|
|
174
|
+
display: grid;
|
|
175
|
+
place-items: center;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.openpress-workspace-gallery__thumb-frame {
|
|
179
|
+
position: relative;
|
|
180
|
+
box-shadow:
|
|
181
|
+
0 18px 36px rgba(20, 20, 17, 0.18),
|
|
182
|
+
0 0 0 1px rgba(20, 20, 17, 0.08);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.openpress-workspace-gallery__thumb-stage .openpress-public-page {
|
|
186
|
+
display: block;
|
|
187
|
+
pointer-events: none;
|
|
188
|
+
user-select: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.openpress-workspace-gallery__thumb-placeholder {
|
|
192
|
+
position: absolute;
|
|
193
|
+
inset: clamp(0.85rem, 6%, 1.45rem);
|
|
194
|
+
display: grid;
|
|
195
|
+
place-items: center;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.openpress-workspace-gallery__thumb-skel {
|
|
199
|
+
display: block;
|
|
200
|
+
width: 70%;
|
|
201
|
+
height: 70%;
|
|
202
|
+
background:
|
|
203
|
+
repeating-linear-gradient(
|
|
204
|
+
135deg,
|
|
205
|
+
rgba(20, 20, 17, 0.04) 0 6px,
|
|
206
|
+
transparent 6px 14px
|
|
207
|
+
),
|
|
208
|
+
#fff;
|
|
209
|
+
border: 1px solid var(--workspace-card-line);
|
|
210
|
+
border-radius: 3px;
|
|
211
|
+
box-shadow: 0 14px 28px rgba(20, 20, 17, 0.14);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.openpress-workspace-gallery__thumb-placeholder[data-state="loading"] .openpress-workspace-gallery__thumb-skel {
|
|
215
|
+
animation: openpress-gallery-skel-pulse 1.4s ease-in-out infinite;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@keyframes openpress-gallery-skel-pulse {
|
|
219
|
+
0%, 100% { opacity: 1; }
|
|
220
|
+
50% { opacity: 0.55; }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.openpress-workspace-gallery__body {
|
|
224
|
+
display: grid;
|
|
225
|
+
align-content: space-between;
|
|
226
|
+
gap: 1.2rem;
|
|
227
|
+
min-height: 6.75rem;
|
|
228
|
+
padding: 1.1rem 1.22rem 1.15rem;
|
|
229
|
+
background: var(--workspace-card);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.openpress-workspace-gallery__title {
|
|
233
|
+
display: block;
|
|
234
|
+
color: var(--workspace-card-ink);
|
|
235
|
+
font-size: 1rem;
|
|
236
|
+
font-weight: 700;
|
|
237
|
+
line-height: 1.2;
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
text-overflow: ellipsis;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.openpress-workspace-gallery__meta {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
gap: 0.7rem;
|
|
248
|
+
flex-wrap: wrap;
|
|
249
|
+
color: var(--workspace-card-muted);
|
|
250
|
+
font-family: var(--openpress-font-mono, ui-monospace, monospace);
|
|
251
|
+
font-size: 0.66rem;
|
|
252
|
+
letter-spacing: 0.03em;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.openpress-workspace-gallery__slug {
|
|
256
|
+
max-width: 13rem;
|
|
257
|
+
color: color-mix(in srgb, var(--workspace-card-ink) 72%, transparent);
|
|
258
|
+
font-weight: 500;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
text-overflow: ellipsis;
|
|
261
|
+
text-transform: uppercase;
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.openpress-workspace-gallery__dot {
|
|
266
|
+
color: color-mix(in srgb, var(--workspace-card-muted) 55%, transparent);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.openpress-workspace-gallery__pages,
|
|
270
|
+
.openpress-workspace-gallery__geom {
|
|
271
|
+
color: var(--workspace-card-muted);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.openpress-workspace-gallery__geom {
|
|
275
|
+
display: inline-flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
min-height: 1.35rem;
|
|
278
|
+
padding: 0 0.48rem;
|
|
279
|
+
border: 1px solid var(--workspace-card-line);
|
|
280
|
+
border-radius: 4px;
|
|
281
|
+
background: rgba(255, 255, 255, 0.36);
|
|
282
|
+
color: color-mix(in srgb, var(--workspace-card-ink) 76%, transparent);
|
|
283
|
+
font-size: 0.62rem;
|
|
284
|
+
white-space: nowrap;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@media (max-width: 720px) {
|
|
288
|
+
.openpress-workspace-gallery {
|
|
289
|
+
padding: 2.25rem 1rem 4rem;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.openpress-workspace-gallery__header {
|
|
293
|
+
display: grid;
|
|
294
|
+
align-items: start;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.openpress-workspace-gallery__grid {
|
|
298
|
+
grid-template-columns: 1fr;
|
|
299
|
+
}
|
|
300
|
+
}
|
package/src/styles/openpress.css
CHANGED
package/tsconfig.json
CHANGED
package/engine/commands/init.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { initWorkspace, listStylePackSkills } from "../init.mjs";
|
|
2
|
-
import { formatDisplayPath, parseInitOptions } from "./_shared.mjs";
|
|
3
|
-
|
|
4
|
-
export const needsWorkspace = false;
|
|
5
|
-
|
|
6
|
-
export async function run({ argv }) {
|
|
7
|
-
const options = parseInitOptions(argv);
|
|
8
|
-
if (!options.target) {
|
|
9
|
-
console.error("openpress init: target path is required");
|
|
10
|
-
console.error("Usage: openpress init <target> [--skill <name>] [--force]");
|
|
11
|
-
const available = await listStylePackSkills();
|
|
12
|
-
if (available.length) console.error(`Style packs available: ${available.join(", ")}`);
|
|
13
|
-
return 1;
|
|
14
|
-
}
|
|
15
|
-
const result = await initWorkspace(options);
|
|
16
|
-
const displayPath = formatDisplayPath(result.targetPath);
|
|
17
|
-
console.log(`OpenPress init: created ${displayPath} from style pack "${result.skill}".`);
|
|
18
|
-
console.log("Next steps:");
|
|
19
|
-
console.log(` cd ${displayPath}`);
|
|
20
|
-
console.log(" # 填入 openpress.config.mjs 的 title / subtitle / organization");
|
|
21
|
-
console.log(" # 改 document/index.tsx 與 document/chapters/**/*.mdx 為實際內容");
|
|
22
|
-
console.log(" node engine/cli.mjs validate");
|
|
23
|
-
return 0;
|
|
24
|
-
}
|
package/engine/init.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const SELF_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const ENGINE_ROOT = path.resolve(SELF_DIR, "..");
|
|
7
|
-
const SKILLS_DIR = path.join(ENGINE_ROOT, "skills");
|
|
8
|
-
|
|
9
|
-
const DEFAULT_SKILL = "editorial-monograph";
|
|
10
|
-
|
|
11
|
-
export async function initWorkspace({ target, skill = DEFAULT_SKILL, force = false }) {
|
|
12
|
-
if (!target) throw new Error("openpress init: target path is required");
|
|
13
|
-
const targetPath = path.resolve(target);
|
|
14
|
-
|
|
15
|
-
const starterPath = path.join(SKILLS_DIR, skill, "starter");
|
|
16
|
-
try {
|
|
17
|
-
const stat = await fs.stat(starterPath);
|
|
18
|
-
if (!stat.isDirectory()) {
|
|
19
|
-
throw new Error(`openpress init: skill "${skill}" has no starter/ directory at ${starterPath}`);
|
|
20
|
-
}
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (error?.code === "ENOENT") {
|
|
23
|
-
const available = await listStylePackSkills();
|
|
24
|
-
throw new Error(
|
|
25
|
-
`openpress init: skill "${skill}" not found or has no starter. ` +
|
|
26
|
-
`Available style packs: ${available.join(", ") || "(none)"}`,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!force) {
|
|
33
|
-
try {
|
|
34
|
-
const stat = await fs.stat(targetPath);
|
|
35
|
-
if (stat.isDirectory()) {
|
|
36
|
-
const entries = await fs.readdir(targetPath);
|
|
37
|
-
if (entries.length > 0) {
|
|
38
|
-
throw new Error(`openpress init: target ${targetPath} exists and is not empty. Pass --force to overwrite.`);
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
throw new Error(`openpress init: target ${targetPath} exists and is not a directory.`);
|
|
42
|
-
}
|
|
43
|
-
} catch (error) {
|
|
44
|
-
if (error?.code !== "ENOENT") throw error;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
await fs.mkdir(targetPath, { recursive: true });
|
|
49
|
-
await copyDirectory(starterPath, targetPath);
|
|
50
|
-
|
|
51
|
-
return { targetPath, skill };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function listStylePackSkills() {
|
|
55
|
-
try {
|
|
56
|
-
const entries = await fs.readdir(SKILLS_DIR, { withFileTypes: true });
|
|
57
|
-
const names = [];
|
|
58
|
-
for (const entry of entries) {
|
|
59
|
-
if (!entry.isDirectory()) continue;
|
|
60
|
-
const starter = path.join(SKILLS_DIR, entry.name, "starter");
|
|
61
|
-
try {
|
|
62
|
-
const stat = await fs.stat(starter);
|
|
63
|
-
if (stat.isDirectory()) names.push(entry.name);
|
|
64
|
-
} catch {
|
|
65
|
-
// skill without starter/ is not a style pack — skip
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return names.sort();
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (error?.code === "ENOENT") return [];
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function copyDirectory(source, destination) {
|
|
76
|
-
await fs.mkdir(destination, { recursive: true });
|
|
77
|
-
for (const entry of await fs.readdir(source, { withFileTypes: true })) {
|
|
78
|
-
if (entry.name === ".DS_Store") continue;
|
|
79
|
-
const sourcePath = path.join(source, entry.name);
|
|
80
|
-
const destPath = path.join(destination, entry.name);
|
|
81
|
-
if (entry.isDirectory()) {
|
|
82
|
-
await copyDirectory(sourcePath, destPath);
|
|
83
|
-
} else if (entry.isFile()) {
|
|
84
|
-
await fs.copyFile(sourcePath, destPath);
|
|
85
|
-
} else if (entry.isSymbolicLink()) {
|
|
86
|
-
const link = await fs.readlink(sourcePath);
|
|
87
|
-
await fs.symlink(link, destPath);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|