@markii/pack-build 0.17.0-next.4 → 0.17.0-next.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,4 +7,63 @@ esbuild's WebAssembly build, writes the prebuilt form, or produces a
7
7
  toolchain, for authoring hosts and pack build scripts; an app that only
8
8
  installs prebuilt packs does not need it. Node only.
9
9
 
10
+ ## Entry points
11
+
12
+ - `buildPackArchive(packFolder, options?)` takes a folder that holds one
13
+ pack and returns the bytes of its `.mkp` archive. It discovers the pack,
14
+ compiles its React views and, when the pack has terminal views, its
15
+ terminal script, and zips the result. The optional `cacheDir` is a folder
16
+ you own for the compiled scripts. It defaults to a folder under the
17
+ system's temporary directory and is never the pack's own folder.
18
+ - `buildPackRegistrationScript(pack, cacheDir, options?)` compiles the
19
+ pack's React sources into one registration script, `webview.js`, and an
20
+ optional stylesheet. It caches the result under `cacheDir`.
21
+ - `buildPackTerminalScript(pack, cacheDir, options?)` does the same for
22
+ the pack's `ansi` sources and produces `terminal.js`. It reports
23
+ `skipped` when no component names an `ansi` source.
24
+ - `exportPackArchive({ pack, cacheDir, build, buildTerminal?, fs })`
25
+ builds through those functions and returns the bytes of a
26
+ `<name>-<version>.mkp` archive. It writes nothing itself.
27
+ - `exportPack` writes the prebuilt form into a destination folder instead
28
+ of an archive.
29
+
30
+ Every build function returns an outcome and never throws. The outcome is
31
+ `built`, `failed` with a reason, or `skipped`. `buildPackArchive` returns
32
+ `built` or `failed`.
33
+
34
+ The two script builders return paths into the cache. Those paths are valid
35
+ until the next build replaces them, so read a script before you build
36
+ again. `buildPackArchive` does this for you and returns bytes.
37
+
38
+ ## The pack source
39
+
40
+ The lower-level functions take a pack as `discoverPacks` from
41
+ `@markii/host/internal` returns it. A discovered pack carries its `folder`,
42
+ its parsed `manifest`, the `manifestText`, the absolute `componentPaths` of
43
+ its React sources, `componentPathsByEngine` for other engines, and its
44
+ `scriptsDir` for shared Lua. A build script that only wants an archive
45
+ does not need any of this: it calls `buildPackArchive` with the folder.
46
+
47
+ ## Example
48
+
49
+ This script writes one `.mkp` archive for each pack folder under
50
+ `./packs`.
51
+
52
+ ```ts
53
+ import { readdir, writeFile } from 'node:fs/promises';
54
+ import { join } from 'node:path';
55
+ import { buildPackArchive } from '@markii/pack-build';
56
+
57
+ const root = './packs';
58
+ const cacheDir = join(process.cwd(), '.pack-cache');
59
+
60
+ for (const entry of await readdir(root, { withFileTypes: true })) {
61
+ if (!entry.isDirectory()) continue;
62
+ const result = await buildPackArchive(join(root, entry.name), { cacheDir });
63
+ if (result.kind === 'failed') throw new Error(result.reason);
64
+ for (const warning of result.warnings) console.warn(warning);
65
+ await writeFile(result.fileName, result.bytes);
66
+ }
67
+ ```
68
+
10
69
  See `docs/packs.md` in the repository for the pack contract.
