@volter/project 0.5.211 → 0.5.213

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.
Files changed (52) hide show
  1. package/NOTICE +2 -2
  2. package/README.md +8 -15
  3. package/package.json +5 -26
  4. package/schemas/volter-project.schema.json +33 -145
  5. package/schemas/volter-settings.schema.json +11 -49
  6. package/src/adapter/adapter-module.ts +208 -465
  7. package/src/adapter/adapter-surface.ts +4 -32
  8. package/src/adapter/authoring-seam-contract.ts +7 -24
  9. package/src/adapter/authoring.ts +126 -265
  10. package/src/adapter/editor-looks.ts +98 -127
  11. package/src/adapter/finders/registry.ts +6 -21
  12. package/src/adapter/host-context.ts +17 -72
  13. package/src/adapter/index.ts +4 -77
  14. package/src/adapter/manifest-interpreter.ts +7 -50
  15. package/src/adapter/physics-adapter-2d.ts +4 -21
  16. package/src/adapter/root-adapter.ts +37 -111
  17. package/src/adapter/root-seam-contract.ts +3 -6
  18. package/src/adapter/root-stacking.ts +4 -46
  19. package/src/adapter/seam-evidence.ts +0 -16
  20. package/src/adapter/system-adapter.ts +24 -656
  21. package/src/adapter/system-seam-contract.ts +1 -91
  22. package/src/adapter/transform.ts +4 -3
  23. package/src/adapter/workspace-arrangement.ts +33 -49
  24. package/src/manifest/editor-port.ts +10 -36
  25. package/src/manifest/filename.ts +3 -42
  26. package/src/manifest/load-file.ts +9 -15
  27. package/src/manifest/load.ts +44 -179
  28. package/src/manifest/locate.ts +8 -32
  29. package/src/manifest/schema.ts +155 -427
  30. package/src/semver.ts +16 -0
  31. package/src/settings/keys.ts +9 -11
  32. package/src/settings/schema.ts +55 -44
  33. package/schemas/engine-api.json +0 -57
  34. package/schemas/engine-api.md +0 -22
  35. package/src/adapter/binding.ts +0 -445
  36. package/src/adapter/debug-registry-ref.ts +0 -19
  37. package/src/adapter/frame-capture.ts +0 -80
  38. package/src/adapter/ingest/contract-system-slots.ts +0 -28
  39. package/src/adapter/ingest/game-contract-seams.ts +0 -86
  40. package/src/adapter/ingest/game-contract.ts +0 -506
  41. package/src/adapter/ingest/mount-readiness.ts +0 -140
  42. package/src/adapter/loop-gate-report.ts +0 -53
  43. package/src/adapter/native-entry-surface.ts +0 -100
  44. package/src/adapter/render-audio.ts +0 -20
  45. package/src/adapter/render-memory.ts +0 -34
  46. package/src/adapter/renderer-config.ts +0 -84
  47. package/src/adapter/system-slot.ts +0 -301
  48. package/src/core/system-phase.ts +0 -52
  49. package/src/inspection.ts +0 -281
  50. package/src/manifest/configuration-kinds.ts +0 -241
  51. package/src/manifest/kind-modules.ts +0 -58
  52. package/src/manifest/runtime-environment.ts +0 -48
@@ -1,28 +1,10 @@
1
- // The game manifest loader/validator.
2
- //
3
- // Pure data-in/data-out: `loadGameManifest(raw)` parses `raw` against
4
- // `GameManifestSchema`, runs the cross-field checks §3 promises beyond what
5
- // Zod alone can express, resolves every optional/derivable field, and
6
- // returns a `ResolvedGameManifest` with nothing left implicit. This is the
7
- // schema's runtime reader (T4.1 policy) — every described field in
8
- // `schema.ts` either drives a check here or is carried into the resolved
9
- // output explicitly (see the per-field comments below and
10
- // `packages/engine/test/schema-consumption-map.ts`'s `manifest.*` entries).
11
- //
12
- // No `fs` import here on purpose — `load-file.ts` is the thin Node-only
1
+ // Parse and resolve project manifests without filesystem access. load-file.ts supplies the
2
+ // Node path wrapper; this reader validates cross-field constraints and resolves root defaults.
13
3
 
