@markii/bundle 0.10.0 → 0.12.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.
@@ -1,14 +1,22 @@
1
1
  import type { BundleFsGrant } from './paths.js';
2
2
  /**
3
- * `manifest.json`'s contract (spec §9–§11). `mark` is the only required
3
+ * `manifest.json`'s contract (spec §9–§11). `spec` is the only required
4
4
  * field. The index signature keeps unrecognized top-level keys typed as
5
5
  * `unknown` rather than dropped — `parseManifest` preserves them verbatim
6
- * (see the "forward compatibility" note there) so a manifest written by a
7
- * newer spec version round-trips through an older implementation intact.
6
+ * (see the "forward compatibility" note there) so a manifest field
7
+ * introduced by a newer spec version round-trips through an older
8
+ * implementation intact.
9
+ *
10
+ * `mark` is the retired name for this field (pre-rename). It is still
11
+ * accepted on read (see `parseManifest`) but is never written; keep it as
12
+ * an explicit optional field, rather than relying on the index signature,
13
+ * so a reader can name it without an `as` cast.
8
14
  */
9
15
  export interface BundleManifest {
10
16
  /** Spec semver this bundle was authored against, e.g. `"0.1.0"`. */
11
- mark: string;
17
+ spec: string;
18
+ /** Retired name for `spec`. Read-only compatibility; never written. */
19
+ mark?: string;
12
20
  permissions?: BundlePermissions;
13
21
  uses?: string[];
14
22
  /**
@@ -61,6 +69,17 @@ export declare const CURRENT_SPEC_VERSION = "0.1.0";
61
69
  * rather than preserved, since `permissions` itself is a known, normalized
62
70
  * key. Extending preservation to arbitrary nesting depth would be a
63
71
  * reasonable follow-up but wasn't asked for here.
72
+ *
73
+ * `spec`/`mark` compatibility: `spec` is the current, required field name.
74
+ * `mark` is the retired name. If `spec` is present and valid it is always
75
+ * used, regardless of whether `mark` is also present. If `spec` is absent
76
+ * but a valid `mark` is present, `mark`'s value is used as `spec` and a
77
+ * warning is recorded (never an error) so legacy manifests keep working.
78
+ * If both are present, `mark` is ignored and a warning names it, since a
79
+ * manifest author who sets both most likely left `mark` behind while
80
+ * migrating to `spec` — preferring the new field is the least surprising
81
+ * behavior and matches how unknown-key forward-compat already favors the
82
+ * newer shape.
64
83
  */
65
84
  export declare function parseManifest(json: string): ManifestParseResult;
66
85
  /** A minimal, valid manifest for a freshly promoted bundle: no permissions granted, no packs declared. */
package/dist/manifest.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /** The current spec version this package's default manifests declare. */
2
2
  export const CURRENT_SPEC_VERSION = '0.1.0';
3
3
  const KNOWN_TOP_LEVEL_KEYS = new Set([
4
+ 'spec',
4
5
  'mark',
5
6
  'permissions',
6
7
  'uses',
@@ -20,6 +21,9 @@ function isStringArray(value) {
20
21
  function isPlainObject(value) {
21
22
  return value !== null && typeof value === 'object' && !Array.isArray(value);
22
23
  }
24
+ function isValidSpecVersion(value) {
25
+ return typeof value === 'string' && SEMVER_RE.test(value);
26
+ }
23
27
  /**
24
28
  * Hand-rolled `manifest.json` validation (no schema library — see AGENTS.md
25
29
  * dependency policy). Never throws: malformed JSON, a non-object root, or
@@ -37,6 +41,17 @@ function isPlainObject(value) {
37
41
  * rather than preserved, since `permissions` itself is a known, normalized
38
42
  * key. Extending preservation to arbitrary nesting depth would be a
39
43
  * reasonable follow-up but wasn't asked for here.
44
+ *
45
+ * `spec`/`mark` compatibility: `spec` is the current, required field name.
46
+ * `mark` is the retired name. If `spec` is present and valid it is always
47
+ * used, regardless of whether `mark` is also present. If `spec` is absent
48
+ * but a valid `mark` is present, `mark`'s value is used as `spec` and a
49
+ * warning is recorded (never an error) so legacy manifests keep working.
50
+ * If both are present, `mark` is ignored and a warning names it, since a
51
+ * manifest author who sets both most likely left `mark` behind while
52
+ * migrating to `spec` — preferring the new field is the least surprising
53
+ * behavior and matches how unknown-key forward-compat already favors the
54
+ * newer shape.
40
55
  */
41
56
  export function parseManifest(json) {
42
57
  let raw;
@@ -57,13 +72,35 @@ export function parseManifest(json) {
57
72
  const obj = raw;
58
73
  const errors = [];
59
74
  const warnings = [];
60
- // --- mark (required) ---
75
+ // --- spec (required, with legacy `mark` fallback) ---
76
+ const specRaw = obj.spec;
61
77
  const markRaw = obj.mark;
62
- if (typeof markRaw !== 'string') {
63
- errors.push('"mark" is required and must be a string');
78
+ const specPresent = specRaw !== undefined;
79
+ const markPresent = markRaw !== undefined;
80
+ let resolvedSpec;
81
+ if (specPresent) {
82
+ if (!isValidSpecVersion(specRaw)) {
83
+ errors.push(`"spec" must be a semver string (got ${JSON.stringify(specRaw)})`);
84
+ }
85
+ else {
86
+ resolvedSpec = specRaw;
87
+ if (markPresent) {
88
+ warnings.push('"mark" is ignored because "spec" is also present; remove "mark" ' +
89
+ 'from this manifest');
90
+ }
91
+ }
92
+ }
93
+ else if (markPresent) {
94
+ if (!isValidSpecVersion(markRaw)) {
95
+ errors.push(`"mark" must be a semver string (got ${JSON.stringify(markRaw)})`);
96
+ }
97
+ else {
98
+ resolvedSpec = markRaw;
99
+ warnings.push('"mark" is a retired field name; rename it to "spec" in this manifest');
100
+ }
64
101
  }
65
- else if (!SEMVER_RE.test(markRaw)) {
66
- errors.push(`"mark" must be a semver string (got ${JSON.stringify(markRaw)})`);
102
+ else {
103
+ errors.push('"spec" is required and must be a string');
67
104
  }
68
105
  // --- permissions (optional) ---
69
106
  let permissions;
@@ -154,7 +191,11 @@ export function parseManifest(json) {
154
191
  if (errors.length > 0) {
155
192
  return { ok: false, errors };
156
193
  }
157
- const manifest = { ...obj, mark: markRaw };
194
+ const manifest = {
195
+ ...obj,
196
+ spec: resolvedSpec,
197
+ };
198
+ delete manifest.mark;
158
199
  if (permissions !== undefined)
159
200
  manifest.permissions = permissions;
160
201
  if (uses !== undefined)
@@ -165,5 +206,5 @@ export function parseManifest(json) {
165
206
  }
166
207
  /** A minimal, valid manifest for a freshly promoted bundle: no permissions granted, no packs declared. */
167
208
  export function createDefaultManifest(specVersion = CURRENT_SPEC_VERSION) {
168
- return { mark: specVersion };
209
+ return { spec: specVersion };
169
210
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/bundle",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Bundle (.mkz, formerly .mkbundle) storage and policy layer for Markii: manifest handling, the bundle-relative path-jail, zip (fflate) and Node directory storage forms, and a capability-restricted script view. No React, no parsing.",
5
5
  "keywords": [
6
6
  "markdown",