@@ -0,0 +1,34 @@
1
+ export interface BuildPackArchiveOptions {
2
+ /**
3
+ * A folder this caller owns, where compiled scripts are cached. It is
4
+ * never the pack's own folder. Defaults to `markii-pack-build` under the
5
+ * operating system's temporary folder.
6
+ */
7
+ readonly cacheDir?: string;
8
+ }
9
+ export type BuildPackArchiveOutcome = {
10
+ readonly kind: 'built';
11
+ /** The `.mkp` archive's bytes. */
12
+ readonly bytes: Uint8Array;
13
+ /** `<name>-<version>.mkp`, or `<name>.mkp` when the manifest declares no version. */
14
+ readonly fileName: string;
15
+ /** The build's pack-CSS lint warnings. Empty when there are none. */
16
+ readonly warnings: readonly string[];
17
+ } | {
18
+ readonly kind: 'failed';
19
+ readonly reason: string;
20
+ };
21
+ /**
22
+ * Builds the `.mkp` archive for the one pack in `packFolder`. The folder
23
+ * either holds a `pack.json` itself or holds exactly one child folder that
24
+ * does. Zero packs, several packs and an invalid pack are all `failed`,
25
+ * with the reason discovery gave. Never throws, and never writes into
26
+ * `packFolder`: the build is cached under `options.cacheDir`.
27
+ *
28
+ * The compiled scripts live in that cache until the next build.
29
+ * `buildPackRegistrationScript` and `buildPackTerminalScript` return paths
30
+ * into it, and those paths are valid only until the next build replaces
31
+ * them. This function reads them back before it returns, so its result
32
+ * holds bytes, not paths.
33
+ */
34
+ export declare function buildPackArchive(packFolder: string, options?: BuildPackArchiveOptions): Promise<BuildPackArchiveOutcome>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `buildPackArchive`: one call from a pack folder to the bytes of its
3
+ * `.mkp` archive. It discovers the pack in the folder, compiles its React
4
+ * views and, when the pack has terminal views, its terminal script, and
5
+ * zips the result. A build script needs this and nothing else from
6
+ * `@markii/host`.
7
+ */
8
+ import { readFile, readdir } from 'node:fs/promises';
9
+ import { tmpdir } from 'node:os';
10
+ import * as path from 'node:path';
11
+ import { discoverPacks } from '@markii/host/internal';
12
+ import { buildPackRegistrationScript, buildPackTerminalScript, } from './pack-build.js';
13
+ import { exportPackArchive } from './pack-export-archive.js';
14
+ async function readText(absolutePath) {
15
+ try {
16
+ return await readFile(absolutePath, 'utf8');
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ async function listEntries(absoluteDir) {
23
+ try {
24
+ const entries = await readdir(absoluteDir, { withFileTypes: true });
25
+ return entries.map((entry) => ({
26
+ name: entry.name,
27
+ isDirectory: entry.isDirectory(),
28
+ }));
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ function describeThrown(error) {
35
+ return error instanceof Error ? error.message : String(error);
36
+ }
37
+ /**
38
+ * Builds the `.mkp` archive for the one pack in `packFolder`. The folder
39
+ * either holds a `pack.json` itself or holds exactly one child folder that
40
+ * does. Zero packs, several packs and an invalid pack are all `failed`,
41
+ * with the reason discovery gave. Never throws, and never writes into
42
+ * `packFolder`: the build is cached under `options.cacheDir`.
43
+ *
44
+ * The compiled scripts live in that cache until the next build.
45
+ * `buildPackRegistrationScript` and `buildPackTerminalScript` return paths
46
+ * into it, and those paths are valid only until the next build replaces
47
+ * them. This function reads them back before it returns, so its result
48
+ * holds bytes, not paths.
49
+ */
50
+ export async function buildPackArchive(packFolder, options = {}) {
51
+ try {
52
+ const cacheDir = options.cacheDir ?? path.join(tmpdir(), 'markii-pack-build');
53
+ const discovered = await discoverPacks([packFolder], readText, listEntries);
54
+ if (discovered.packs.length === 0) {
55
+ const reasons = discovered.skipped.map((entry) => entry.reason);
56
+ return {
57
+ kind: 'failed',
58
+ reason: reasons.length > 0
59
+ ? `no pack could be built from "${packFolder}": ${reasons.join('; ')}`
60
+ : `no pack was found in "${packFolder}"`,
61
+ };
62
+ }
63
+ if (discovered.packs.length > 1) {
64
+ return {
65
+ kind: 'failed',
66
+ reason: `"${packFolder}" holds ${String(discovered.packs.length)} packs, and a build takes one`,
67
+ };
68
+ }
69
+ const pack = discovered.packs[0];
70
+ const built = await exportPackArchive({
71
+ pack,
72
+ cacheDir,
73
+ build: buildPackRegistrationScript,
74
+ buildTerminal: buildPackTerminalScript,
75
+ fs: { readFile: readText, listDirectory: listFileNames },
76
+ });
77
+ if (built.kind === 'failed') {
78
+ return { kind: 'failed', reason: built.reason };
79
+ }
80
+ return {
81
+ kind: 'built',
82
+ bytes: built.bytes,
83
+ fileName: built.fileName,
84
+ warnings: built.warnings,
85
+ };
86
+ }
87
+ catch (error) {
88
+ return { kind: 'failed', reason: describeThrown(error) };
89
+ }
90
+ }
91
+ async function listFileNames(absoluteDir) {
92
+ try {
93
+ return await readdir(absoluteDir);
94
+ }
95
+ catch {
96
+ return [];
97
+ }
98
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export type { PackBuildOptions, PackBuildOutcome, PackBuildSource, PackFileReader, } from './pack-build.js';
2
- export { buildPackRegistrationScript, buildPackTerminalScript, computeCacheKey, } from './pack-build.js';
3
- export { lintPackCss, lintPackCssColors, lintPackCssPrefix, } from './pack-css-lint.js';
2
+ export { buildPackRegistrationScript, buildPackTerminalScript, } from './pack-build.js';
3
+ export type { BuildPackArchiveOptions, BuildPackArchiveOutcome, } from './build-pack-archive.js';
4
+ export { buildPackArchive } from './build-pack-archive.js';
4
5
  export type { ConfirmPackOverwrite, ExportPackOptions, PackExportBuilder, PackExportFs, PackExportOutcome, } from './pack-export.js';
5
- export { exportPack, resolveExportTarget } from './pack-export.js';
6
+ export { exportPack } from './pack-export.js';
6
7
  export type { ExportPackArchiveOptions, PackExportArchiveOutcome, } from './pack-export-archive.js';
7
8
  export { exportPackArchive } from './pack-export-archive.js';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // prebuilt form. Node only (esbuild-wasm, node:fs, node:crypto). The
3
3
  // neutral half of the prebuilt-pack convention (file names, shadowing
4
4
  // detection) stays in @markii/host, which this package imports.
5
- export { buildPackRegistrationScript, buildPackTerminalScript, computeCacheKey, } from './pack-build.js';
6
- export { lintPackCss, lintPackCssColors, lintPackCssPrefix, } from './pack-css-lint.js';
7
- export { exportPack, resolveExportTarget } from './pack-export.js';
5
+ export { buildPackRegistrationScript, buildPackTerminalScript, } from './pack-build.js';
6
+ export { buildPackArchive } from './build-pack-archive.js';
7
+ export { exportPack } from './pack-export.js';
8
8
  export { exportPackArchive } from './pack-export-archive.js';
@@ -21,8 +21,6 @@ export interface PackBuildSource {
21
21
  }
22
22
  /** Reads a file's UTF-8 text, or resolves `undefined` if it does not exist / cannot be read. Never rejects for an ordinary "not found" — injected so this module needs no real filesystem to test. Structurally identical to `apps/vscode`'s own `PackFileReader` (`./discover.ts`), deliberately not shared by import: a host's discovery module and this builder are independent seams. */
23
23
  export type PackFileReader = (absolutePath: string) => Promise<string | undefined>;
24
- /** esbuild-wasm's browser (in-process WebAssembly) `build` function signature — imported as a TYPE only, so this line never causes `esbuild-wasm` itself to be pulled into whatever bundles this module (see `loadEsbuildWasm`'s doc comment for why the VALUE side must never be a static import). */
25
- type EsbuildBuildFn = typeof import('esbuild-wasm/lib/browser.js').build;
26
24
  /**
27
25
  * The PRE-build cache key for one pack: a SHA-256 of `BUILDER_VERSION`, the
28
26
  * manifest (stable-stringified: `Object.keys` order for a plain
@@ -78,8 +76,6 @@ export interface PackBuildOptions {
78
76
  readonly esbuildWasmBinaryPath?: string;
79
77
  /** Reads one component source file's UTF-8 text, or `undefined` if unreadable — defaults to real `node:fs`. Injected so a build-failure ("missing component source") path is testable without touching disk. */
80
78
  readonly readComponentSource?: PackFileReader;
81
- /** Replaces esbuild-wasm's `build` function outright — for tests that want to assert cache behavior (or a build failure) without paying for a real esbuild-wasm invocation, and for the ANY-real-invocation counting a cache-hit test needs. Bypasses `loadEsbuildWasm`/the module-path options entirely when given. */
82
- readonly build?: EsbuildBuildFn;
83
79
  }
84
80
  /**
85
81
  * Builds (or reuses a cached build of) one pack's webview registration
@@ -112,4 +108,3 @@ export declare function buildPackRegistrationScript(pack: PackBuildSource, cache
112
108
  * import in a terminal source is compiled away and ignored. Never throws.
113
109
  */
114
110
  export declare function buildPackTerminalScript(pack: PackBuildSource, cacheDir: string, options?: PackBuildOptions): Promise<PackBuildOutcome>;
115
- export {};
@@ -183,7 +183,10 @@ export async function exportPackArchive(options) {
183
183
  for (const file of luaFiles) {
184
184
  zipInput[`scripts/${file.name}`] = encoder.encode(file.text);
185
185
  }
186
- const bytes = zipSync(zipInput);
186
+ // A fixed modification time, so the same pack zips to the same bytes at
187
+ // any hour and on any machine. The date is built from local components,
188
+ // so the stored DOS time reads 1980-01-01 00:00 in every time zone.
189
+ const bytes = zipSync(zipInput, { mtime: new Date(1980, 0, 1, 0, 0, 0) });
187
190
  return {
188
191
  kind: 'built',
189
192
  packName,
@@ -58,7 +58,7 @@ export type PackExportOutcome = {
58
58
  /** The build's pack-CSS lint warnings, passed through unchanged. */
59
59
  readonly warnings: readonly string[];
60
60
  } | {
61
- readonly kind: 'cancelled';
61
+ readonly kind: 'declined';
62
62
  readonly packName: string;
63
63
  } | {
64
64
  readonly kind: 'failed';
@@ -80,7 +80,7 @@ export declare function resolveExportTarget(root: string, ...segments: readonly
80
80
  * NEVER throws: every failure (an unsafe `exportName`, a build failure, an
81
81
  * unreadable build output or `pack.json`, a write that fails) comes back as
82
82
  * `{ kind: 'failed', reason }`. A declined overwrite comes back as
83
- * `{ kind: 'cancelled' }` with nothing written.
83
+ * `{ kind: 'declined' }` with nothing written.
84
84
  *
85
85
  * Order of operations: resolve the destination folder and every write
86
86
  * target inside it (path-jailed against `destinationDir`, including the
@@ -67,7 +67,7 @@ function packNameOf(pack) {
67
67
  * NEVER throws: every failure (an unsafe `exportName`, a build failure, an
68
68
  * unreadable build output or `pack.json`, a write that fails) comes back as
69
69
  * `{ kind: 'failed', reason }`. A declined overwrite comes back as
70
- * `{ kind: 'cancelled' }` with nothing written.
70
+ * `{ kind: 'declined' }` with nothing written.
71
71
  *
72
72
  * Order of operations: resolve the destination folder and every write
73
73
  * target inside it (path-jailed against `destinationDir`, including the
@@ -274,7 +274,7 @@ export async function exportPack(options) {
274
274
  return { kind: 'failed', packName, reason: describeThrown(err) };
275
275
  }
276
276
  if (!proceed) {
277
- return { kind: 'cancelled', packName };
277
+ return { kind: 'declined', packName };
278
278
  }
279
279
  }
280
280
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/pack-build",
3
- "version": "0.17.0-next.4",
3
+ "version": "0.17.0-next.6",
4
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
5
  "keywords": [
6
6
  "markdown",
@@ -39,8 +39,8 @@
39
39
  "build:dist": "rm -rf dist && tsc -p tsconfig.build.json"
40
40
  },
41
41
  "dependencies": {
42
- "@markii/host": "^0.17.0-next.4",
43
- "@markii/pack": "^0.17.0-next.4",
42
+ "@markii/host": "^0.17.0-next.6",
43
+ "@markii/pack": "^0.17.0-next.6",
44
44
  "esbuild-wasm": "^0.28.2",
45
45
  "fflate": "^0.8.3"
46
46
  },