14
- // wrapper that reads a path and calls this function, so browser bundles that
15
- // only need `loadGameManifest` never pull in `node:fs`.
16
-
17
- import {
18
- configurationIssues,
19
- configurationKind,
20
- type DeclaredConfiguration,
21
- } from './configuration-kinds';
22
4
  import {
23
5
  type AdapterRoot,
24
- type GameManifest,
25
- GameManifestSchema,
6
+ type ProjectManifest,
7
+ ProjectManifestSchema,
26
8
  type LearnMetadata,
27
9
  type RootAdapter,
28
10
  } from './schema';
@@ -31,37 +13,28 @@ import {
31
13
  // Resolved shapes
32
14
  // ---------------------------------------------------------------------------
33
15
 
34
- /** D6's resolved adapter identities. */
16
+ /** Resolved adapter identities. */
35
17
  export type ResolvedAdapter =
36
18
  | {
37
19
  readonly type: 'builtin';
38
- readonly identity: 'three' | 'canvas' | 'dom';
39
- readonly surface: 'three' | 'canvas' | 'dom';
20
+ readonly identity: 'stage' | 'canvas' | 'dom';
21
+ readonly surface: 'stage' | 'canvas' | 'dom';
40
22
  }
41
23
  | {
42
24
  readonly type: 'module';
43
25
  readonly identity: 'module';
44
26
  readonly module: string;
45
- readonly surface: 'three' | 'canvas' | 'dom';
27
+ readonly surface: 'stage' | 'canvas' | 'dom';
46
28
  }
47
29
  | {
48
30
  readonly type: 'ingest';
49
- // 'ingest-react' (Track N, N1) is assigned here purely structurally
50
- // (surface === 'dom'), same as 'ingest-three'/'ingest-pixi'.
51
- readonly identity: 'ingest-three' | 'ingest-pixi' | 'ingest-react';
52
- readonly surface: 'three' | 'canvas' | 'dom';
31
+ // Named for the root's declared surface alone.
32
+ readonly identity: `ingest-${'stage' | 'canvas' | 'dom'}`;
33
+ readonly surface: 'stage' | 'canvas' | 'dom';
53
34
  /**
54
- * Project-relative ES module declaring `window.volterGame` beside a
55
- * pristine copy. OPTIONAL rather than required-and-possibly-undefined
56
- * (the shape its siblings above use) because most ingested games have
57
- * nothing to say about it, and requiring the key would only make every
58
- * construction site say `undefined`.
59
- */
60
- readonly contractShim?: string | undefined;
61
- /**
62
- * Project-relative ES module that writes this game'''s own DATA file back
63
- * (see the field'''s schema description). Same OPTIONAL shape and same
64
- * reason as {@link contractShim}: a game whose truth is entirely source
35
+ * Project-relative ES module that writes this project's own DATA file back
36
+ * (see the field's schema description). OPTIONAL rather than
37
+ * required-and-possibly-undefined: a project whose truth is entirely source
65
38
  * declares nothing here.
66
39
  */
67
40
  readonly dataWriter?: string | undefined;
@@ -77,9 +50,9 @@ export interface ResolvedAdapterRoot {
77
50
  readonly adapter: ResolvedAdapter;
78
51
  readonly entry: string | undefined;
79
52
  /**
80
- * The component this root's WORLD is, in the game's own source (see the
53
+ * The component this root's WORLD is, in the project's own source (see the
81
54
  * field's schema description). Present ⇒ Edit mounts THAT component alone as
82
- * its design-time document and Play mounts the game through `entry`;
55
+ * its design-time document and Play mounts the project through `entry`;
83
56
  * absent ⇒ `entry` is what both modes mount.
84
57
  */
85
58
  readonly world?: { readonly entry: string; readonly export: string | undefined } | undefined;
@@ -88,24 +61,15 @@ export interface ResolvedAdapterRoot {
88
61
  readonly loop: 'gated' | 'self-driven';
89
62
  }
90
63
 
91
- export interface ResolvedGameManifest {
92
- readonly manifestVersion: 2;
64
+ export interface ResolvedProjectManifest {
65
+ readonly manifestVersion: number;
93
66
  readonly name: string;
94
67
  readonly appId: string | undefined;
95
68
  readonly version: string;
96
69
  readonly engine: { readonly version: string };
97
- /** Sorted by zOrder; ties broken by original array order (§3). */
70
+ /** Sorted by zOrder; ties broken by original array order. */
98
71
  readonly roots: readonly ResolvedAdapterRoot[];
99
- /** The declared run configurations, validated by kind (`run-kinds.ts`). */
100
- readonly configurations: readonly DeclaredConfiguration[];
101
- /** Ids whose kind no registry here knows — only ever non-empty when the
102
- * caller loaded with `configurationKinds: 'defer'` (a bundled host that
103
- * cannot evaluate a project's kind contributions); the editor session
104
- * is the validator for those. */
105
- readonly deferredConfigurationKinds: readonly string[];
106
72
  readonly resolution: { readonly width: number; readonly height: number } | undefined;
107
- /** Construction-time renderer properties no world can declare for itself — see the schema. */
108
- readonly rendering: { readonly antialias: boolean } | undefined;
109
73
  readonly authoring:
110
74
  | {
111
75
  readonly hierarchy?:
@@ -122,32 +86,7 @@ export interface ResolvedGameManifest {
122
86
  | undefined;
123
87
  }
124
88
  | undefined;
125
- /** D18 — read by the debug-bridge installer to gate `?volter-debug=1` in production builds. */
126
- readonly debug: { readonly allowInProduction: boolean } | undefined;
127
- /** D15 — read by `mount-manifest.ts`'s boot-time seeding reader (gates
128
- * whether `ctx.random` is seeded from `defaultSeed`/`?volter-seed=`), the
129
- * gameplay-rng-ban burn-down scan, and the dev-mode Math.random phase
130
- * trap.. */
131
- readonly determinism:
132
- | { readonly seededRandom: boolean; readonly defaultSeed?: number | undefined }
133
- | undefined;
134
- /** W3d (F11 perf gates) — read by `volter perf --assert-budget`
135
- * (packages/volter-cli/src/perf.ts, assertBudget): each set metric is
136
- * compared against the seeded headless perf run's measured value and any
137
- * exceedance fails the gate with a per-metric table + nonzero exit. */
138
- readonly budget:
139
- | {
140
- readonly frameCpuMsP95?: number | undefined;
141
- readonly physicsMsP95?: number | undefined;
142
- readonly maxEntities?: number | undefined;
143
- readonly maxDrawCalls?: number | undefined;
144
- readonly maxTriangles?: number | undefined;
145
- }
146
- | undefined;
147
- /** Learner-facing metadata on shipped example projects; read by the editor
148
- * server's registry surface (`GET /__editor/examples`,
149
- * packages/editor-core/server/editor-server.ts) to generate the
150
- * gallery/wizard/Learn-catalog registries. */
89
+ /** Learner-facing metadata carried from the project manifest. */
151
90
  readonly learn: LearnMetadata | undefined;
152
91
  }
153
92
 
@@ -164,37 +103,31 @@ function isModuleAdapter(
164
103
  }
165
104
 
166
105
  // ---------------------------------------------------------------------------
167
- // Cross-field checks (§3 — beyond what Zod alone enforces)
106
+ // Cross-field checks beyond individual schema fields
168
107
  // ---------------------------------------------------------------------------
169
108
  //
170
- // Root-id uniqueness used to be a cross-field check HERE
171
- // (`checkUniqueRootIds`, removed) — it moved INTO `GameManifestSchema`'s
172
- // `roots` `superRefine` (schema.ts) instead, so the generated JSON Schema and
173
- // any other schema consumer see the same constraint the loader always
174
- // enforced. `parseManifest` below catches that one recognizable schema-level
175
- // issue and rethrows it as a clean, single `Error` — the exact message shape
176
- // `checkUniqueRootIds` used to throw directly — rather than a raw multi-issue
177
- // `ZodError` dump, so existing callers/tests that pattern-match the message
178
- // text are unaffected by the enforcement moving one layer down.
109
+ // ProjectManifestSchema's `roots` superRefine checks composition rules, including root-id
110
+ // uniqueness. The maintained JSON schema validates field shapes; runtime loading applies these
111
+ // additional rules. loadProjectManifest surfaces composition issues as a single Error.
179
112
 
180
113
  function checkAdapterEntryRules(root: AdapterRoot): void {
181
114
  const { adapter } = root;
182
115
  if (typeof adapter === 'string' && !root.entry) {
183
116
  throw new Error(
184
- `Game manifest: root "${root.id}" uses the '${adapter}' adapter but declares no ` +
117
+ `Project manifest: root "${root.id}" uses the '${adapter}' adapter but declares no ` +
185
118
  '`entry` — a built-in adapter root is authored as an entry module.',
186
119
  );
187
120
  }
188
121
  if (typeof adapter === 'object' && 'ingest' in adapter && !root.entry) {
189
122
  throw new Error(
190
- `Game manifest: root "${root.id}" is an { ingest } root but declares no \`entry\` — the ` +
191
- "game's own entry module is what the host imports.",
123
+ `Project manifest: root "${root.id}" is an { ingest } root but declares no \`entry\` — the ` +
124
+ "project's own entry module is what the host imports.",
192
125
  );
193
126
  }
194
127
  }
195
128
 
196
129
  // ---------------------------------------------------------------------------
197
- // Resolution (§5)
130
+ // Resolution
198
131
  // ---------------------------------------------------------------------------
199
132
 
200
133
  function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
@@ -213,20 +146,13 @@ function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
213
146
  };
214
147
  }
215
148
 
216
- // isIngestAdapter (Track N, N1: 'ingest-react' joins 'ingest-three'/
217
- // 'ingest-pixi').
218
- const identity =
219
- adapter.surface === 'three'
220
- ? 'ingest-three'
221
- : adapter.surface === 'canvas'
222
- ? 'ingest-pixi'
223
- : 'ingest-react';
149
+ // isIngestAdapter: the identity names the root's declared surface.
150
+ const identity = `ingest-${adapter.surface}` as const;
224
151
  const ingest = adapter.ingest;
225
152
  return {
226
153
  type: 'ingest',
227
154
  identity,
228
155
  surface: adapter.surface,
229
- contractShim: ingest.contractShim,
230
156
  dataWriter: ingest.dataWriter,
231
157
  assets: ingest.assets,
232
158
  domStubs: ingest.domStubs,
@@ -237,28 +163,17 @@ function resolveAdapter(root: AdapterRoot): ResolvedAdapter {
237
163
  function checkEngineVersionPin(version: string): void {
238
164
  if (!SEMVER_RE.test(version)) {
239
165
  throw new Error(
240
- `Game manifest: engine.version "${version}" is not a valid exact semver string (e.g. ` +
166
+ `Project manifest: engine.version "${version}" is not a valid exact semver string (e.g. ` +
241
167
  '"0.1.0") — §1.5 requires an exact identity pin, not a range.',
242
168
  );
243
169
  }
244
170
  }
245
171
 
246
172
  /**
247
- * Resolve one already-Zod-validated root entry into its `ResolvedAdapterRoot`
248
- * (adapter identity, and a pass-through of every other field).
249
- * Every `AdapterRoot` field participates here or in a check above:
250
- * `id` drives uniqueness; `adapter` (incl. its surface and
251
- * `module`/`ingest.*` sub-fields) drives adapter identity
252
- * resolution; `entry` drives the entry cross-field rule and is
253
- * carried through; `world` is carried through for the ONE reader that mounts a
254
- * design-time document (the editor's design session) and is deliberately not
255
- * cross-checked against `entry` — the two name different modules on purpose;
256
- * `zOrder` drives the final sort; `pausable`/`loop` are
257
- * carried through as-is (their consumers are the runtime/CLI, T3.2/T3.3);
258
- * `description` (T3.3
259
- * slice 3) is carried through as-is — its consumer today is
260
- * `resolveIngestDescriptor` (packages/editor-game/src/ingest/resolve-three.ts), which
261
- * threads an ingest-three root's description into `IngestGame.description`.
173
+ * Resolve a validated root’s adapter identity and entry rules, carrying its
174
+ * remaining fields into the resolved root. `world` names the design-time module
175
+ * and is independent of the runtime `entry`. The manifest loader checks unique
176
+ * ids and sorts the resolved roots by z-order.
262
177
  */
263
178
  function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
264
179
  checkAdapterEntryRules(root);
@@ -283,43 +198,25 @@ function resolveRoot(root: AdapterRoot): ResolvedAdapterRoot {
283
198
  // ---------------------------------------------------------------------------
284
199
 
285
200
  /**
286
- * Parse + validate + resolve a raw manifest value into a `ResolvedGameManifest`.
201
+ * Parse + validate + resolve a raw manifest value into a `ResolvedProjectManifest`.
287
202
  * Pure data-in/data-out (no `fs`) — see `load-file.ts` for the Node path-based
288
203
  * wrapper. Throws a descriptive `Error` (naming the offending root/field) on
289
204
  * any Zod validation failure or cross-field rule violation.
290
205
  */
291
- export interface LoadGameManifestOptions {
292
- /** `require` (default): a declaration of an unregistered kind refuses.
293
- * `defer`: it is reported in `deferredConfigurationKinds` instead. */
294
- readonly configurationKinds?: 'require' | 'defer';
295
- }
296
-
297
- export function loadGameManifest(
298
- raw: unknown,
299
- options: LoadGameManifestOptions = {},
300
- ): ResolvedGameManifest {
301
- // Removed-format guard FIRST, on the raw value: a root declaring a removed
302
- // content field would otherwise fail Zod's `roots` discriminated union with
303
- // a confusing shape error instead of the named migration message.
304
- const parsed = GameManifestSchema.safeParse(raw);
206
+ export function loadProjectManifest(raw: unknown): ResolvedProjectManifest {
207
+ const parsed = ProjectManifestSchema.safeParse(raw);
305
208
  if (!parsed.success) {
306
- // D-V6: the `roots` `superRefine`'s composition issues — a duplicate root
307
- // id, or more than one world root of the same medium — are the
308
- // schema-level violations this loader surfaces as a clean, single `Error`
309
- // (matching `checkUniqueRootIds`'s pre-D-V6 message shape) rather than a
310
- // raw multi-issue dump, because each one already reads as a finished
311
- // sentence naming the offending ids. Every other schema violation keeps
312
- // propagating as the raw `ZodError` it always has (unchanged behavior,
313
- // e.g. an unrecognized key or a bad `manifestVersion`).
209
+ // Composition issues already name the offending root ids. Surface that
210
+ // message as a single Error; preserve other failures as ZodError details.
314
211
  const compositionIssue = parsed.error.issues.find(
315
212
  (issue) =>
316
- issue.message.startsWith('Game manifest: duplicate root id') ||
317
- issue.message.startsWith('Game manifest: a project has at most one'),
213
+ issue.message.startsWith('Project manifest: duplicate root id') ||
214
+ issue.message.startsWith('Project manifest: a project has at most one'),
318
215
  );
319
216
  if (compositionIssue) throw new Error(compositionIssue.message);
320
217
  throw parsed.error;
321
218
  }
322
- const manifest: GameManifest = parsed.data;
219
+ const manifest: ProjectManifest = parsed.data;
323
220
 
324
221
  checkEngineVersionPin(manifest.engine.version);
325
222
 
@@ -329,37 +226,11 @@ export function loadGameManifest(
329
226
  }));
330
227
 
331
228
  resolvedWithIndex.sort(
332
- (a, b) => a.root.zOrder - b.root.zOrder || a.index - b.index, // ties -> array order (§3)
229
+ (a, b) => a.root.zOrder - b.root.zOrder || a.index - b.index, // ties -> array order
333
230
  );
334
231
 
335
232
  const roots = resolvedWithIndex.map(({ root }) => root);
336
233
 
337
- const configurations = (manifest.configurations ?? []) as DeclaredConfiguration[];
338
- // Kinds are OPEN (ARCHITECTURE-CORE §The project model): each declaration
339
- // is checked against its registered kind's own schema HERE, where the
340
- // host's registry is, not in the Zod schema. A host that cannot evaluate a
341
- // project's kind contributions defers unknown kinds by name instead of
342
- // refusing them.
343
- const deferredConfigurationKinds: string[] = [];
344
- const kindIssues = configurationIssues(configurations).filter((issue) => {
345
- if (options.configurationKinds !== 'defer') return true;
346
- const unknown = /^configurations "([^"]+)": unknown kind/.exec(issue);
347
- if (!unknown) return true;
348
- deferredConfigurationKinds.push(unknown[1] as string);
349
- return false;
350
- });
351
- if (kindIssues.length > 0) {
352
- throw new Error(`Game manifest: ${kindIssues.join('\n ')}`);
353
- }
354
- for (const configuration of configurations) {
355
- if (
356
- !configurationKind(configuration.kind) &&
357
- !deferredConfigurationKinds.includes(configuration.id)
358
- ) {
359
- deferredConfigurationKinds.push(configuration.id);
360
- }
361
- }
362
-
363
234
  return {
364
235
  manifestVersion: manifest.manifestVersion,
365
236
  name: manifest.name,
@@ -367,14 +238,8 @@ export function loadGameManifest(
367
238
  version: manifest.version,
368
239
  engine: { version: manifest.engine.version },
369
240
  roots,
370
- configurations,
371
- deferredConfigurationKinds,
372
241
  resolution: manifest.resolution,
373
- rendering: manifest.rendering,
374
242
  authoring: manifest.authoring,
375
- debug: manifest.debug,
376
- determinism: manifest.determinism,
377
- budget: manifest.budget,
378
243
  learn: manifest.learn,
379
244
  };
380
245
  }
@@ -1,51 +1,27 @@
1
- // Finding the project manifest ON DISK. There is ONE filename — see
2
- // `./filename.ts` for why the old one was removed rather than kept readable.
1
+ // Finding the project manifest ON DISK. There is ONE filename (`./filename.ts`).
3
2
  //
4
3
  // Deliberately dependency-light — `node:fs` + `node:path` + `./filename` and
5
- // NOTHING else (no Zod, no `./load`). Several callers are explicitly
6
- // schema-free by design (`packages/editor-core/server/project-root-surface.ts`,
7
- // `packages/editor/vite-plugin-ui-oid.ts`) and must be able to locate a
8
- // manifest without dragging the validator in.
4
+ // NOTHING else (no Zod, no `./load`). Its callers (the editor's server, the
5
+ // live session and a product's CLI) locate a manifest without dragging the
6
+ // validator in.
9
7
 
10
8
  import { existsSync } from 'node:fs';
11
9
  import { join } from 'node:path';
12
- import {
13
- MANIFEST_FILENAME,
14
- REMOVED_MANIFEST_FILENAME,
15
- removedManifestFilenameMessage,
16
- } from './filename';
10
+ import { MANIFEST_FILENAME } from './filename';
17
11
 
18
12
  /**
19
- * Throw the rename instruction if `dir` carries only the REMOVED manifest
20
- * filename. The `assertNoRemoved*` shape: a removed thing must reject loudly
21
- * naming the exact fix, never be silently ignored (which would present as
22
- * "this folder is not a project" — a diagnosis pointing nowhere).
23
- *
24
- * Returns normally when `dir` holds the current manifest, or neither file.
25
- */
26
- export function assertNoRemovedManifestFilename(dir: string): void {
27
- if (existsSync(join(dir, MANIFEST_FILENAME))) return;
28
- if (!existsSync(join(dir, REMOVED_MANIFEST_FILENAME))) return;
29
- throw new Error(removedManifestFilenameMessage(join(dir, REMOVED_MANIFEST_FILENAME)));
30
- }
31
-
32
- /**
33
- * The manifest path inside `dir`. Always `volter.project.json` — so a "not
34
- * found" message names the filename a project must be using — except that a
35
- * directory carrying only the removed `volter.game.json` THROWS the rename
36
- * instruction instead of reporting a phantom path.
13
+ * The manifest path inside `dir`: always `volter.project.json`, so a "not
14
+ * found" message names the filename a project must be using.
37
15
  *
38
16
  * This is the drop-in replacement for a hand-written `join(dir, …)` everywhere
39
17
  * a manifest is READ. Writers want {@link manifestWritePath}.
40
18
  */
41
19
  export function resolveManifestPath(dir: string): string {
42
- assertNoRemovedManifestFilename(dir);
43
20
  return join(dir, MANIFEST_FILENAME);
44
21
  }
45
22
 
46
- /** True when `dir` holds a manifest. A lone `volter.game.json` throws. */
23
+ /** True when `dir` holds a manifest. */
47
24
  export function hasManifest(dir: string): boolean {
48
- assertNoRemovedManifestFilename(dir);
49
25
  return existsSync(join(dir, MANIFEST_FILENAME));
50
26
  }
51
27