@markii/pack-build 0.0.0-stage → 0.17.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/pack-build.d.ts +113 -0
- package/dist/pack-build.js +1259 -0
- package/dist/pack-css-lint.d.ts +38 -0
- package/dist/pack-css-lint.js +231 -0
- package/dist/pack-export-archive.d.ts +45 -0
- package/dist/pack-export-archive.js +198 -0
- package/dist/pack-export.d.ts +94 -0
- package/dist/pack-export.js +335 -0
- package/package.json +48 -4
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The internal compose-and-write path pack packaging builds on (VS Code is
|
|
3
|
+
* the AUTHORING host and owns pack packaging; Obsidian only ever CONSUMES a
|
|
4
|
+
* pack another host produced). Compiles a pack with the existing
|
|
5
|
+
* `./pack-build.ts` machinery, through its normal host-owned cache, exactly
|
|
6
|
+
* like an ordinary preview build; only its OUTPUT is copied. Then writes a
|
|
7
|
+
* clean, distributable folder at a CALLER-CHOSEN destination: `pack.json`
|
|
8
|
+
* (copied verbatim from the pack's own folder), `webview.js`,
|
|
9
|
+
* `webview.css` when the build emitted one, and every `*.lua` file the
|
|
10
|
+
* pack's own `scripts/` directory holds.
|
|
11
|
+
*
|
|
12
|
+
* The pack's SOURCE folder is NEVER written to. Two callers compose
|
|
13
|
+
* through this module: `apps/vscode/src/packs/build-bundled-packs.ts`
|
|
14
|
+
* writes a pack's build output into the extension's own `dist/packs` at
|
|
15
|
+
* build time, and `./pack-export-archive.ts` reuses its `PackExportBuilder`
|
|
16
|
+
* and `PackExportFs` types to build the ONE shape the "Markii: Export
|
|
17
|
+
* Pack" command itself produces, a single `.mkp` archive.
|
|
18
|
+
*
|
|
19
|
+
* Filesystem access is injected (`PackExportFs`), matching every other
|
|
20
|
+
* module in this directory, so this module is testable with in-memory
|
|
21
|
+
* fakes and carries no host-specific save-dialog/notification behavior of
|
|
22
|
+
* its own; `confirmOverwrite` is the one piece of user interaction, and it
|
|
23
|
+
* is injected too.
|
|
24
|
+
*/
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
import { PREBUILT_SCRIPT_FILENAME, PREBUILT_STYLESHEET_FILENAME, PREBUILT_TERMINAL_SCRIPT_FILENAME, emptyWebviewScript, } from '@markii/host';
|
|
27
|
+
const MANIFEST_FILENAME = 'pack.json';
|
|
28
|
+
const SCRIPTS_DIRNAME = 'scripts';
|
|
29
|
+
/**
|
|
30
|
+
* Path jail. Joins `segments` onto `root`, or `undefined` when any segment
|
|
31
|
+
* is not a plain path segment (empty, `.`, `..`, containing `/` or `\`, or
|
|
32
|
+
* absolute) or the resolved join would land outside `root`. This is the
|
|
33
|
+
* ONLY function `exportPack` uses to compute a write target: nothing is
|
|
34
|
+
* ever written to a path this function did not itself derive. The
|
|
35
|
+
* caller-supplied `exportName` goes through this exact same validation as
|
|
36
|
+
* every other segment — an `exportName` carrying a separator, `..`, or an
|
|
37
|
+
* absolute path is refused here, before anything is written.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveExportTarget(root, ...segments) {
|
|
40
|
+
if (segments.length === 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
for (const segment of segments) {
|
|
43
|
+
if (segment.length === 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (segment === '.' || segment === '..')
|
|
46
|
+
return undefined;
|
|
47
|
+
if (segment.includes('/') || segment.includes('\\'))
|
|
48
|
+
return undefined;
|
|
49
|
+
if (path.isAbsolute(segment))
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const resolvedRoot = path.resolve(root);
|
|
53
|
+
const target = path.resolve(resolvedRoot, ...segments);
|
|
54
|
+
if (target !== resolvedRoot && !target.startsWith(resolvedRoot + path.sep)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return target;
|
|
58
|
+
}
|
|
59
|
+
function describeThrown(err) {
|
|
60
|
+
return err instanceof Error ? err.message : String(err);
|
|
61
|
+
}
|
|
62
|
+
/** `manifest.name` unless empty/whitespace, in which case a placeholder that still reads sensibly in a failure message. */
|
|
63
|
+
function packNameOf(pack) {
|
|
64
|
+
return pack.manifest.name;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* NEVER throws: every failure (an unsafe `exportName`, a build failure, an
|
|
68
|
+
* unreadable build output or `pack.json`, a write that fails) comes back as
|
|
69
|
+
* `{ kind: 'failed', reason }`. A declined overwrite comes back as
|
|
70
|
+
* `{ kind: 'cancelled' }` with nothing written.
|
|
71
|
+
*
|
|
72
|
+
* Order of operations: resolve the destination folder and every write
|
|
73
|
+
* target inside it (path-jailed against `destinationDir`, including the
|
|
74
|
+
* user-supplied `exportName` itself); run the build through the normal
|
|
75
|
+
* cache; read `pack.json` and the built script (and stylesheet, when the
|
|
76
|
+
* build produced one) back as text; list the pack's `scripts/` directory
|
|
77
|
+
* for `.lua` files; ask `confirmOverwrite` once, only if the destination
|
|
78
|
+
* folder already holds files this export would replace; then create the
|
|
79
|
+
* destination folder and write everything.
|
|
80
|
+
*/
|
|
81
|
+
export async function exportPack(options) {
|
|
82
|
+
const { pack, cacheDir, destinationDir, exportName, build, buildTerminal, fs, confirmOverwrite, } = options;
|
|
83
|
+
const packName = packNameOf(pack);
|
|
84
|
+
const destinationFolder = resolveExportTarget(destinationDir, exportName);
|
|
85
|
+
if (destinationFolder === undefined) {
|
|
86
|
+
return {
|
|
87
|
+
kind: 'failed',
|
|
88
|
+
packName,
|
|
89
|
+
reason: `"${exportName}" is not a valid export folder name`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const manifestTarget = resolveExportTarget(destinationFolder, MANIFEST_FILENAME);
|
|
93
|
+
const scriptTarget = resolveExportTarget(destinationFolder, PREBUILT_SCRIPT_FILENAME);
|
|
94
|
+
const stylesheetTarget = resolveExportTarget(destinationFolder, PREBUILT_STYLESHEET_FILENAME);
|
|
95
|
+
const terminalTarget = resolveExportTarget(destinationFolder, PREBUILT_TERMINAL_SCRIPT_FILENAME);
|
|
96
|
+
const scriptsDirTarget = resolveExportTarget(destinationFolder, SCRIPTS_DIRNAME);
|
|
97
|
+
if (manifestTarget === undefined ||
|
|
98
|
+
scriptTarget === undefined ||
|
|
99
|
+
stylesheetTarget === undefined ||
|
|
100
|
+
terminalTarget === undefined ||
|
|
101
|
+
scriptsDirTarget === undefined) {
|
|
102
|
+
return {
|
|
103
|
+
kind: 'failed',
|
|
104
|
+
packName,
|
|
105
|
+
reason: `could not resolve a safe write location inside "${destinationFolder}"`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
let manifestText;
|
|
109
|
+
try {
|
|
110
|
+
manifestText = await fs.readFile(path.join(pack.folder, MANIFEST_FILENAME));
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
114
|
+
}
|
|
115
|
+
if (manifestText === undefined) {
|
|
116
|
+
return {
|
|
117
|
+
kind: 'failed',
|
|
118
|
+
packName,
|
|
119
|
+
reason: `pack.json for pack "${packName}" could not be read`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
let outcome;
|
|
123
|
+
try {
|
|
124
|
+
outcome = await build(pack, cacheDir);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
128
|
+
}
|
|
129
|
+
if (outcome.kind === 'failed') {
|
|
130
|
+
// Verbatim: a host-specific build-failure reason arrives through here,
|
|
131
|
+
// and its single home is that app's own diagnostics module — never
|
|
132
|
+
// reworded on this path.
|
|
133
|
+
return { kind: 'failed', packName, reason: outcome.reason };
|
|
134
|
+
}
|
|
135
|
+
let terminalText;
|
|
136
|
+
const hasTerminalSources = Object.keys(pack.componentPathsByEngine?.['ansi'] ?? {}).length > 0;
|
|
137
|
+
if (buildTerminal !== undefined && hasTerminalSources) {
|
|
138
|
+
let terminalOutcome;
|
|
139
|
+
try {
|
|
140
|
+
terminalOutcome = await buildTerminal(pack, cacheDir);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
144
|
+
}
|
|
145
|
+
if (terminalOutcome.kind === 'failed') {
|
|
146
|
+
return {
|
|
147
|
+
kind: 'failed',
|
|
148
|
+
packName,
|
|
149
|
+
reason: `terminal build: ${terminalOutcome.reason}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (terminalOutcome.kind === 'built') {
|
|
153
|
+
try {
|
|
154
|
+
terminalText = await fs.readFile(terminalOutcome.scriptPath);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
terminalText = undefined;
|
|
158
|
+
}
|
|
159
|
+
if (terminalText === undefined) {
|
|
160
|
+
return {
|
|
161
|
+
kind: 'failed',
|
|
162
|
+
packName,
|
|
163
|
+
reason: `built terminal script for pack "${packName}" could not be read back`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
let scriptText;
|
|
169
|
+
if (outcome.kind === 'skipped') {
|
|
170
|
+
if (terminalText === undefined) {
|
|
171
|
+
return {
|
|
172
|
+
kind: 'failed',
|
|
173
|
+
packName,
|
|
174
|
+
reason: `pack "${packName}" was not built`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
scriptText = emptyWebviewScript(JSON.stringify(pack.manifest));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
try {
|
|
181
|
+
scriptText = await fs.readFile(outcome.scriptPath);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
185
|
+
}
|
|
186
|
+
if (scriptText === undefined) {
|
|
187
|
+
return {
|
|
188
|
+
kind: 'failed',
|
|
189
|
+
packName,
|
|
190
|
+
reason: `built script for pack "${packName}" could not be read back`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
let stylesheetText;
|
|
195
|
+
if (outcome.kind === 'built' && outcome.stylesheetPath !== undefined) {
|
|
196
|
+
try {
|
|
197
|
+
stylesheetText = await fs.readFile(outcome.stylesheetPath);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
stylesheetText = undefined;
|
|
201
|
+
}
|
|
202
|
+
// A read failure here is treated as "no stylesheet" rather than failing
|
|
203
|
+
// the whole export — the script alone is still a usable pack.
|
|
204
|
+
}
|
|
205
|
+
// The pack's own scripts/ directory, copied through verbatim. Absent or
|
|
206
|
+
// unreadable simply yields no script files, matching every other
|
|
207
|
+
// filesystem-adjacent module in this directory's "quiet skip" posture for
|
|
208
|
+
// an individually unreadable entry.
|
|
209
|
+
let scriptEntries = [];
|
|
210
|
+
try {
|
|
211
|
+
scriptEntries = await fs.listDirectory(pack.scriptsDir);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
scriptEntries = [];
|
|
215
|
+
}
|
|
216
|
+
const luaFilenames = scriptEntries.filter((name) => name.endsWith('.lua'));
|
|
217
|
+
const luaFiles = [];
|
|
218
|
+
for (const filename of luaFilenames) {
|
|
219
|
+
const target = resolveExportTarget(scriptsDirTarget, filename);
|
|
220
|
+
if (target === undefined)
|
|
221
|
+
continue; // an unsafe entry name is skipped, not fatal
|
|
222
|
+
let text;
|
|
223
|
+
try {
|
|
224
|
+
text = await fs.readFile(path.join(pack.scriptsDir, filename));
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
text = undefined;
|
|
228
|
+
}
|
|
229
|
+
if (text === undefined)
|
|
230
|
+
continue; // unreadable individual file: quietly skipped
|
|
231
|
+
luaFiles.push({ target, text });
|
|
232
|
+
}
|
|
233
|
+
let destinationExists;
|
|
234
|
+
try {
|
|
235
|
+
destinationExists = await fs.exists(destinationFolder);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
239
|
+
}
|
|
240
|
+
const existingPaths = [];
|
|
241
|
+
if (destinationExists) {
|
|
242
|
+
const candidates = [
|
|
243
|
+
manifestTarget,
|
|
244
|
+
scriptTarget,
|
|
245
|
+
...(terminalText !== undefined ? [terminalTarget] : []),
|
|
246
|
+
...(stylesheetText !== undefined ? [stylesheetTarget] : []),
|
|
247
|
+
...luaFiles.map((file) => file.target),
|
|
248
|
+
];
|
|
249
|
+
try {
|
|
250
|
+
for (const candidate of candidates) {
|
|
251
|
+
if (await fs.exists(candidate)) {
|
|
252
|
+
existingPaths.push(candidate);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// A stale webview.css this export would remove (see below) also
|
|
256
|
+
// counts as "a file this export would replace".
|
|
257
|
+
if (stylesheetText === undefined && (await fs.exists(stylesheetTarget))) {
|
|
258
|
+
existingPaths.push(stylesheetTarget);
|
|
259
|
+
}
|
|
260
|
+
if (terminalText === undefined && (await fs.exists(terminalTarget))) {
|
|
261
|
+
existingPaths.push(terminalTarget);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (existingPaths.length > 0) {
|
|
269
|
+
let proceed;
|
|
270
|
+
try {
|
|
271
|
+
proceed = await confirmOverwrite({ packName, existingPaths });
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
275
|
+
}
|
|
276
|
+
if (!proceed) {
|
|
277
|
+
return { kind: 'cancelled', packName };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await fs.makeDirectory(destinationFolder);
|
|
282
|
+
await fs.writeFile(manifestTarget, manifestText);
|
|
283
|
+
await fs.writeFile(scriptTarget, scriptText);
|
|
284
|
+
if (terminalText !== undefined) {
|
|
285
|
+
await fs.writeFile(terminalTarget, terminalText);
|
|
286
|
+
}
|
|
287
|
+
else if (await fs.exists(terminalTarget)) {
|
|
288
|
+
// A stale terminal.js from an earlier export would otherwise keep
|
|
289
|
+
// rendering views this build no longer has.
|
|
290
|
+
await fs.deleteFile(terminalTarget);
|
|
291
|
+
}
|
|
292
|
+
let stylesheetBytes;
|
|
293
|
+
let removedStylesheetPath;
|
|
294
|
+
if (stylesheetText !== undefined) {
|
|
295
|
+
await fs.writeFile(stylesheetTarget, stylesheetText);
|
|
296
|
+
stylesheetBytes = Buffer.byteLength(stylesheetText, 'utf8');
|
|
297
|
+
}
|
|
298
|
+
else if (await fs.exists(stylesheetTarget)) {
|
|
299
|
+
// A stale webview.css from an earlier export would otherwise keep
|
|
300
|
+
// styling the pack even though this build produced none.
|
|
301
|
+
await fs.deleteFile(stylesheetTarget);
|
|
302
|
+
removedStylesheetPath = stylesheetTarget;
|
|
303
|
+
}
|
|
304
|
+
if (luaFiles.length > 0) {
|
|
305
|
+
await fs.makeDirectory(scriptsDirTarget);
|
|
306
|
+
for (const file of luaFiles) {
|
|
307
|
+
await fs.writeFile(file.target, file.text);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
kind: 'written',
|
|
312
|
+
packName,
|
|
313
|
+
destinationFolder,
|
|
314
|
+
manifestPath: manifestTarget,
|
|
315
|
+
manifestBytes: Buffer.byteLength(manifestText, 'utf8'),
|
|
316
|
+
scriptPath: scriptTarget,
|
|
317
|
+
scriptBytes: Buffer.byteLength(scriptText, 'utf8'),
|
|
318
|
+
...(terminalText !== undefined
|
|
319
|
+
? {
|
|
320
|
+
terminalScriptPath: terminalTarget,
|
|
321
|
+
terminalScriptBytes: Buffer.byteLength(terminalText, 'utf8'),
|
|
322
|
+
}
|
|
323
|
+
: {}),
|
|
324
|
+
...(stylesheetText !== undefined
|
|
325
|
+
? { stylesheetPath: stylesheetTarget, stylesheetBytes }
|
|
326
|
+
: {}),
|
|
327
|
+
...(removedStylesheetPath !== undefined ? { removedStylesheetPath } : {}),
|
|
328
|
+
scriptFilesCopied: luaFiles.length,
|
|
329
|
+
warnings: outcome.kind === 'built' ? outcome.warnings : [],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
return { kind: 'failed', packName, reason: describeThrown(err) };
|
|
334
|
+
}
|
|
335
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markii/pack-build",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.17.0-next.2",
|
|
4
|
+
"description": "Pack compilation for Markii authoring hosts: compiles a pack's component sources (and imported CSS) with esbuild-wasm's in-process path, and writes the prebuilt form or a .mkp archive. Node only.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"markdown",
|
|
7
|
+
"mk.md",
|
|
8
|
+
"markii",
|
|
9
|
+
"pack",
|
|
10
|
+
"esbuild"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "sadigaxund",
|
|
14
|
+
"homepage": "https://github.com/markii-org/markii#readme",
|
|
15
|
+
"bugs": "https://github.com/markii-org/markii/issues",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/markii-org/markii.git",
|
|
19
|
+
"directory": "packages/markii-pack-build"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"build": "tsc --noEmit -p tsconfig.json",
|
|
35
|
+
"lint": "eslint .",
|
|
36
|
+
"build:dist": "rm -rf dist && tsc -p tsconfig.build.json"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@markii/host": "^0.17.0-next.2",
|
|
40
|
+
"@markii/pack": "^0.17.0-next.2",
|
|
41
|
+
"esbuild-wasm": "^0.28.2",
|
|
42
|
+
"fflate": "^0.8.3"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|