@office-kit/pptx-dev 0.2.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/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +296 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +46 -0
- package/dist/index.mjs +2 -0
- package/dist/src-BA02cMY9.mjs +204 -0
- package/dist/src-BA02cMY9.mjs.map +1 -0
- package/dist/worker.d.mts +1 -0
- package/dist/worker.mjs +93 -0
- package/dist/worker.mjs.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yuichiro Yamashita
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @office-kit/pptx-dev
|
|
2
|
+
|
|
3
|
+
Local TSX authoring tools for `@office-kit/pptx-dsl`. Build and preview an editable
|
|
4
|
+
PowerPoint presentation while changing its source in VSCode or Claude Code.
|
|
5
|
+
Requires Node.js 22.18 or later.
|
|
6
|
+
|
|
7
|
+
The CLI and DSL packages are **not yet published to npm**. Start with the
|
|
8
|
+
[step-by-step authoring guide](https://office-kit.github.io/pptx/docs/authoring)
|
|
9
|
+
for a source-checkout installation. In the repository root (after
|
|
10
|
+
`pnpm install --frozen-lockfile`), run:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pnpm --filter @office-kit/pptx-dev... build
|
|
14
|
+
PPTX_REPO="$PWD"
|
|
15
|
+
PPTX_PACKS="$PPTX_REPO/.probe/packages"
|
|
16
|
+
mkdir -p "$PPTX_PACKS"
|
|
17
|
+
pnpm --filter @office-kit/pptx-dev... exec pnpm pack --pack-destination "$PPTX_PACKS"
|
|
18
|
+
node "$PPTX_REPO/packages/dev/dist/cli.mjs" init ../my-slides
|
|
19
|
+
cd ../my-slides
|
|
20
|
+
npm install --legacy-peer-deps "$PPTX_PACKS"/*.tgz
|
|
21
|
+
npm run check
|
|
22
|
+
npm run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install all four tarballs from the same checkout. The temporary peer override is
|
|
26
|
+
needed because the DSL targets the next core release, whose version number has
|
|
27
|
+
not been bumped yet; the source checkout contains the required core changes.
|
|
28
|
+
Keep the tarballs for reinstalls, and use an empty pack directory after upgrading
|
|
29
|
+
so old versions are not included. The guide also covers Claude Code, VSCode,
|
|
30
|
+
templates, viewing controls, and troubleshooting.
|
|
31
|
+
|
|
32
|
+
Open the local URL printed by the server. Save `deck.tsx` to rebuild. The viewer
|
|
33
|
+
has a vertical thumbnail strip and a large slide canvas. Click a thumbnail or use
|
|
34
|
+
arrow keys, Page Up/Down, Home/End to navigate. Fit/zoom and Present (Escape to
|
|
35
|
+
exit) are viewing controls; the canvas has no editing, dragging or resize handles.
|
|
36
|
+
Changes are made only in TSX, including when an AI agent edits the presentation.
|
|
37
|
+
Download PPTX exports the last successful build.
|
|
38
|
+
A syntax or runtime error is shown without discarding the last successful preview.
|
|
39
|
+
DSL evaluation errors include the TSX element's source file and line number.
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npm run check
|
|
43
|
+
npm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`check` runs TypeScript; `build` writes `deck.pptx`. Compilation alone does not
|
|
47
|
+
perform TypeScript checking, so run both before delivery. Exported charts, text,
|
|
48
|
+
images and tables remain editable. The renderer previews the serialized PPTX,
|
|
49
|
+
but is not a guarantee of identical rendering in PowerPoint.
|
|
50
|
+
|
|
51
|
+
The generated `CLAUDE.md` explains authoring conventions. VSCode picks up the
|
|
52
|
+
included `tsconfig.json` for completion and diagnostics. Start the **Preview
|
|
53
|
+
presentation** task, then use **Simple Browser: Show** with the printed URL and
|
|
54
|
+
move it to a side editor group. No custom editor extension is needed for this
|
|
55
|
+
workflow. Preview-to-source selection is not implemented.
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
- `office-pptx init <new-directory>` creates a TSX project without overwriting an existing directory.
|
|
60
|
+
- `office-pptx dev <deck.tsx> [--port 4173]` watches the entry file's directory recursively.
|
|
61
|
+
- `office-pptx build <deck.tsx> [--out deck.pptx]` writes a PPTX.
|
|
62
|
+
- `office-pptx inspect <template.pptx>` prints slides, shape targets and layout references as JSON.
|
|
63
|
+
|
|
64
|
+
Keep imported components, source PPTX files and images inside the watched
|
|
65
|
+
directory. `node_modules`, `.git`, `dist` and `.office-kit` are excluded. Load
|
|
66
|
+
assets with `readFile(new URL('./asset.png', import.meta.url))`; the compiler
|
|
67
|
+
preserves the original URL of each bundled source module.
|
|
68
|
+
|
|
69
|
+
TSX runs as trusted local Node code, including imports and Raw callbacks.
|
|
70
|
+
A fresh worker evaluates each build, with a 60-second limit, so module state
|
|
71
|
+
cannot accumulate across updates. Workers are not a security sandbox. The
|
|
72
|
+
preview server binds to `127.0.0.1` and rejects unexpected Host headers.
|
|
73
|
+
|
|
74
|
+
Programmatic exports: `buildDeck`, `exportDeck`, `initProject`, `inspectTemplate`.
|
|
75
|
+
`buildDeck` returns PPTX bytes, SVG slides, aspect ratio, module dependencies and
|
|
76
|
+
core validation diagnostics. Browser APIs and DOM globals are not available in
|
|
77
|
+
TSX evaluation.
|
|
78
|
+
|
|
79
|
+
## Developing in this monorepo
|
|
80
|
+
|
|
81
|
+
The new packages must be built locally before running the CLI; they need not be
|
|
82
|
+
published to use this checkout.
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
pnpm --filter @office-kit/pptx-dev... build
|
|
86
|
+
node packages/dev/dist/cli.mjs dev packages/dsl/examples/review.tsx
|
|
87
|
+
node packages/dev/dist/cli.mjs build packages/dsl/examples/review.tsx --out review.pptx
|
|
88
|
+
pnpm --filter @office-kit/pptx-dev test
|
|
89
|
+
```
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as initProject, n as exportDeck, r as inspectTemplate, t as buildDeck } from "./src-BA02cMY9.mjs";
|
|
3
|
+
import { dirname, resolve, sep } from "node:path";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { watch } from "node:fs";
|
|
6
|
+
//#region src/page.ts
|
|
7
|
+
const page = `<!doctype html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
|
10
|
+
<title>Office Kit — PowerPoint preview</title>
|
|
11
|
+
<style>
|
|
12
|
+
*{box-sizing:border-box}
|
|
13
|
+
body{margin:0;height:100dvh;overflow:hidden;background:#e9ecf1;color:#202735;font:13px system-ui;display:grid;grid-template-rows:60px minmax(0,1fr) 42px}
|
|
14
|
+
button,a,select{font:inherit;color:inherit}button,select,.download{border:1px solid #d4d9e2;border-radius:6px;background:#fff;padding:7px 12px;text-decoration:none;cursor:pointer}
|
|
15
|
+
button:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{opacity:.4;cursor:default}
|
|
16
|
+
:focus-visible{outline:2px solid #4967dd;outline-offset:3px}
|
|
17
|
+
header{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}
|
|
18
|
+
.brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}
|
|
19
|
+
#status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}
|
|
20
|
+
.workspace{display:grid;grid-template-columns:224px minmax(0,1fr);min-height:0}
|
|
21
|
+
.filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}
|
|
22
|
+
.filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}
|
|
23
|
+
#thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}
|
|
24
|
+
.thumbnail{display:flex;align-items:flex-start;gap:8px;width:100%;padding:4px 3px;border:0;background:transparent;border-radius:5px;text-align:left}
|
|
25
|
+
.slide-number{width:18px;flex:none;text-align:right;font-size:11px;color:#737d8e;padding-top:5px}
|
|
26
|
+
.thumbnail img{display:block;min-width:0;width:calc(100% - 26px);background:white;box-shadow:0 1px 4px #19212d18;border:2px solid transparent;border-radius:3px;aspect-ratio:var(--slide-ratio,16/9);object-fit:contain}
|
|
27
|
+
.thumbnail[aria-current="true"]{background:#e7edff}.thumbnail[aria-current="true"] img{border-color:#4967dd}.thumbnail[aria-current="true"] .slide-number{color:#3654c1;font-weight:700}
|
|
28
|
+
main{min-width:0;min-height:0;display:flex;flex-direction:column}
|
|
29
|
+
#error{flex:none;max-height:30%;overflow:auto;background:#fff0ef;color:#922e25;margin:0;padding:16px 20px;white-space:pre-wrap;border-bottom:1px solid #e8bbb7}
|
|
30
|
+
#error[hidden]{display:none}
|
|
31
|
+
#stage{flex:1;min-height:0;overflow:auto;display:flex;padding:32px;overscroll-behavior:contain}
|
|
32
|
+
#slide{flex:none;margin:auto;background:white;box-shadow:0 3px 24px #19212d20;overflow:hidden}
|
|
33
|
+
#slide iframe{display:block;width:100%;height:100%;border:0;pointer-events:none}
|
|
34
|
+
#empty{margin:auto;color:#737d8e}
|
|
35
|
+
footer{display:flex;align-items:center;gap:14px;padding:0 16px;background:#fff;border-top:1px solid #d4d9e2;font-size:12px}
|
|
36
|
+
#count{min-width:90px}footer .hint{flex:1;color:#737d8e}footer button{padding:3px 10px}footer select{padding:3px 8px}
|
|
37
|
+
#presentation-controls{display:none}
|
|
38
|
+
body.presenting{grid-template-rows:minmax(0,1fr);background:#111}
|
|
39
|
+
.presenting header,.presenting footer,.presenting .filmstrip{display:none}
|
|
40
|
+
.presenting .workspace{grid-template-columns:minmax(0,1fr)}.presenting #stage{padding:0;background:#111}.presenting #slide{box-shadow:none}
|
|
41
|
+
.presenting #presentation-controls{display:flex;position:fixed;bottom:16px;left:50%;transform:translateX(-50%);align-items:center;gap:12px;background:#202735e8;color:white;padding:6px;border-radius:8px;opacity:0;transition:opacity .15s}
|
|
42
|
+
.presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}
|
|
43
|
+
#presentation-controls button{background:transparent;color:white;border-color:#5d6575}
|
|
44
|
+
@media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}
|
|
45
|
+
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
|
|
46
|
+
</style>
|
|
47
|
+
<header><span class="brand">Office Kit</span><span class="badge">Preview</span><span id="status" role="status">Building…</span><button id="present" disabled>Present</button><a class="download" href="/deck.pptx">Download PPTX</a></header>
|
|
48
|
+
<div class="workspace">
|
|
49
|
+
<nav class="filmstrip" aria-label="Slides"><h2>Slides</h2><ol id="thumbnails"></ol></nav>
|
|
50
|
+
<main aria-label="Slide viewer"><pre id="error" role="alert" hidden></pre><div id="stage" tabindex="-1"><div id="empty">Waiting for slides…</div><div id="slide" hidden></div></div></main>
|
|
51
|
+
</div>
|
|
52
|
+
<footer><span id="count" aria-live="polite">No slides</span><span class="hint">Changes appear automatically · View only</span><button id="prev" aria-label="Previous slide" disabled>‹</button><button id="next" aria-label="Next slide" disabled>›</button><label for="zoom">Zoom</label><select id="zoom"><option value="fit">Fit</option><option value="0.5">50%</option><option value="0.75">75%</option><option value="1">100%</option><option value="1.25">125%</option><option value="1.5">150%</option><option value="2">200%</option></select></footer>
|
|
53
|
+
<div id="presentation-controls"><button id="present-prev" aria-label="Previous slide">‹</button><span id="present-count"></span><button id="present-next" aria-label="Next slide">›</button><button id="exit-present">Exit · Esc</button></div>
|
|
54
|
+
<script>
|
|
55
|
+
let state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=false;
|
|
56
|
+
const byId=id=>document.getElementById(id);
|
|
57
|
+
const stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');
|
|
58
|
+
function resize(){
|
|
59
|
+
if(!state.slides.length)return;
|
|
60
|
+
const style=getComputedStyle(stage);
|
|
61
|
+
const width=Math.max(1,stage.clientWidth-parseFloat(style.paddingLeft)-parseFloat(style.paddingRight));
|
|
62
|
+
const height=Math.max(1,stage.clientHeight-parseFloat(style.paddingTop)-parseFloat(style.paddingBottom));
|
|
63
|
+
const ratio=state.aspectRatio;
|
|
64
|
+
const zoom=byId('zoom').value;
|
|
65
|
+
const slideWidth=presenting||zoom==='fit'?Math.min(width,height*ratio):1280*Number(zoom);
|
|
66
|
+
slide.style.width=slideWidth+'px';slide.style.height=slideWidth/ratio+'px';
|
|
67
|
+
}
|
|
68
|
+
function selectSlide(next,focusThumbnail=false){
|
|
69
|
+
index=Math.max(0,Math.min(next,state.slides.length-1));
|
|
70
|
+
const count=state.slides.length?'Slide '+(index+1)+' of '+state.slides.length:'No slides';
|
|
71
|
+
byId('count').textContent=count;byId('present-count').textContent=count;
|
|
72
|
+
for(const id of ['prev','present-prev'])byId(id).disabled=index===0;
|
|
73
|
+
for(const id of ['next','present-next'])byId(id).disabled=index>=state.slides.length-1;
|
|
74
|
+
byId('present').disabled=!state.slides.length;
|
|
75
|
+
byId('zoom').disabled=!state.slides.length;
|
|
76
|
+
slide.hidden=!state.slides.length;byId('empty').hidden=!!state.slides.length;
|
|
77
|
+
slide.replaceChildren();
|
|
78
|
+
if(state.slides[index]){
|
|
79
|
+
const frame=document.createElement('iframe');frame.sandbox='';frame.tabIndex=-1;frame.title='Slide '+(index+1);
|
|
80
|
+
frame.srcdoc='<style>html,body{margin:0;width:100%;height:100%;overflow:hidden}svg{display:block;width:100%;height:100%}</style>'+state.slides[index];
|
|
81
|
+
slide.append(frame);
|
|
82
|
+
}
|
|
83
|
+
for(const [position,item] of Array.from(thumbnails.children).entries()){
|
|
84
|
+
const button=item.firstElementChild,selected=position===index;
|
|
85
|
+
button.setAttribute('aria-current',String(selected));button.tabIndex=selected?0:-1;
|
|
86
|
+
if(selected){button.scrollIntoView({block:'nearest'});if(focusThumbnail)button.focus({preventScroll:true});}
|
|
87
|
+
}
|
|
88
|
+
resize();
|
|
89
|
+
}
|
|
90
|
+
function update(updated){
|
|
91
|
+
const focusedThumbnail=thumbnails.contains(document.activeElement);
|
|
92
|
+
const changed=updated.slides.length!==state.slides.length||updated.slides.some((svg,i)=>svg!==state.slides[i]);
|
|
93
|
+
state=updated;
|
|
94
|
+
byId('status').textContent=state.error?'Build failed · showing last successful output':state.slides.length+' slides · Live';
|
|
95
|
+
byId('error').textContent=state.error||'';byId('error').hidden=!state.error;
|
|
96
|
+
document.documentElement.style.setProperty('--slide-ratio',String(state.aspectRatio));
|
|
97
|
+
if(changed){
|
|
98
|
+
for(const url of urls)URL.revokeObjectURL(url);
|
|
99
|
+
urls=state.slides.map(svg=>URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'})));
|
|
100
|
+
thumbnails.replaceChildren(...urls.map((url,i)=>{
|
|
101
|
+
const item=document.createElement('li'),button=document.createElement('button'),number=document.createElement('span'),image=document.createElement('img');
|
|
102
|
+
button.className='thumbnail';button.setAttribute('aria-label','Slide '+(i+1));button.onclick=()=>selectSlide(i,true);
|
|
103
|
+
number.className='slide-number';number.textContent=String(i+1);
|
|
104
|
+
image.src=url;image.alt='';image.draggable=false;image.loading='lazy';
|
|
105
|
+
button.append(number,image);item.append(button);return item;
|
|
106
|
+
}));
|
|
107
|
+
selectSlide(index,focusedThumbnail);
|
|
108
|
+
}else resize();
|
|
109
|
+
if(!state.slides.length&&presenting)void exitPresentation();
|
|
110
|
+
}
|
|
111
|
+
function setPresenting(value){
|
|
112
|
+
presenting=value;document.body.classList.toggle('presenting',value);resize();
|
|
113
|
+
if(value)stage.focus();else{
|
|
114
|
+
byId('present').focus();
|
|
115
|
+
thumbnails.children[index]?.firstElementChild.scrollIntoView({block:'nearest'});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function exitPresentation(){
|
|
119
|
+
setPresenting(false);
|
|
120
|
+
if(document.fullscreenElement)await document.exitFullscreen();
|
|
121
|
+
}
|
|
122
|
+
byId('present').onclick=async()=>{
|
|
123
|
+
setPresenting(true);
|
|
124
|
+
try{await document.documentElement.requestFullscreen();}
|
|
125
|
+
catch{byId('exit-present').textContent='Exit view · Esc';}
|
|
126
|
+
};
|
|
127
|
+
byId('exit-present').onclick=exitPresentation;
|
|
128
|
+
document.addEventListener('fullscreenchange',()=>{if(!document.fullscreenElement&&presenting)setPresenting(false);});
|
|
129
|
+
for(const id of ['prev','present-prev'])byId(id).onclick=()=>selectSlide(index-1);
|
|
130
|
+
for(const id of ['next','present-next'])byId(id).onclick=()=>selectSlide(index+1);
|
|
131
|
+
byId('zoom').onchange=resize;
|
|
132
|
+
stage.onclick=()=>{if(presenting)selectSlide(index+1);};
|
|
133
|
+
document.addEventListener('keydown',event=>{
|
|
134
|
+
if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}
|
|
135
|
+
if(event.altKey||event.ctrlKey||event.metaKey||event.target.closest('select,input,textarea,[contenteditable]'))return;
|
|
136
|
+
let next=index;
|
|
137
|
+
if(['ArrowLeft','ArrowUp','PageUp'].includes(event.key))next--;
|
|
138
|
+
else if(['ArrowRight','ArrowDown','PageDown'].includes(event.key))next++;
|
|
139
|
+
else if(event.key==='Home')next=0;
|
|
140
|
+
else if(event.key==='End')next=state.slides.length-1;
|
|
141
|
+
else if(event.key===' '&&presenting&&!event.target.closest('button,a'))next+=event.shiftKey?-1:1;
|
|
142
|
+
else return;
|
|
143
|
+
event.preventDefault();selectSlide(next,thumbnails.contains(document.activeElement));
|
|
144
|
+
});
|
|
145
|
+
new ResizeObserver(resize).observe(stage);
|
|
146
|
+
let refreshId=0;
|
|
147
|
+
async function refresh(){
|
|
148
|
+
const id=++refreshId;
|
|
149
|
+
try{const response=await fetch('/state');if(!response.ok)throw new Error('Preview unavailable');const updated=await response.json();if(id===refreshId)update(updated);}
|
|
150
|
+
catch{if(id===refreshId)byId('status').textContent='Reconnecting…';}
|
|
151
|
+
}
|
|
152
|
+
const events=new EventSource('/events');events.onmessage=refresh;events.onerror=()=>{byId('status').textContent='Reconnecting…'};refresh();
|
|
153
|
+
<\/script></html>`;
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/server.ts
|
|
156
|
+
async function serveDeck(entry, port = 4173) {
|
|
157
|
+
let latest;
|
|
158
|
+
let error = null;
|
|
159
|
+
let building = false;
|
|
160
|
+
let pending = false;
|
|
161
|
+
let closed = false;
|
|
162
|
+
const clients = /* @__PURE__ */ new Set();
|
|
163
|
+
const server = createServer((request, response) => {
|
|
164
|
+
const host = request.headers.host;
|
|
165
|
+
if (host !== `127.0.0.1:${actualPort}` && host !== `localhost:${actualPort}`) {
|
|
166
|
+
response.writeHead(403).end();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
response.setHeader("Cache-Control", "no-store");
|
|
170
|
+
if (request.url === "/events") {
|
|
171
|
+
response.writeHead(200, { "Content-Type": "text/event-stream" });
|
|
172
|
+
response.write("data: ready\n\n");
|
|
173
|
+
clients.add(response);
|
|
174
|
+
request.on("close", () => clients.delete(response));
|
|
175
|
+
} else if (request.url === "/state") {
|
|
176
|
+
response.writeHead(200, { "Content-Type": "application/json" });
|
|
177
|
+
response.end(JSON.stringify({
|
|
178
|
+
slides: latest?.slides ?? [],
|
|
179
|
+
aspectRatio: latest?.aspectRatio ?? 16 / 9,
|
|
180
|
+
error,
|
|
181
|
+
diagnostics: latest?.diagnostics ?? []
|
|
182
|
+
}));
|
|
183
|
+
} else if (request.url === "/deck.pptx" && latest) {
|
|
184
|
+
response.writeHead(200, {
|
|
185
|
+
"Content-Type": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
186
|
+
"Content-Disposition": "attachment; filename=\"deck.pptx\""
|
|
187
|
+
});
|
|
188
|
+
response.end(latest.bytes);
|
|
189
|
+
} else if (request.url === "/") {
|
|
190
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
191
|
+
response.end(page);
|
|
192
|
+
} else response.writeHead(404).end();
|
|
193
|
+
});
|
|
194
|
+
async function rebuild() {
|
|
195
|
+
if (closed) return;
|
|
196
|
+
if (building) {
|
|
197
|
+
pending = true;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
building = true;
|
|
201
|
+
try {
|
|
202
|
+
latest = await buildDeck(entry);
|
|
203
|
+
error = null;
|
|
204
|
+
} catch (cause) {
|
|
205
|
+
error = cause instanceof Error ? cause.stack ?? cause.message : String(cause);
|
|
206
|
+
} finally {
|
|
207
|
+
building = false;
|
|
208
|
+
for (const client of clients) client.write("data: updated\n\n");
|
|
209
|
+
if (pending) {
|
|
210
|
+
pending = false;
|
|
211
|
+
rebuild();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let timer;
|
|
216
|
+
const watcher = watch(dirname(resolve(entry)), { recursive: true }, (_, filename) => {
|
|
217
|
+
if (!filename || filename.split(sep).some((part) => [
|
|
218
|
+
"node_modules",
|
|
219
|
+
".git",
|
|
220
|
+
"dist",
|
|
221
|
+
".office-kit"
|
|
222
|
+
].includes(part))) return;
|
|
223
|
+
if (!/\.([cm]?[jt]sx?|json|pptx|png|jpe?g|gif|bmp|tiff?|emf|wmf|svg)$/i.test(filename)) return;
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
timer = setTimeout(() => {
|
|
226
|
+
rebuild();
|
|
227
|
+
}, 100);
|
|
228
|
+
});
|
|
229
|
+
let actualPort = port;
|
|
230
|
+
try {
|
|
231
|
+
await new Promise((resolveListen, reject) => {
|
|
232
|
+
server.once("error", reject);
|
|
233
|
+
server.listen(port, "127.0.0.1", () => {
|
|
234
|
+
server.off("error", reject);
|
|
235
|
+
resolveListen();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
} catch (cause) {
|
|
239
|
+
watcher.close();
|
|
240
|
+
throw cause;
|
|
241
|
+
}
|
|
242
|
+
const address = server.address();
|
|
243
|
+
if (address && typeof address !== "string") actualPort = address.port;
|
|
244
|
+
await rebuild();
|
|
245
|
+
return {
|
|
246
|
+
url: `http://127.0.0.1:${actualPort}`,
|
|
247
|
+
async close() {
|
|
248
|
+
closed = true;
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
watcher.close();
|
|
251
|
+
for (const client of clients) client.end();
|
|
252
|
+
await new Promise((done, reject) => server.close((cause) => cause ? reject(cause) : done()));
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/cli.ts
|
|
258
|
+
const usage = `Usage: office-pptx init <new-directory>
|
|
259
|
+
office-pptx dev <deck.tsx> [--port 4173]
|
|
260
|
+
office-pptx build <deck.tsx> [--out deck.pptx]
|
|
261
|
+
office-pptx inspect <template.pptx>`;
|
|
262
|
+
const [command, entry, ...args] = process.argv.slice(2);
|
|
263
|
+
try {
|
|
264
|
+
if (command === "--help" || command === "-h") console.log(usage);
|
|
265
|
+
else {
|
|
266
|
+
if (!entry) throw new Error(usage);
|
|
267
|
+
const option = command === "build" ? "--out" : command === "dev" ? "--port" : void 0;
|
|
268
|
+
if (args.length && (args.length !== 2 || !option || args[0] !== option || !args[1])) throw new Error(`Unexpected arguments: ${args.join(" ")}\n${usage}`);
|
|
269
|
+
if (command === "init") {
|
|
270
|
+
const directory = await initProject(entry);
|
|
271
|
+
console.log(`Created ${directory}\nRun npm install, then npm run dev inside that directory.`);
|
|
272
|
+
} else if (command === "inspect") console.log(JSON.stringify(await inspectTemplate(entry), null, 2));
|
|
273
|
+
else if (command === "build") {
|
|
274
|
+
const output = resolve(args[1] ?? "deck.pptx");
|
|
275
|
+
const result = await exportDeck(entry, output);
|
|
276
|
+
console.log(`Wrote ${output} (${result.slides.length} slides)`);
|
|
277
|
+
} else if (command === "dev") {
|
|
278
|
+
const port = args[1] === void 0 ? 4173 : Number(args[1]);
|
|
279
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error("Port must be an integer between 0 and 65535.");
|
|
280
|
+
const server = await serveDeck(entry, port);
|
|
281
|
+
console.log(`Preview: ${server.url}`);
|
|
282
|
+
const stop = () => {
|
|
283
|
+
server.close().then(() => process.exit(0));
|
|
284
|
+
};
|
|
285
|
+
process.once("SIGINT", stop);
|
|
286
|
+
process.once("SIGTERM", stop);
|
|
287
|
+
} else throw new Error(usage);
|
|
288
|
+
}
|
|
289
|
+
} catch (cause) {
|
|
290
|
+
console.error(cause instanceof Error ? cause.message : cause);
|
|
291
|
+
process.exitCode = 1;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
export {};
|
|
295
|
+
|
|
296
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/page.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["export const page = `<!doctype html>\n<html lang=\"en\">\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\">\n<title>Office Kit — PowerPoint preview</title>\n<style>\n*{box-sizing:border-box}\nbody{margin:0;height:100dvh;overflow:hidden;background:#e9ecf1;color:#202735;font:13px system-ui;display:grid;grid-template-rows:60px minmax(0,1fr) 42px}\nbutton,a,select{font:inherit;color:inherit}button,select,.download{border:1px solid #d4d9e2;border-radius:6px;background:#fff;padding:7px 12px;text-decoration:none;cursor:pointer}\nbutton:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{opacity:.4;cursor:default}\n:focus-visible{outline:2px solid #4967dd;outline-offset:3px}\nheader{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}\n.brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}\n#status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}\n.workspace{display:grid;grid-template-columns:224px minmax(0,1fr);min-height:0}\n.filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}\n.filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}\n#thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}\n.thumbnail{display:flex;align-items:flex-start;gap:8px;width:100%;padding:4px 3px;border:0;background:transparent;border-radius:5px;text-align:left}\n.slide-number{width:18px;flex:none;text-align:right;font-size:11px;color:#737d8e;padding-top:5px}\n.thumbnail img{display:block;min-width:0;width:calc(100% - 26px);background:white;box-shadow:0 1px 4px #19212d18;border:2px solid transparent;border-radius:3px;aspect-ratio:var(--slide-ratio,16/9);object-fit:contain}\n.thumbnail[aria-current=\"true\"]{background:#e7edff}.thumbnail[aria-current=\"true\"] img{border-color:#4967dd}.thumbnail[aria-current=\"true\"] .slide-number{color:#3654c1;font-weight:700}\nmain{min-width:0;min-height:0;display:flex;flex-direction:column}\n#error{flex:none;max-height:30%;overflow:auto;background:#fff0ef;color:#922e25;margin:0;padding:16px 20px;white-space:pre-wrap;border-bottom:1px solid #e8bbb7}\n#error[hidden]{display:none}\n#stage{flex:1;min-height:0;overflow:auto;display:flex;padding:32px;overscroll-behavior:contain}\n#slide{flex:none;margin:auto;background:white;box-shadow:0 3px 24px #19212d20;overflow:hidden}\n#slide iframe{display:block;width:100%;height:100%;border:0;pointer-events:none}\n#empty{margin:auto;color:#737d8e}\nfooter{display:flex;align-items:center;gap:14px;padding:0 16px;background:#fff;border-top:1px solid #d4d9e2;font-size:12px}\n#count{min-width:90px}footer .hint{flex:1;color:#737d8e}footer button{padding:3px 10px}footer select{padding:3px 8px}\n#presentation-controls{display:none}\nbody.presenting{grid-template-rows:minmax(0,1fr);background:#111}\n.presenting header,.presenting footer,.presenting .filmstrip{display:none}\n.presenting .workspace{grid-template-columns:minmax(0,1fr)}.presenting #stage{padding:0;background:#111}.presenting #slide{box-shadow:none}\n.presenting #presentation-controls{display:flex;position:fixed;bottom:16px;left:50%;transform:translateX(-50%);align-items:center;gap:12px;background:#202735e8;color:white;padding:6px;border-radius:8px;opacity:0;transition:opacity .15s}\n.presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}\n#presentation-controls button{background:transparent;color:white;border-color:#5d6575}\n@media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}\n@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n</style>\n<header><span class=\"brand\">Office Kit</span><span class=\"badge\">Preview</span><span id=\"status\" role=\"status\">Building…</span><button id=\"present\" disabled>Present</button><a class=\"download\" href=\"/deck.pptx\">Download PPTX</a></header>\n<div class=\"workspace\">\n<nav class=\"filmstrip\" aria-label=\"Slides\"><h2>Slides</h2><ol id=\"thumbnails\"></ol></nav>\n<main aria-label=\"Slide viewer\"><pre id=\"error\" role=\"alert\" hidden></pre><div id=\"stage\" tabindex=\"-1\"><div id=\"empty\">Waiting for slides…</div><div id=\"slide\" hidden></div></div></main>\n</div>\n<footer><span id=\"count\" aria-live=\"polite\">No slides</span><span class=\"hint\">Changes appear automatically · View only</span><button id=\"prev\" aria-label=\"Previous slide\" disabled>‹</button><button id=\"next\" aria-label=\"Next slide\" disabled>›</button><label for=\"zoom\">Zoom</label><select id=\"zoom\"><option value=\"fit\">Fit</option><option value=\"0.5\">50%</option><option value=\"0.75\">75%</option><option value=\"1\">100%</option><option value=\"1.25\">125%</option><option value=\"1.5\">150%</option><option value=\"2\">200%</option></select></footer>\n<div id=\"presentation-controls\"><button id=\"present-prev\" aria-label=\"Previous slide\">‹</button><span id=\"present-count\"></span><button id=\"present-next\" aria-label=\"Next slide\">›</button><button id=\"exit-present\">Exit · Esc</button></div>\n<script>\nlet state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=false;\nconst byId=id=>document.getElementById(id);\nconst stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');\nfunction resize(){\n if(!state.slides.length)return;\n const style=getComputedStyle(stage);\n const width=Math.max(1,stage.clientWidth-parseFloat(style.paddingLeft)-parseFloat(style.paddingRight));\n const height=Math.max(1,stage.clientHeight-parseFloat(style.paddingTop)-parseFloat(style.paddingBottom));\n const ratio=state.aspectRatio;\n const zoom=byId('zoom').value;\n const slideWidth=presenting||zoom==='fit'?Math.min(width,height*ratio):1280*Number(zoom);\n slide.style.width=slideWidth+'px';slide.style.height=slideWidth/ratio+'px';\n}\nfunction selectSlide(next,focusThumbnail=false){\n index=Math.max(0,Math.min(next,state.slides.length-1));\n const count=state.slides.length?'Slide '+(index+1)+' of '+state.slides.length:'No slides';\n byId('count').textContent=count;byId('present-count').textContent=count;\n for(const id of ['prev','present-prev'])byId(id).disabled=index===0;\n for(const id of ['next','present-next'])byId(id).disabled=index>=state.slides.length-1;\n byId('present').disabled=!state.slides.length;\n byId('zoom').disabled=!state.slides.length;\n slide.hidden=!state.slides.length;byId('empty').hidden=!!state.slides.length;\n slide.replaceChildren();\n if(state.slides[index]){\n const frame=document.createElement('iframe');frame.sandbox='';frame.tabIndex=-1;frame.title='Slide '+(index+1);\n frame.srcdoc='<style>html,body{margin:0;width:100%;height:100%;overflow:hidden}svg{display:block;width:100%;height:100%}</style>'+state.slides[index];\n slide.append(frame);\n }\n for(const [position,item] of Array.from(thumbnails.children).entries()){\n const button=item.firstElementChild,selected=position===index;\n button.setAttribute('aria-current',String(selected));button.tabIndex=selected?0:-1;\n if(selected){button.scrollIntoView({block:'nearest'});if(focusThumbnail)button.focus({preventScroll:true});}\n }\n resize();\n}\nfunction update(updated){\n const focusedThumbnail=thumbnails.contains(document.activeElement);\n const changed=updated.slides.length!==state.slides.length||updated.slides.some((svg,i)=>svg!==state.slides[i]);\n state=updated;\n byId('status').textContent=state.error?'Build failed · showing last successful output':state.slides.length+' slides · Live';\n byId('error').textContent=state.error||'';byId('error').hidden=!state.error;\n document.documentElement.style.setProperty('--slide-ratio',String(state.aspectRatio));\n if(changed){\n for(const url of urls)URL.revokeObjectURL(url);\n urls=state.slides.map(svg=>URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'})));\n thumbnails.replaceChildren(...urls.map((url,i)=>{\n const item=document.createElement('li'),button=document.createElement('button'),number=document.createElement('span'),image=document.createElement('img');\n button.className='thumbnail';button.setAttribute('aria-label','Slide '+(i+1));button.onclick=()=>selectSlide(i,true);\n number.className='slide-number';number.textContent=String(i+1);\n image.src=url;image.alt='';image.draggable=false;image.loading='lazy';\n button.append(number,image);item.append(button);return item;\n }));\n selectSlide(index,focusedThumbnail);\n }else resize();\n if(!state.slides.length&&presenting)void exitPresentation();\n}\nfunction setPresenting(value){\n presenting=value;document.body.classList.toggle('presenting',value);resize();\n if(value)stage.focus();else{\n byId('present').focus();\n thumbnails.children[index]?.firstElementChild.scrollIntoView({block:'nearest'});\n }\n}\nasync function exitPresentation(){\n setPresenting(false);\n if(document.fullscreenElement)await document.exitFullscreen();\n}\nbyId('present').onclick=async()=>{\n setPresenting(true);\n try{await document.documentElement.requestFullscreen();}\n catch{byId('exit-present').textContent='Exit view · Esc';}\n};\nbyId('exit-present').onclick=exitPresentation;\ndocument.addEventListener('fullscreenchange',()=>{if(!document.fullscreenElement&&presenting)setPresenting(false);});\nfor(const id of ['prev','present-prev'])byId(id).onclick=()=>selectSlide(index-1);\nfor(const id of ['next','present-next'])byId(id).onclick=()=>selectSlide(index+1);\nbyId('zoom').onchange=resize;\nstage.onclick=()=>{if(presenting)selectSlide(index+1);};\ndocument.addEventListener('keydown',event=>{\n if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}\n if(event.altKey||event.ctrlKey||event.metaKey||event.target.closest('select,input,textarea,[contenteditable]'))return;\n let next=index;\n if(['ArrowLeft','ArrowUp','PageUp'].includes(event.key))next--;\n else if(['ArrowRight','ArrowDown','PageDown'].includes(event.key))next++;\n else if(event.key==='Home')next=0;\n else if(event.key==='End')next=state.slides.length-1;\n else if(event.key===' '&&presenting&&!event.target.closest('button,a'))next+=event.shiftKey?-1:1;\n else return;\n event.preventDefault();selectSlide(next,thumbnails.contains(document.activeElement));\n});\nnew ResizeObserver(resize).observe(stage);\nlet refreshId=0;\nasync function refresh(){\n const id=++refreshId;\n try{const response=await fetch('/state');if(!response.ok)throw new Error('Preview unavailable');const updated=await response.json();if(id===refreshId)update(updated);}\n catch{if(id===refreshId)byId('status').textContent='Reconnecting…';}\n}\nconst events=new EventSource('/events');events.onmessage=refresh;events.onerror=()=>{byId('status').textContent='Reconnecting…'};refresh();\n</script></html>`;\n","import { createServer, type ServerResponse } from 'node:http';\nimport { watch } from 'node:fs';\nimport { dirname, resolve, sep } from 'node:path';\nimport { buildDeck, type BuildResult } from './index.ts';\nimport { page } from './page.ts';\n\nexport async function serveDeck(entry: string, port = 4173) {\n let latest: BuildResult | undefined;\n let error: string | null = null;\n let building = false;\n let pending = false;\n let closed = false;\n const clients = new Set<ServerResponse>();\n const server = createServer((request, response) => {\n const host = request.headers.host;\n if (host !== `127.0.0.1:${actualPort}` && host !== `localhost:${actualPort}`) {\n response.writeHead(403).end();\n return;\n }\n response.setHeader('Cache-Control', 'no-store');\n if (request.url === '/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write('data: ready\\n\\n');\n clients.add(response);\n request.on('close', () => clients.delete(response));\n } else if (request.url === '/state') {\n response.writeHead(200, { 'Content-Type': 'application/json' });\n response.end(\n JSON.stringify({\n slides: latest?.slides ?? [],\n aspectRatio: latest?.aspectRatio ?? 16 / 9,\n error,\n diagnostics: latest?.diagnostics ?? [],\n }),\n );\n } else if (request.url === '/deck.pptx' && latest) {\n response.writeHead(200, {\n 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'Content-Disposition': 'attachment; filename=\"deck.pptx\"',\n });\n response.end(latest.bytes);\n } else if (request.url === '/') {\n response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n response.end(page);\n } else {\n response.writeHead(404).end();\n }\n });\n async function rebuild() {\n if (closed) return;\n if (building) {\n pending = true;\n return;\n }\n building = true;\n try {\n latest = await buildDeck(entry);\n error = null;\n } catch (cause) {\n error = cause instanceof Error ? (cause.stack ?? cause.message) : String(cause);\n } finally {\n building = false;\n for (const client of clients) client.write('data: updated\\n\\n');\n if (pending) {\n pending = false;\n void rebuild();\n }\n }\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const root = dirname(resolve(entry));\n const watcher = watch(root, { recursive: true }, (_, filename) => {\n if (\n !filename ||\n filename\n .split(sep)\n .some((part) => ['node_modules', '.git', 'dist', '.office-kit'].includes(part))\n )\n return;\n if (!/\\.([cm]?[jt]sx?|json|pptx|png|jpe?g|gif|bmp|tiff?|emf|wmf|svg)$/i.test(filename)) return;\n clearTimeout(timer);\n timer = setTimeout(() => {\n void rebuild();\n }, 100);\n });\n let actualPort = port;\n try {\n await new Promise<void>((resolveListen, reject) => {\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n server.off('error', reject);\n resolveListen();\n });\n });\n } catch (cause) {\n watcher.close();\n throw cause;\n }\n const address = server.address();\n if (address && typeof address !== 'string') actualPort = address.port;\n await rebuild();\n return {\n url: `http://127.0.0.1:${actualPort}`,\n async close() {\n closed = true;\n clearTimeout(timer);\n watcher.close();\n for (const client of clients) client.end();\n await new Promise<void>((done, reject) =>\n server.close((cause) => (cause ? reject(cause) : done())),\n );\n },\n };\n}\n","#!/usr/bin/env node\nimport { resolve } from 'node:path';\nimport { exportDeck } from './index.ts';\nimport { serveDeck } from './server.ts';\nimport { initProject } from './init.ts';\nimport { inspectTemplate } from './inspect.ts';\n\nconst usage = `Usage: office-pptx init <new-directory>\n office-pptx dev <deck.tsx> [--port 4173]\n office-pptx build <deck.tsx> [--out deck.pptx]\n office-pptx inspect <template.pptx>`;\nconst [command, entry, ...args] = process.argv.slice(2);\ntry {\n if (command === '--help' || command === '-h') {\n console.log(usage);\n } else {\n if (!entry) throw new Error(usage);\n const option = command === 'build' ? '--out' : command === 'dev' ? '--port' : undefined;\n if (args.length && (args.length !== 2 || !option || args[0] !== option || !args[1])) {\n throw new Error(`Unexpected arguments: ${args.join(' ')}\\n${usage}`);\n }\n if (command === 'init') {\n const directory = await initProject(entry);\n console.log(`Created ${directory}\\nRun npm install, then npm run dev inside that directory.`);\n } else if (command === 'inspect') {\n console.log(JSON.stringify(await inspectTemplate(entry), null, 2));\n } else if (command === 'build') {\n const output = resolve(args[1] ?? 'deck.pptx');\n const result = await exportDeck(entry, output);\n console.log(`Wrote ${output} (${result.slides.length} slides)`);\n } else if (command === 'dev') {\n const port = args[1] === undefined ? 4173 : Number(args[1]);\n if (!Number.isInteger(port) || port < 0 || port > 65535)\n throw new Error('Port must be an integer between 0 and 65535.');\n const server = await serveDeck(entry, port);\n console.log(`Preview: ${server.url}`);\n const stop = () => {\n void server.close().then(() => process.exit(0));\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } else {\n throw new Error(usage);\n }\n }\n} catch (cause) {\n console.error(cause instanceof Error ? cause.message : cause);\n process.exitCode = 1;\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { validatePresentation } from "@office-kit/pptx";
|
|
2
|
+
|
|
3
|
+
//#region src/build.d.ts
|
|
4
|
+
interface BuildResult {
|
|
5
|
+
bytes: Uint8Array;
|
|
6
|
+
slides: string[];
|
|
7
|
+
aspectRatio: number;
|
|
8
|
+
dependencies: string[];
|
|
9
|
+
diagnostics: ReturnType<typeof validatePresentation>;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/init.d.ts
|
|
13
|
+
/** Creates a new directory; existing projects are never overwritten. */
|
|
14
|
+
declare function initProject(directory: string): Promise<string>;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/inspect.d.ts
|
|
17
|
+
/** Source references usable directly in Slide, Fill and Remove props. */
|
|
18
|
+
declare function inspectTemplate(file: string): Promise<{
|
|
19
|
+
slides: {
|
|
20
|
+
index: number;
|
|
21
|
+
part: string;
|
|
22
|
+
title: string | null;
|
|
23
|
+
shapes: {
|
|
24
|
+
id: number;
|
|
25
|
+
name: string;
|
|
26
|
+
kind: "shape" | "picture" | "group" | "graphicFrame" | "connector";
|
|
27
|
+
placeholder: {
|
|
28
|
+
type: string | null;
|
|
29
|
+
idx: number | null;
|
|
30
|
+
};
|
|
31
|
+
}[];
|
|
32
|
+
}[];
|
|
33
|
+
layouts: {
|
|
34
|
+
part: string;
|
|
35
|
+
name: string;
|
|
36
|
+
type: string | null;
|
|
37
|
+
}[];
|
|
38
|
+
}>;
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/index.d.ts
|
|
41
|
+
/** Each evaluation gets a fresh module cache and releases it when finished. */
|
|
42
|
+
declare function buildDeck(entry: string): Promise<BuildResult>;
|
|
43
|
+
declare function exportDeck(entry: string, output: string): Promise<BuildResult>;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { type BuildResult, buildDeck, exportDeck, initProject, inspectTemplate };
|
|
46
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { getShapeId, getShapeKind, getShapeName, getShapePlaceholderIdx, getShapePlaceholderType, getSlideLayoutName, getSlideLayoutPartName, getSlideLayoutType, getSlideLayouts, getSlidePartName, getSlideShapes, getSlideTitle, getSlides, loadPresentation } from "@office-kit/pptx";
|
|
5
|
+
//#region src/init.ts
|
|
6
|
+
const starter = `import { Presentation, Slide, Text, Shape } from '@office-kit/pptx-dsl';
|
|
7
|
+
|
|
8
|
+
export default (
|
|
9
|
+
<Presentation>
|
|
10
|
+
<Slide background="#15171C">
|
|
11
|
+
<Shape preset="rect" x={0.9} y={2.55} width={0.14} height={1.75} fill="#E5481F" stroke={false} />
|
|
12
|
+
<Text x={1.25} y={2.4} width={10} height={1.1} size={48} bold color="#FFFFFF">
|
|
13
|
+
Your next presentation
|
|
14
|
+
</Text>
|
|
15
|
+
<Text x={1.25} y={3.55} width={10} height={0.6} size={22} color="#B4B9C4">
|
|
16
|
+
Edit this TSX and save to update the preview.
|
|
17
|
+
</Text>
|
|
18
|
+
</Slide>
|
|
19
|
+
</Presentation>
|
|
20
|
+
);
|
|
21
|
+
`;
|
|
22
|
+
const guide = `# Writing this presentation
|
|
23
|
+
|
|
24
|
+
Edit deck.tsx and local TypeScript components. The JSX runtime is
|
|
25
|
+
@office-kit/pptx-dsl, with no React or Vue dependency.
|
|
26
|
+
|
|
27
|
+
- Run npm run dev, then open its local URL to preview. Saving source files
|
|
28
|
+
updates the preview. A failed build retains the last successful output.
|
|
29
|
+
- The preview is view-only: use the vertical thumbnails to select slides, zoom
|
|
30
|
+
to inspect details, and Present to view full-screen (Escape exits). Make all
|
|
31
|
+
content and layout changes in TSX; there are no canvas editing controls.
|
|
32
|
+
- Run npm run check for TypeScript diagnostics; run npm run build to export
|
|
33
|
+
deck.pptx. Inspect every slide in the preview after changes.
|
|
34
|
+
- Coordinates and dimensions are inches; font sizes and stroke widths are points.
|
|
35
|
+
The default 16:9 canvas is 13.333 × 7.5 inches.
|
|
36
|
+
- Use native Text, Shape, Image, Table and Chart elements. Use JS functions,
|
|
37
|
+
arrays and map to compose elements; do not import React.
|
|
38
|
+
- Chart takes the core ChartSpec in its spec prop. Text accepts rich ParagraphSpec
|
|
39
|
+
arrays through paragraphs. Raw is an escape hatch for public core APIs.
|
|
40
|
+
- For templates: readFile(new URL('./template.pptx', import.meta.url)), then use
|
|
41
|
+
Presentation source={bytes} mode="edit" and Slide target={{index: 0}}.
|
|
42
|
+
Fill target={{name: 'Title 1'}} changes an existing shape. Indices are zero-based.
|
|
43
|
+
Use office-pptx inspect template.pptx to discover names, layouts and placeholders.
|
|
44
|
+
- Use mode="compose" and Slide from={{index: 0}} to build a new sequence from a
|
|
45
|
+
source deck. This intentionally removes the original slide sequence.
|
|
46
|
+
- Keep assets inside this project so the dev server sees changes. Use source-relative
|
|
47
|
+
new URL('./image.png', import.meta.url) when loading local assets.
|
|
48
|
+
- Preserve existing slides and formatting unless the request calls for changes.
|
|
49
|
+
Never rasterize a chart/table as a shortcut. Raw-only features are not DSL coverage.
|
|
50
|
+
- Preview is a rendering aid, not a guarantee of identical PowerPoint rendering.
|
|
51
|
+
Check the exported file in the target application for final delivery.
|
|
52
|
+
|
|
53
|
+
VSCode uses tsconfig.json for completion and diagnostics. Start the
|
|
54
|
+
"Preview presentation" task, then run "Simple Browser: Show" with the printed URL
|
|
55
|
+
and move that editor to a side group. Type errors appear in the Problems panel.
|
|
56
|
+
`;
|
|
57
|
+
/** Creates a new directory; existing projects are never overwritten. */
|
|
58
|
+
async function initProject(directory) {
|
|
59
|
+
const root = resolve(directory);
|
|
60
|
+
const packageLocations = [
|
|
61
|
+
import.meta.resolve("@office-kit/pptx/package.json"),
|
|
62
|
+
import.meta.resolve("@office-kit/pptx-dsl/package.json"),
|
|
63
|
+
new URL("../package.json", import.meta.url)
|
|
64
|
+
];
|
|
65
|
+
const versions = await Promise.all(packageLocations.map(async (location) => {
|
|
66
|
+
return `^${JSON.parse(await readFile(new URL(location), "utf8")).version}`;
|
|
67
|
+
}));
|
|
68
|
+
await mkdir(root);
|
|
69
|
+
const files = {
|
|
70
|
+
"package.json": JSON.stringify({
|
|
71
|
+
name: "my-presentation",
|
|
72
|
+
private: true,
|
|
73
|
+
type: "module",
|
|
74
|
+
scripts: {
|
|
75
|
+
dev: "office-pptx dev deck.tsx",
|
|
76
|
+
build: "office-pptx build deck.tsx --out deck.pptx",
|
|
77
|
+
check: "tsc --noEmit"
|
|
78
|
+
},
|
|
79
|
+
dependencies: {
|
|
80
|
+
"@office-kit/pptx": versions[0],
|
|
81
|
+
"@office-kit/pptx-dsl": versions[1]
|
|
82
|
+
},
|
|
83
|
+
devDependencies: {
|
|
84
|
+
"@office-kit/pptx-dev": versions[2],
|
|
85
|
+
"@types/node": "^24.13.2",
|
|
86
|
+
typescript: "^6.0.3"
|
|
87
|
+
}
|
|
88
|
+
}, null, 2) + "\n",
|
|
89
|
+
"tsconfig.json": JSON.stringify({
|
|
90
|
+
compilerOptions: {
|
|
91
|
+
target: "ES2022",
|
|
92
|
+
module: "ESNext",
|
|
93
|
+
moduleResolution: "Bundler",
|
|
94
|
+
jsx: "react-jsx",
|
|
95
|
+
jsxImportSource: "@office-kit/pptx-dsl",
|
|
96
|
+
strict: true,
|
|
97
|
+
exactOptionalPropertyTypes: true,
|
|
98
|
+
noEmit: true,
|
|
99
|
+
allowImportingTsExtensions: true,
|
|
100
|
+
types: ["node"]
|
|
101
|
+
},
|
|
102
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
103
|
+
exclude: ["node_modules"]
|
|
104
|
+
}, null, 2) + "\n",
|
|
105
|
+
"deck.tsx": starter,
|
|
106
|
+
"CLAUDE.md": guide,
|
|
107
|
+
".gitignore": "node_modules/\ndeck.pptx\n",
|
|
108
|
+
".vscode/tasks.json": JSON.stringify({
|
|
109
|
+
version: "2.0.0",
|
|
110
|
+
tasks: [
|
|
111
|
+
{
|
|
112
|
+
label: "Preview presentation",
|
|
113
|
+
type: "shell",
|
|
114
|
+
command: "npm run dev",
|
|
115
|
+
isBackground: true,
|
|
116
|
+
problemMatcher: {
|
|
117
|
+
pattern: {
|
|
118
|
+
regexp: "^NEVER_MATCH$",
|
|
119
|
+
file: 1,
|
|
120
|
+
message: 2
|
|
121
|
+
},
|
|
122
|
+
background: {
|
|
123
|
+
activeOnStart: true,
|
|
124
|
+
beginsPattern: "^.*office-pptx dev",
|
|
125
|
+
endsPattern: "^Preview: "
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label: "Check presentation",
|
|
131
|
+
type: "shell",
|
|
132
|
+
command: "npm run check",
|
|
133
|
+
problemMatcher: "$tsc"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: "Export presentation",
|
|
137
|
+
type: "shell",
|
|
138
|
+
command: "npm run build",
|
|
139
|
+
problemMatcher: []
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}, null, 2) + "\n"
|
|
143
|
+
};
|
|
144
|
+
await mkdir(join(root, ".vscode"));
|
|
145
|
+
await Promise.all(Object.entries(files).map(([name, content]) => writeFile(join(root, name), content, { flag: "wx" })));
|
|
146
|
+
return root;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/inspect.ts
|
|
150
|
+
/** Source references usable directly in Slide, Fill and Remove props. */
|
|
151
|
+
async function inspectTemplate(file) {
|
|
152
|
+
const presentation = await loadPresentation(await readFile(file));
|
|
153
|
+
return {
|
|
154
|
+
slides: getSlides(presentation).map((slide, index) => ({
|
|
155
|
+
index,
|
|
156
|
+
part: getSlidePartName(slide),
|
|
157
|
+
title: getSlideTitle(slide),
|
|
158
|
+
shapes: getSlideShapes(slide).map((shape) => ({
|
|
159
|
+
id: getShapeId(shape),
|
|
160
|
+
name: getShapeName(shape),
|
|
161
|
+
kind: getShapeKind(shape),
|
|
162
|
+
placeholder: {
|
|
163
|
+
type: getShapePlaceholderType(shape),
|
|
164
|
+
idx: getShapePlaceholderIdx(shape)
|
|
165
|
+
}
|
|
166
|
+
}))
|
|
167
|
+
})),
|
|
168
|
+
layouts: getSlideLayouts(presentation).map((layout) => ({
|
|
169
|
+
part: getSlideLayoutPartName(layout),
|
|
170
|
+
name: getSlideLayoutName(layout),
|
|
171
|
+
type: getSlideLayoutType(layout)
|
|
172
|
+
}))
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/index.ts
|
|
177
|
+
/** Each evaluation gets a fresh module cache and releases it when finished. */
|
|
178
|
+
async function buildDeck(entry) {
|
|
179
|
+
const worker = new Worker(new URL("./worker.mjs", import.meta.url), {
|
|
180
|
+
workerData: { entry: resolve(entry) },
|
|
181
|
+
execArgv: ["--enable-source-maps"]
|
|
182
|
+
});
|
|
183
|
+
let timer;
|
|
184
|
+
try {
|
|
185
|
+
return await new Promise((resolveBuild, reject) => {
|
|
186
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Build exceeded 60 seconds. Check the deck for loops or unresolved promises.")), 6e4);
|
|
187
|
+
worker.once("message", (result) => resolveBuild(result));
|
|
188
|
+
worker.once("error", reject);
|
|
189
|
+
worker.once("exit", (code) => reject(/* @__PURE__ */ new Error(`Build worker exited without a result (code ${code}).`)));
|
|
190
|
+
});
|
|
191
|
+
} finally {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
await worker.terminate();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function exportDeck(entry, output) {
|
|
197
|
+
const result = await buildDeck(entry);
|
|
198
|
+
await writeFile(output, result.bytes);
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { initProject as i, exportDeck as n, inspectTemplate as r, buildDeck as t };
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=src-BA02cMY9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-BA02cMY9.mjs","names":[],"sources":["../src/init.ts","../src/inspect.ts","../src/index.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst starter = `import { Presentation, Slide, Text, Shape } from '@office-kit/pptx-dsl';\n\nexport default (\n <Presentation>\n <Slide background=\"#15171C\">\n <Shape preset=\"rect\" x={0.9} y={2.55} width={0.14} height={1.75} fill=\"#E5481F\" stroke={false} />\n <Text x={1.25} y={2.4} width={10} height={1.1} size={48} bold color=\"#FFFFFF\">\n Your next presentation\n </Text>\n <Text x={1.25} y={3.55} width={10} height={0.6} size={22} color=\"#B4B9C4\">\n Edit this TSX and save to update the preview.\n </Text>\n </Slide>\n </Presentation>\n);\n`;\n\nconst guide = `# Writing this presentation\n\nEdit deck.tsx and local TypeScript components. The JSX runtime is\n@office-kit/pptx-dsl, with no React or Vue dependency.\n\n- Run npm run dev, then open its local URL to preview. Saving source files\n updates the preview. A failed build retains the last successful output.\n- The preview is view-only: use the vertical thumbnails to select slides, zoom\n to inspect details, and Present to view full-screen (Escape exits). Make all\n content and layout changes in TSX; there are no canvas editing controls.\n- Run npm run check for TypeScript diagnostics; run npm run build to export\n deck.pptx. Inspect every slide in the preview after changes.\n- Coordinates and dimensions are inches; font sizes and stroke widths are points.\n The default 16:9 canvas is 13.333 × 7.5 inches.\n- Use native Text, Shape, Image, Table and Chart elements. Use JS functions,\n arrays and map to compose elements; do not import React.\n- Chart takes the core ChartSpec in its spec prop. Text accepts rich ParagraphSpec\n arrays through paragraphs. Raw is an escape hatch for public core APIs.\n- For templates: readFile(new URL('./template.pptx', import.meta.url)), then use\n Presentation source={bytes} mode=\"edit\" and Slide target={{index: 0}}.\n Fill target={{name: 'Title 1'}} changes an existing shape. Indices are zero-based.\n Use office-pptx inspect template.pptx to discover names, layouts and placeholders.\n- Use mode=\"compose\" and Slide from={{index: 0}} to build a new sequence from a\n source deck. This intentionally removes the original slide sequence.\n- Keep assets inside this project so the dev server sees changes. Use source-relative\n new URL('./image.png', import.meta.url) when loading local assets.\n- Preserve existing slides and formatting unless the request calls for changes.\n Never rasterize a chart/table as a shortcut. Raw-only features are not DSL coverage.\n- Preview is a rendering aid, not a guarantee of identical PowerPoint rendering.\n Check the exported file in the target application for final delivery.\n\nVSCode uses tsconfig.json for completion and diagnostics. Start the\n\"Preview presentation\" task, then run \"Simple Browser: Show\" with the printed URL\nand move that editor to a side group. Type errors appear in the Problems panel.\n`;\n\n/** Creates a new directory; existing projects are never overwritten. */\nexport async function initProject(directory: string): Promise<string> {\n const root = resolve(directory);\n const packageLocations = [\n import.meta.resolve('@office-kit/pptx/package.json'),\n import.meta.resolve('@office-kit/pptx-dsl/package.json'),\n new URL('../package.json', import.meta.url),\n ];\n const versions = await Promise.all(\n packageLocations.map(async (location) => {\n const manifest: { version: string } = JSON.parse(await readFile(new URL(location), 'utf8'));\n return `^${manifest.version}`;\n }),\n );\n await mkdir(root);\n const files: Record<string, string> = {\n 'package.json':\n JSON.stringify(\n {\n name: 'my-presentation',\n private: true,\n type: 'module',\n scripts: {\n dev: 'office-pptx dev deck.tsx',\n build: 'office-pptx build deck.tsx --out deck.pptx',\n check: 'tsc --noEmit',\n },\n dependencies: { '@office-kit/pptx': versions[0]!, '@office-kit/pptx-dsl': versions[1]! },\n devDependencies: {\n '@office-kit/pptx-dev': versions[2]!,\n '@types/node': '^24.13.2',\n typescript: '^6.0.3',\n },\n },\n null,\n 2,\n ) + '\\n',\n 'tsconfig.json':\n JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n jsx: 'react-jsx',\n jsxImportSource: '@office-kit/pptx-dsl',\n strict: true,\n exactOptionalPropertyTypes: true,\n noEmit: true,\n allowImportingTsExtensions: true,\n types: ['node'],\n },\n include: ['**/*.ts', '**/*.tsx'],\n exclude: ['node_modules'],\n },\n null,\n 2,\n ) + '\\n',\n 'deck.tsx': starter,\n 'CLAUDE.md': guide,\n '.gitignore': 'node_modules/\\ndeck.pptx\\n',\n '.vscode/tasks.json':\n JSON.stringify(\n {\n version: '2.0.0',\n tasks: [\n {\n label: 'Preview presentation',\n type: 'shell',\n command: 'npm run dev',\n isBackground: true,\n problemMatcher: {\n pattern: { regexp: '^NEVER_MATCH$', file: 1, message: 2 },\n background: {\n activeOnStart: true,\n beginsPattern: '^.*office-pptx dev',\n endsPattern: '^Preview: ',\n },\n },\n },\n {\n label: 'Check presentation',\n type: 'shell',\n command: 'npm run check',\n problemMatcher: '$tsc',\n },\n {\n label: 'Export presentation',\n type: 'shell',\n command: 'npm run build',\n problemMatcher: [],\n },\n ],\n },\n null,\n 2,\n ) + '\\n',\n };\n await mkdir(join(root, '.vscode'));\n await Promise.all(\n Object.entries(files).map(([name, content]) =>\n writeFile(join(root, name), content, { flag: 'wx' }),\n ),\n );\n return root;\n}\n","import { readFile } from 'node:fs/promises';\nimport {\n loadPresentation,\n getSlides,\n getSlidePartName,\n getSlideTitle,\n getSlideShapes,\n getShapeName,\n getShapeId,\n getShapeKind,\n getShapePlaceholderType,\n getShapePlaceholderIdx,\n getSlideLayouts,\n getSlideLayoutName,\n getSlideLayoutPartName,\n getSlideLayoutType,\n} from '@office-kit/pptx';\n\n/** Source references usable directly in Slide, Fill and Remove props. */\nexport async function inspectTemplate(file: string) {\n const presentation = await loadPresentation(await readFile(file));\n return {\n slides: getSlides(presentation).map((slide, index) => ({\n index,\n part: getSlidePartName(slide),\n title: getSlideTitle(slide),\n shapes: getSlideShapes(slide).map((shape) => ({\n id: getShapeId(shape),\n name: getShapeName(shape),\n kind: getShapeKind(shape),\n placeholder: { type: getShapePlaceholderType(shape), idx: getShapePlaceholderIdx(shape) },\n })),\n })),\n layouts: getSlideLayouts(presentation).map((layout) => ({\n part: getSlideLayoutPartName(layout),\n name: getSlideLayoutName(layout),\n type: getSlideLayoutType(layout),\n })),\n };\n}\n","import { Worker } from 'node:worker_threads';\nimport { writeFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport type { BuildResult } from './build.ts';\nexport type { BuildResult } from './build.ts';\nexport { initProject } from './init.ts';\nexport { inspectTemplate } from './inspect.ts';\n\n/** Each evaluation gets a fresh module cache and releases it when finished. */\nexport async function buildDeck(entry: string): Promise<BuildResult> {\n const worker = new Worker(new URL('./worker.mjs', import.meta.url), {\n workerData: { entry: resolve(entry) },\n execArgv: ['--enable-source-maps'],\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await new Promise<BuildResult>((resolveBuild, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n 'Build exceeded 60 seconds. Check the deck for loops or unresolved promises.',\n ),\n ),\n 60_000,\n );\n worker.once('message', (result: BuildResult) => resolveBuild(result));\n worker.once('error', reject);\n worker.once('exit', (code) =>\n reject(new Error(`Build worker exited without a result (code ${code}).`)),\n );\n });\n } finally {\n clearTimeout(timer);\n await worker.terminate();\n }\n}\nexport async function exportDeck(entry: string, output: string): Promise<BuildResult> {\n const result = await buildDeck(entry);\n await writeFile(output, result.bytes);\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/worker.mjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { getSlideSize, getSlides, loadPresentation, savePresentation, validatePresentation } from "@office-kit/pptx";
|
|
5
|
+
import { build, transform } from "esbuild";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import { compile } from "@office-kit/pptx-dsl";
|
|
9
|
+
import { renderSlideToSvg } from "@office-kit/pptx-preview";
|
|
10
|
+
//#region src/build.ts
|
|
11
|
+
const resolvePackage = (name) => fileURLToPath(import.meta.resolve(name));
|
|
12
|
+
/** Evaluates trusted local TSX. This is code execution, not a sandbox. */
|
|
13
|
+
async function buildDeck(entry) {
|
|
14
|
+
const directory = await mkdtemp(join(tmpdir(), "office-pptx-"));
|
|
15
|
+
const output = join(directory, "deck.mjs");
|
|
16
|
+
try {
|
|
17
|
+
const result = await build({
|
|
18
|
+
entryPoints: [resolve(entry)],
|
|
19
|
+
outfile: output,
|
|
20
|
+
bundle: true,
|
|
21
|
+
platform: "node",
|
|
22
|
+
format: "esm",
|
|
23
|
+
target: "node22",
|
|
24
|
+
jsx: "automatic",
|
|
25
|
+
jsxImportSource: "@office-kit/pptx-dsl",
|
|
26
|
+
sourcemap: "inline",
|
|
27
|
+
metafile: true,
|
|
28
|
+
logLevel: "silent",
|
|
29
|
+
alias: {
|
|
30
|
+
"@office-kit/pptx-dsl/jsx-runtime": resolvePackage("@office-kit/pptx-dsl/jsx-runtime"),
|
|
31
|
+
"@office-kit/pptx-dsl/jsx-dev-runtime": resolvePackage("@office-kit/pptx-dsl/jsx-dev-runtime"),
|
|
32
|
+
"@office-kit/pptx-dsl": resolvePackage("@office-kit/pptx-dsl")
|
|
33
|
+
},
|
|
34
|
+
plugins: [{
|
|
35
|
+
name: "source-location",
|
|
36
|
+
setup(builder) {
|
|
37
|
+
builder.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async ({ path }) => {
|
|
38
|
+
return {
|
|
39
|
+
contents: (await transform(await readFile(path, "utf8"), {
|
|
40
|
+
loader: path.endsWith(".tsx") ? "tsx" : path.endsWith(".ts") || path.endsWith(".mts") || path.endsWith(".cts") ? "ts" : path.endsWith(".jsx") ? "jsx" : "js",
|
|
41
|
+
sourcefile: path,
|
|
42
|
+
sourcemap: "inline",
|
|
43
|
+
jsx: "automatic",
|
|
44
|
+
jsxDev: true,
|
|
45
|
+
jsxImportSource: "@office-kit/pptx-dsl",
|
|
46
|
+
define: { "import.meta.url": JSON.stringify(pathToFileURL(path).href) }
|
|
47
|
+
})).code,
|
|
48
|
+
loader: "js"
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}, {
|
|
53
|
+
name: "shared-core",
|
|
54
|
+
setup(builder) {
|
|
55
|
+
builder.onResolve({ filter: /^@office-kit\/pptx$/ }, () => ({
|
|
56
|
+
path: resolvePackage("@office-kit/pptx"),
|
|
57
|
+
external: true
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
}]
|
|
61
|
+
});
|
|
62
|
+
const module = await import(pathToFileURL(output).href);
|
|
63
|
+
if (!module.default) throw new Error("The TSX file must default-export a Presentation.");
|
|
64
|
+
const presentation = await compile(module.default);
|
|
65
|
+
const diagnostics = validatePresentation(presentation);
|
|
66
|
+
const errors = diagnostics.filter((issue) => issue.severity === "error");
|
|
67
|
+
if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);
|
|
68
|
+
const bytes = await savePresentation(presentation);
|
|
69
|
+
const saved = await loadPresentation(bytes);
|
|
70
|
+
const size = getSlideSize(saved);
|
|
71
|
+
return {
|
|
72
|
+
bytes,
|
|
73
|
+
aspectRatio: size ? size.width / size.height : 16 / 9,
|
|
74
|
+
slides: getSlides(saved).map((slide) => renderSlideToSvg(saved, slide)),
|
|
75
|
+
dependencies: Object.keys(result.metafile.inputs).map((path) => resolve(path)),
|
|
76
|
+
diagnostics
|
|
77
|
+
};
|
|
78
|
+
} finally {
|
|
79
|
+
await rm(directory, {
|
|
80
|
+
recursive: true,
|
|
81
|
+
force: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/worker.ts
|
|
87
|
+
if (!parentPort) throw new Error("The build worker must run in a worker thread.");
|
|
88
|
+
const { entry } = workerData;
|
|
89
|
+
parentPort.postMessage(await buildDeck(entry));
|
|
90
|
+
//#endregion
|
|
91
|
+
export {};
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=worker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.mjs","names":[],"sources":["../src/build.ts","../src/worker.ts"],"sourcesContent":["import { build, transform } from 'esbuild';\nimport { mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { compile, type Node } from '@office-kit/pptx-dsl';\nimport {\n getSlideSize,\n getSlides,\n loadPresentation,\n savePresentation,\n validatePresentation,\n} from '@office-kit/pptx';\nimport { renderSlideToSvg } from '@office-kit/pptx-preview';\n\nconst resolvePackage = (name: string) => fileURLToPath(import.meta.resolve(name));\nexport interface BuildResult {\n bytes: Uint8Array;\n slides: string[];\n aspectRatio: number;\n dependencies: string[];\n diagnostics: ReturnType<typeof validatePresentation>;\n}\n/** Evaluates trusted local TSX. This is code execution, not a sandbox. */\nexport async function buildDeck(entry: string): Promise<BuildResult> {\n const directory = await mkdtemp(join(tmpdir(), 'office-pptx-'));\n const output = join(directory, 'deck.mjs');\n try {\n const result = await build({\n entryPoints: [resolve(entry)],\n outfile: output,\n bundle: true,\n platform: 'node',\n format: 'esm',\n target: 'node22',\n jsx: 'automatic',\n jsxImportSource: '@office-kit/pptx-dsl',\n sourcemap: 'inline',\n metafile: true,\n logLevel: 'silent',\n alias: {\n '@office-kit/pptx-dsl/jsx-runtime': resolvePackage('@office-kit/pptx-dsl/jsx-runtime'),\n '@office-kit/pptx-dsl/jsx-dev-runtime': resolvePackage(\n '@office-kit/pptx-dsl/jsx-dev-runtime',\n ),\n '@office-kit/pptx-dsl': resolvePackage('@office-kit/pptx-dsl'),\n },\n plugins: [\n {\n name: 'source-location',\n setup(builder) {\n // A bundled module lives in a temporary directory. Keep source-relative\n // asset URLs pointing at each original module, including imported files.\n builder.onLoad({ filter: /\\.[cm]?[jt]sx?$/ }, async ({ path }) => {\n const source = await readFile(path, 'utf8');\n const loader = path.endsWith('.tsx')\n ? 'tsx'\n : path.endsWith('.ts') || path.endsWith('.mts') || path.endsWith('.cts')\n ? 'ts'\n : path.endsWith('.jsx')\n ? 'jsx'\n : 'js';\n const transformed = await transform(source, {\n loader,\n sourcefile: path,\n sourcemap: 'inline',\n jsx: 'automatic',\n jsxDev: true,\n jsxImportSource: '@office-kit/pptx-dsl',\n define: { 'import.meta.url': JSON.stringify(pathToFileURL(path).href) },\n });\n return { contents: transformed.code, loader: 'js' };\n });\n },\n },\n {\n name: 'shared-core',\n setup(builder) {\n // Core uses symbol-backed handles; all consumers must share its instance.\n builder.onResolve({ filter: /^@office-kit\\/pptx$/ }, () => ({\n path: resolvePackage('@office-kit/pptx'),\n external: true,\n }));\n },\n },\n ],\n });\n const module: { default?: Node } = await import(pathToFileURL(output).href);\n if (!module.default) throw new Error('The TSX file must default-export a Presentation.');\n const presentation = await compile(module.default);\n const diagnostics = validatePresentation(presentation);\n const errors = diagnostics.filter((issue) => issue.severity === 'error');\n if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);\n const bytes = await savePresentation(presentation);\n // Preview serialized output too, so persistence defects are visible during authoring.\n const saved = await loadPresentation(bytes);\n const size = getSlideSize(saved);\n return {\n bytes,\n aspectRatio: size ? size.width / size.height : 16 / 9,\n slides: getSlides(saved).map((slide) => renderSlideToSvg(saved, slide)),\n dependencies: Object.keys(result.metafile.inputs).map((path) => resolve(path)),\n diagnostics,\n };\n } finally {\n await rm(directory, { recursive: true, force: true });\n }\n}\n","import { parentPort, workerData } from 'node:worker_threads';\nimport { buildDeck } from './build.ts';\n\nif (!parentPort) throw new Error('The build worker must run in a worker thread.');\nconst { entry } = workerData as { entry: string };\nparentPort.postMessage(await buildDeck(entry));\n"],"mappings":";;;;;;;;;;AAeA,MAAM,kBAAkB,SAAiB,cAAc,OAAO,KAAK,QAAQ,IAAI,CAAC;;AAShF,eAAsB,UAAU,OAAqC;CACnE,MAAM,YAAY,MAAM,QAAQ,KAAK,OAAO,GAAG,cAAc,CAAC;CAC9D,MAAM,SAAS,KAAK,WAAW,UAAU;CACzC,IAAI;EACF,MAAM,SAAS,MAAM,MAAM;GACzB,aAAa,CAAC,QAAQ,KAAK,CAAC;GAC5B,SAAS;GACT,QAAQ;GACR,UAAU;GACV,QAAQ;GACR,QAAQ;GACR,KAAK;GACL,iBAAiB;GACjB,WAAW;GACX,UAAU;GACV,UAAU;GACV,OAAO;IACL,oCAAoC,eAAe,kCAAkC;IACrF,wCAAwC,eACtC,sCACF;IACA,wBAAwB,eAAe,sBAAsB;GAC/D;GACA,SAAS,CACP;IACE,MAAM;IACN,MAAM,SAAS;KAGb,QAAQ,OAAO,EAAE,QAAQ,kBAAkB,GAAG,OAAO,EAAE,WAAW;MAkBhE,OAAO;OAAE,WAAU,MATO,UAAU,MARf,SAAS,MAAM,MAAM,GAQE;QAC1C,QARa,KAAK,SAAS,MAAM,IAC/B,QACA,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,MAAM,IACnE,OACA,KAAK,SAAS,MAAM,IAClB,QACA;QAGN,YAAY;QACZ,WAAW;QACX,KAAK;QACL,QAAQ;QACR,iBAAiB;QACjB,QAAQ,EAAE,mBAAmB,KAAK,UAAU,cAAc,IAAI,CAAC,CAAC,IAAI,EAAE;OACxE,CAAC,EAAA,CAC8B;OAAM,QAAQ;MAAK;KACpD,CAAC;IACH;GACF,GACA;IACE,MAAM;IACN,MAAM,SAAS;KAEb,QAAQ,UAAU,EAAE,QAAQ,sBAAsB,UAAU;MAC1D,MAAM,eAAe,kBAAkB;MACvC,UAAU;KACZ,EAAE;IACJ;GACF,CACF;EACF,CAAC;EACD,MAAM,SAA6B,MAAM,OAAO,cAAc,MAAM,CAAC,CAAC;EACtE,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,MAAM,kDAAkD;EACvF,MAAM,eAAe,MAAM,QAAQ,OAAO,OAAO;EACjD,MAAM,cAAc,qBAAqB,YAAY;EACrD,MAAM,SAAS,YAAY,QAAQ,UAAU,MAAM,aAAa,OAAO;EACvE,IAAI,OAAO,QAAQ,MAAM,IAAI,MAAM,yBAAyB,KAAK,UAAU,MAAM,GAAG;EACpF,MAAM,QAAQ,MAAM,iBAAiB,YAAY;EAEjD,MAAM,QAAQ,MAAM,iBAAiB,KAAK;EAC1C,MAAM,OAAO,aAAa,KAAK;EAC/B,OAAO;GACL;GACA,aAAa,OAAO,KAAK,QAAQ,KAAK,SAAS,KAAK;GACpD,QAAQ,UAAU,KAAK,CAAC,CAAC,KAAK,UAAU,iBAAiB,OAAO,KAAK,CAAC;GACtE,cAAc,OAAO,KAAK,OAAO,SAAS,MAAM,CAAC,CAAC,KAAK,SAAS,QAAQ,IAAI,CAAC;GAC7E;EACF;CACF,UAAU;EACR,MAAM,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACtD;AACF;;;ACxGA,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,+CAA+C;AAChF,MAAM,EAAE,UAAU;AAClB,WAAW,YAAY,MAAM,UAAU,KAAK,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@office-kit/pptx-dev",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Build and preview PowerPoint TSX presentations locally",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/office-kit/pptx.git",
|
|
9
|
+
"directory": "packages/dev"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"office-pptx": "dist/cli.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"import": "./dist/index.mjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"provenance": true
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"esbuild": "^0.28.1",
|
|
31
|
+
"@office-kit/pptx": "^0.16.1",
|
|
32
|
+
"@office-kit/pptx-preview": "^0.9.5",
|
|
33
|
+
"@office-kit/pptx-dsl": "^0.2.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^24.13.2",
|
|
37
|
+
"tsdown": "^0.22.2",
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22.18.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "node --test test/*.test.mjs"
|
|
47
|
+
}
|
|
48
|
+
}
|