@microsoft/managed-apps-dev-gateway 0.3.1

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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +6 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/resources/DevelopmentMiddleware.d.ts +20 -0
  8. package/dist/resources/DevelopmentMiddleware.d.ts.map +1 -0
  9. package/dist/resources/DevelopmentMiddleware.js +117 -0
  10. package/dist/resources/DevelopmentMiddleware.js.map +1 -0
  11. package/dist/resources/PackagedResources.d.ts +10 -0
  12. package/dist/resources/PackagedResources.d.ts.map +1 -0
  13. package/dist/resources/PackagedResources.js +14 -0
  14. package/dist/resources/PackagedResources.js.map +1 -0
  15. package/dist/resources/appScript.js +69 -0
  16. package/dist/resources/loader.html +98 -0
  17. package/dist/resources/localDevWorker.js +68 -0
  18. package/dist/snapshot/OobRevision.d.ts +38 -0
  19. package/dist/snapshot/OobRevision.d.ts.map +1 -0
  20. package/dist/snapshot/OobRevision.js +40 -0
  21. package/dist/snapshot/OobRevision.js.map +1 -0
  22. package/dist/snapshot/PathPolicy.d.ts +69 -0
  23. package/dist/snapshot/PathPolicy.d.ts.map +1 -0
  24. package/dist/snapshot/PathPolicy.js +129 -0
  25. package/dist/snapshot/PathPolicy.js.map +1 -0
  26. package/dist/snapshot/SnapshotReader.d.ts +63 -0
  27. package/dist/snapshot/SnapshotReader.d.ts.map +1 -0
  28. package/dist/snapshot/SnapshotReader.js +182 -0
  29. package/dist/snapshot/SnapshotReader.js.map +1 -0
  30. package/dist/snapshot/SnapshotSource.d.ts +49 -0
  31. package/dist/snapshot/SnapshotSource.d.ts.map +1 -0
  32. package/dist/snapshot/SnapshotSource.js +81 -0
  33. package/dist/snapshot/SnapshotSource.js.map +1 -0
  34. package/dist/snapshot/index.d.ts +9 -0
  35. package/dist/snapshot/index.d.ts.map +1 -0
  36. package/dist/snapshot/index.js +8 -0
  37. package/dist/snapshot/index.js.map +1 -0
  38. package/package.json +48 -0
@@ -0,0 +1,38 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ /**
5
+ * Deterministic JSON with object keys sorted at every depth. Array order is
6
+ * preserved because it is meaningful in JSON Schema. Used only to derive the
7
+ * effective OOB revision, never to produce the bytes sent to the backend.
8
+ */
9
+ export declare function canonicalJson(value: unknown): string;
10
+ /**
11
+ * Opaque revision for the effective OOB content. It covers exactly what the
12
+ * inherited `validateDevelopmentSnapshotUpdate` treats as OOB-affecting — app
13
+ * identity, the inline manifest, and the effective environment identity — so it
14
+ * changes if and only if one of those changes. Formatting-only, asset-only and
15
+ * connector-only edits leave it unchanged and keep the backend session valid.
16
+ * Key order is insensitive because the digest is taken over canonical JSON.
17
+ * The digest is a local change detector, not a security or integrity claim.
18
+ *
19
+ * Identity is normalized the same way the inherited comparison normalizes it:
20
+ * app IDs are compared case-insensitively, so they are lowercased here, while
21
+ * the environment ID is compared verbatim and is digested verbatim. Digesting
22
+ * the raw body instead would make a case-only app ID edit change the revision
23
+ * without changing effective identity, which trips the inherited "revision
24
+ * changes iff content changes" invariant. The manifest is digested as authored,
25
+ * because its contents are semantic.
26
+ */
27
+ export declare function computeOobRevision(input: {
28
+ environmentId: string;
29
+ body: OobRevisionBody;
30
+ }): string;
31
+ /** The portion of an init body the effective OOB revision is derived from. */
32
+ export interface OobRevisionBody {
33
+ app: {
34
+ id: string;
35
+ manifest: unknown;
36
+ };
37
+ }
38
+ //# sourceMappingURL=OobRevision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OobRevision.d.ts","sourceRoot":"","sources":["../../src/snapshot/OobRevision.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,eAAe,CAAC;CACvB,GAAG,MAAM,CAOT;AAED,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;CACxC"}
@@ -0,0 +1,40 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import { createHash } from 'node:crypto';
5
+ /**
6
+ * Deterministic JSON with object keys sorted at every depth. Array order is
7
+ * preserved because it is meaningful in JSON Schema. Used only to derive the
8
+ * effective OOB revision, never to produce the bytes sent to the backend.
9
+ */
10
+ export function canonicalJson(value) {
11
+ return JSON.stringify(value, (_key, entry) => entry !== null && typeof entry === 'object' && !Array.isArray(entry)
12
+ ? Object.fromEntries(Object.entries(entry).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0))
13
+ : entry);
14
+ }
15
+ /**
16
+ * Opaque revision for the effective OOB content. It covers exactly what the
17
+ * inherited `validateDevelopmentSnapshotUpdate` treats as OOB-affecting — app
18
+ * identity, the inline manifest, and the effective environment identity — so it
19
+ * changes if and only if one of those changes. Formatting-only, asset-only and
20
+ * connector-only edits leave it unchanged and keep the backend session valid.
21
+ * Key order is insensitive because the digest is taken over canonical JSON.
22
+ * The digest is a local change detector, not a security or integrity claim.
23
+ *
24
+ * Identity is normalized the same way the inherited comparison normalizes it:
25
+ * app IDs are compared case-insensitively, so they are lowercased here, while
26
+ * the environment ID is compared verbatim and is digested verbatim. Digesting
27
+ * the raw body instead would make a case-only app ID edit change the revision
28
+ * without changing effective identity, which trips the inherited "revision
29
+ * changes iff content changes" invariant. The manifest is digested as authored,
30
+ * because its contents are semantic.
31
+ */
32
+ export function computeOobRevision(input) {
33
+ const identity = {
34
+ environmentId: input.environmentId,
35
+ appId: input.body.app.id.toLowerCase(),
36
+ manifest: input.body.app.manifest,
37
+ };
38
+ return createHash('sha256').update(canonicalJson(identity), 'utf8').digest('hex').slice(0, 32);
39
+ }
40
+ //# sourceMappingURL=OobRevision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OobRevision.js","sourceRoot":"","sources":["../../src/snapshot/OobRevision.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,KAAc,EAAE,EAAE,CACpD,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAClE,CAAC,CAAC,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAC7C,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACzC,CACF;QACH,CAAC,CAAC,KAAK,CACV,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAGlC;IACC,MAAM,QAAQ,GAAG;QACf,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE;QACtC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ;KAClC,CAAC;IACF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACjG,CAAC"}
@@ -0,0 +1,69 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ /**
5
+ * Filesystem access policy for the local development snapshot reader.
6
+ *
7
+ * The reader only ever opens two kinds of file inside one explicitly supplied
8
+ * project root: the base app config, whose name comes from the shared
9
+ * `BASE_CONFIG_FILE_NAMES` list rather than from a caller, and the schema file
10
+ * named by `data.schemaPath` (or its legacy `db` alias). There is no upward
11
+ * discovery and no second allowed root.
12
+ *
13
+ * The containment rules follow `Pack`'s `loadDataSchemaAsync`
14
+ * (`managed-apps-cli/src/Verbs/App/Lifecycle/Pack.ts`) so a project that packs
15
+ * also reads: non-empty, project-relative, contained after resolution, a regular
16
+ * file, and valid JSON. The reader is **stricter than pack in one way**: the real
17
+ * path of every file it opens — base config as well as schema — must also stay
18
+ * inside the canonicalized root, so a link pointing outside the project is
19
+ * rejected here even though pack would follow it. The root itself is
20
+ * canonicalized first, so explicitly selecting a symlinked project directory
21
+ * still works. This is a deliberate incompatibility: a project that links a
22
+ * schema in from outside its root must copy it in to serve it locally.
23
+ *
24
+ * This is a containment policy, not a defense against concurrent modification of
25
+ * the project directory. Resolution and reading are separate syscalls, so a path
26
+ * replaced between them is not detected; the reader assumes the developer's own
27
+ * working tree and validates the resulting snapshot before it is published.
28
+ *
29
+ * Size is deliberately not pack's 10 MiB: initialization is limited to a 1 MiB
30
+ * whole UTF-8 body by `LOCAL_DEV_INIT_MAX_BODY_BYTES`, enforced through the
31
+ * inherited serializer, so an oversized schema fails here with a local error
32
+ * rather than a backend rejection.
33
+ */
34
+ /** Reader-side failure kinds. Callers map these to their own surface. */
35
+ export declare const SnapshotReadErrorCode: {
36
+ readonly ConfigMissing: "ConfigMissing";
37
+ readonly ConfigPathRejected: "ConfigPathRejected";
38
+ readonly ConfigUnreadable: "ConfigUnreadable";
39
+ readonly ConfigInvalid: "ConfigInvalid";
40
+ readonly SchemaPathRejected: "SchemaPathRejected";
41
+ readonly SchemaUnreadable: "SchemaUnreadable";
42
+ readonly SchemaInvalid: "SchemaInvalid";
43
+ readonly SnapshotInvalid: "SnapshotInvalid";
44
+ };
45
+ export type SnapshotReadErrorCode = (typeof SnapshotReadErrorCode)[keyof typeof SnapshotReadErrorCode];
46
+ /**
47
+ * A read failure that never carries file bytes. The message may name the
48
+ * configured relative path, which the developer wrote, but not absolute
49
+ * locations outside the project root.
50
+ */
51
+ export declare class SnapshotReadError extends Error {
52
+ readonly code: SnapshotReadErrorCode;
53
+ constructor(code: SnapshotReadErrorCode, message: string);
54
+ }
55
+ export declare function isPathMissingError(error: unknown): boolean;
56
+ /** Canonicalize the explicitly selected root so a symlinked root still works. */
57
+ export declare function canonicalizeRootAsync(projectRoot: string): Promise<string>;
58
+ /**
59
+ * Assert that an already-resolved file does not link out of `canonicalRoot`,
60
+ * which must come from {@link canonicalizeRootAsync}.
61
+ */
62
+ export declare function assertRealPathContainedAsync(canonicalRoot: string, resolved: string, rejectedCode: SnapshotReadErrorCode, describe: string): Promise<void>;
63
+ /**
64
+ * Resolve a configured schema path against the canonicalized project root,
65
+ * rejecting anything that is absolute, escapes the root lexically or through a
66
+ * link, or is not a regular file. Returns an absolute path safe to read.
67
+ */
68
+ export declare function resolveSchemaPathAsync(canonicalRoot: string, schemaPath: string, configKey: 'data' | 'db'): Promise<string>;
69
+ //# sourceMappingURL=PathPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PathPolicy.d.ts","sourceRoot":"","sources":["../../src/snapshot/PathPolicy.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,yEAAyE;AACzE,eAAO,MAAM,qBAAqB;;;;;;;;;CASxB,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAC/B,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,OAAO,qBAAqB,CAAC,CAAC;AAErE;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAgB,IAAI,EAAE,qBAAqB,CAAC;gBAEhC,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM;CAKzD;AAYD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,iFAAiF;AACjF,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahF;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,qBAAqB,EACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
@@ -0,0 +1,129 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import { promises as fsp } from 'node:fs';
5
+ import path from 'node:path';
6
+ /**
7
+ * Filesystem access policy for the local development snapshot reader.
8
+ *
9
+ * The reader only ever opens two kinds of file inside one explicitly supplied
10
+ * project root: the base app config, whose name comes from the shared
11
+ * `BASE_CONFIG_FILE_NAMES` list rather than from a caller, and the schema file
12
+ * named by `data.schemaPath` (or its legacy `db` alias). There is no upward
13
+ * discovery and no second allowed root.
14
+ *
15
+ * The containment rules follow `Pack`'s `loadDataSchemaAsync`
16
+ * (`managed-apps-cli/src/Verbs/App/Lifecycle/Pack.ts`) so a project that packs
17
+ * also reads: non-empty, project-relative, contained after resolution, a regular
18
+ * file, and valid JSON. The reader is **stricter than pack in one way**: the real
19
+ * path of every file it opens — base config as well as schema — must also stay
20
+ * inside the canonicalized root, so a link pointing outside the project is
21
+ * rejected here even though pack would follow it. The root itself is
22
+ * canonicalized first, so explicitly selecting a symlinked project directory
23
+ * still works. This is a deliberate incompatibility: a project that links a
24
+ * schema in from outside its root must copy it in to serve it locally.
25
+ *
26
+ * This is a containment policy, not a defense against concurrent modification of
27
+ * the project directory. Resolution and reading are separate syscalls, so a path
28
+ * replaced between them is not detected; the reader assumes the developer's own
29
+ * working tree and validates the resulting snapshot before it is published.
30
+ *
31
+ * Size is deliberately not pack's 10 MiB: initialization is limited to a 1 MiB
32
+ * whole UTF-8 body by `LOCAL_DEV_INIT_MAX_BODY_BYTES`, enforced through the
33
+ * inherited serializer, so an oversized schema fails here with a local error
34
+ * rather than a backend rejection.
35
+ */
36
+ /** Reader-side failure kinds. Callers map these to their own surface. */
37
+ export const SnapshotReadErrorCode = {
38
+ ConfigMissing: 'ConfigMissing',
39
+ ConfigPathRejected: 'ConfigPathRejected',
40
+ ConfigUnreadable: 'ConfigUnreadable',
41
+ ConfigInvalid: 'ConfigInvalid',
42
+ SchemaPathRejected: 'SchemaPathRejected',
43
+ SchemaUnreadable: 'SchemaUnreadable',
44
+ SchemaInvalid: 'SchemaInvalid',
45
+ SnapshotInvalid: 'SnapshotInvalid',
46
+ };
47
+ /**
48
+ * A read failure that never carries file bytes. The message may name the
49
+ * configured relative path, which the developer wrote, but not absolute
50
+ * locations outside the project root.
51
+ */
52
+ export class SnapshotReadError extends Error {
53
+ code;
54
+ constructor(code, message) {
55
+ super(message);
56
+ this.name = 'SnapshotReadError';
57
+ this.code = code;
58
+ }
59
+ }
60
+ function isContained(root, resolved) {
61
+ const relative = path.relative(root, resolved);
62
+ return (relative !== '' &&
63
+ relative !== '..' &&
64
+ !relative.startsWith(`..${path.sep}`) &&
65
+ !path.isAbsolute(relative));
66
+ }
67
+ export function isPathMissingError(error) {
68
+ return error.code === 'ENOENT';
69
+ }
70
+ /** Canonicalize the explicitly selected root so a symlinked root still works. */
71
+ export async function canonicalizeRootAsync(projectRoot) {
72
+ const resolved = path.resolve(projectRoot);
73
+ try {
74
+ return await fsp.realpath(resolved);
75
+ }
76
+ catch (error) {
77
+ if (isPathMissingError(error)) {
78
+ return resolved;
79
+ }
80
+ throw new SnapshotReadError(SnapshotReadErrorCode.ConfigUnreadable, `Unable to resolve the project root: ${error.message}`);
81
+ }
82
+ }
83
+ /**
84
+ * Assert that an already-resolved file does not link out of `canonicalRoot`,
85
+ * which must come from {@link canonicalizeRootAsync}.
86
+ */
87
+ export async function assertRealPathContainedAsync(canonicalRoot, resolved, rejectedCode, describe) {
88
+ let real;
89
+ try {
90
+ real = await fsp.realpath(resolved);
91
+ }
92
+ catch (error) {
93
+ throw new SnapshotReadError(rejectedCode, `Unable to resolve ${describe}: ${error.message}`);
94
+ }
95
+ if (!isContained(canonicalRoot, real)) {
96
+ throw new SnapshotReadError(rejectedCode, `${describe} must not link outside the project root`);
97
+ }
98
+ }
99
+ /**
100
+ * Resolve a configured schema path against the canonicalized project root,
101
+ * rejecting anything that is absolute, escapes the root lexically or through a
102
+ * link, or is not a regular file. Returns an absolute path safe to read.
103
+ */
104
+ export async function resolveSchemaPathAsync(canonicalRoot, schemaPath, configKey) {
105
+ const setting = `${configKey}.schemaPath`;
106
+ if (!schemaPath.trim()) {
107
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaPathRejected, `${setting} must not be empty`);
108
+ }
109
+ if (path.isAbsolute(schemaPath)) {
110
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaPathRejected, `${setting} must be relative to the project root: "${schemaPath}"`);
111
+ }
112
+ const resolved = path.resolve(canonicalRoot, schemaPath);
113
+ if (!isContained(canonicalRoot, resolved)) {
114
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaPathRejected, `${setting} must not resolve outside the project root: "${schemaPath}"`);
115
+ }
116
+ let stat;
117
+ try {
118
+ stat = await fsp.stat(resolved);
119
+ }
120
+ catch (error) {
121
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaUnreadable, `Unable to access ${setting} "${schemaPath}": ${error.message}`);
122
+ }
123
+ if (!stat.isFile()) {
124
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaPathRejected, `${setting} must reference a regular file: "${schemaPath}"`);
125
+ }
126
+ await assertRealPathContainedAsync(canonicalRoot, resolved, SnapshotReadErrorCode.SchemaPathRejected, `${setting} "${schemaPath}"`);
127
+ return resolved;
128
+ }
129
+ //# sourceMappingURL=PathPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PathPolicy.js","sourceRoot":"","sources":["../../src/snapshot/PathPolicy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import type { DevelopmentSnapshot } from '@microsoft/managed-apps-common/gateway';
5
+ /**
6
+ * Filesystem half of `@microsoft/managed-apps-dev-gateway`: it turns the project's
7
+ * on-disk config and schema into one coherent, immutable `DevelopmentSnapshot`.
8
+ *
9
+ * It is a pure reader. There is no HTTP listener, endpoint handler, watcher, Vite
10
+ * integration, browser channel handler or remote authorization here, and it never
11
+ * builds or packs the customer app — `manifest.json` is not required on disk.
12
+ * A snapshot is configuration, never proof of authorization.
13
+ *
14
+ * **Exposure boundary.** The reader reads only the validated `ConfigSchemaV0`
15
+ * fields of the project's base config plus the JSON schema file that config
16
+ * points at. It reads no credentials, tokens or other files, and adds no fields
17
+ * of its own. That is a bound on what it collects, not a claim that the result is
18
+ * harmless: the inline schema is data the legacy config endpoint never carried,
19
+ * and an app config can hold user-authored content the developer considers
20
+ * sensitive. Whoever exposes a snapshot owns that decision; the HTTP exposure
21
+ * policy is not settled here.
22
+ */
23
+ /** Reader inputs. The root is explicit; the reader never discovers one itself. */
24
+ export interface ReadDevelopmentSnapshotOptions {
25
+ /** Absolute path of the project directory whose config may be served. */
26
+ projectRoot: string;
27
+ /**
28
+ * Combined snapshot revision for this read, owned by the caller because it is
29
+ * scoped to a bootstrap lifetime the reader does not observe. Must increase
30
+ * within one bootstrap; a restarted producer starts a new bootstrap instead.
31
+ */
32
+ revision: number;
33
+ }
34
+ /** Successful read plus the exact init bytes retained for retries. */
35
+ export interface DevelopmentSnapshotRead {
36
+ snapshot: DevelopmentSnapshot;
37
+ /**
38
+ * The serialized `InitDevSessionBody` that was validated against the 1 MiB
39
+ * limit. Retain these bytes with the candidate GUID so a retry replays the
40
+ * identical request rather than re-serializing.
41
+ */
42
+ initBody: string;
43
+ }
44
+ /**
45
+ * Freeze a JSON value in place at every depth. `Object.freeze` is shallow, so a
46
+ * shallowly frozen snapshot still lets a caller mutate `hostConfig.appId`,
47
+ * `oob.environmentId` or `oob.body.app.manifest.data`. Because the source stores
48
+ * and hands back the same object, such a mutation would corrupt retained state
49
+ * and desynchronize the snapshot from its already-serialized init body.
50
+ *
51
+ * The value is known-acyclic here: it comes from `JSON.parse` and object
52
+ * literals, then through a Zod parse, so no cycle tracking is needed.
53
+ */
54
+ export declare function deepFreezeJson<T>(value: T): T;
55
+ /**
56
+ * Produce one coherent snapshot from the project's current files. Both portions
57
+ * derive from a single config read, so the host config and the OOB body can never
58
+ * describe different apps. The OOB body carries app identity and the inline
59
+ * manifest only: connection references, connections and deployment fields stay in
60
+ * the host config and are never sent to initialization.
61
+ */
62
+ export declare function readDevelopmentSnapshotAsync(options: ReadDevelopmentSnapshotOptions): Promise<DevelopmentSnapshotRead>;
63
+ //# sourceMappingURL=SnapshotReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SnapshotReader.d.ts","sourceRoot":"","sources":["../../src/snapshot/SnapshotReader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAkBlF;;;;;;;;;;;;;;;;;GAiBG;AAEH,kFAAkF;AAClF,MAAM,WAAW,8BAA8B;IAC7C,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoKD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAI7C;AAED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,uBAAuB,CAAC,CAwDlC"}
@@ -0,0 +1,182 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import { promises as fsp } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { BASE_CONFIG_FILE_NAMES, ConfigSchemaV0 } from '@microsoft/managed-apps-common/config';
7
+ import { DevelopmentSnapshotSchema, LOCAL_DEV_INIT_MAX_BODY_BYTES, LOCAL_DEV_OOB_MANIFEST_VERSION, serializeInitDevSessionBody, } from '@microsoft/managed-apps-common/gateway';
8
+ import { computeOobRevision } from './OobRevision.js';
9
+ import { assertRealPathContainedAsync, canonicalizeRootAsync, isPathMissingError, resolveSchemaPathAsync, SnapshotReadError, SnapshotReadErrorCode, } from './PathPolicy.js';
10
+ const CONFIG_KEYS = ['data', 'db'];
11
+ const CONFIG_MAX_FILE_BYTES = 2 * 1024 * 1024;
12
+ async function readBoundedUtf8FileAsync(options) {
13
+ let stat;
14
+ try {
15
+ stat = await fsp.stat(options.filePath);
16
+ }
17
+ catch (error) {
18
+ throw new SnapshotReadError(options.unreadableCode, `Unable to access ${options.displayName}: ${error.message}`);
19
+ }
20
+ if (stat.size > options.maxBytes) {
21
+ throw new SnapshotReadError(options.invalidCode, `${options.displayName} exceeds the ${options.maxBytes}-byte file limit`);
22
+ }
23
+ let content;
24
+ try {
25
+ content = await fsp.readFile(options.filePath, 'utf-8');
26
+ }
27
+ catch (error) {
28
+ throw new SnapshotReadError(options.unreadableCode, `Unable to read ${options.displayName}: ${error.message}`);
29
+ }
30
+ if (Buffer.byteLength(content, 'utf-8') > options.maxBytes) {
31
+ throw new SnapshotReadError(options.invalidCode, `${options.displayName} exceeds the ${options.maxBytes}-byte file limit`);
32
+ }
33
+ return content;
34
+ }
35
+ async function readBaseConfigAsync(canonicalRoot) {
36
+ const attempted = [];
37
+ for (const fileName of BASE_CONFIG_FILE_NAMES) {
38
+ attempted.push(fileName);
39
+ const filePath = path.join(canonicalRoot, fileName);
40
+ let stat;
41
+ try {
42
+ stat = await fsp.stat(filePath);
43
+ }
44
+ catch (error) {
45
+ if (isPathMissingError(error)) {
46
+ continue;
47
+ }
48
+ throw new SnapshotReadError(SnapshotReadErrorCode.ConfigUnreadable, `Unable to access ${fileName}: ${error.message}`);
49
+ }
50
+ if (!stat?.isFile()) {
51
+ continue;
52
+ }
53
+ await assertRealPathContainedAsync(canonicalRoot, filePath, SnapshotReadErrorCode.ConfigPathRejected, fileName);
54
+ const content = await readBoundedUtf8FileAsync({
55
+ filePath,
56
+ displayName: fileName,
57
+ maxBytes: CONFIG_MAX_FILE_BYTES,
58
+ unreadableCode: SnapshotReadErrorCode.ConfigUnreadable,
59
+ invalidCode: SnapshotReadErrorCode.ConfigInvalid,
60
+ });
61
+ return { fileName, content };
62
+ }
63
+ throw new SnapshotReadError(SnapshotReadErrorCode.ConfigMissing, `No app config found in the project root. Expected one of: ${attempted.join(', ')}`);
64
+ }
65
+ function parseConfig(fileName, content) {
66
+ let parsed;
67
+ try {
68
+ parsed = JSON.parse(content);
69
+ }
70
+ catch (error) {
71
+ throw new SnapshotReadError(SnapshotReadErrorCode.ConfigInvalid, `${fileName} must contain valid JSON: ${error.message}`);
72
+ }
73
+ const result = ConfigSchemaV0.safeParse(parsed);
74
+ if (!result.success) {
75
+ const issues = result.error.issues
76
+ .map((issue) => `${issue.path.length > 0 ? issue.path.join('.') : '(root)'}: ${issue.message}`)
77
+ .join('\n');
78
+ throw new SnapshotReadError(SnapshotReadErrorCode.ConfigInvalid, `${fileName} is not a valid app config:\n${issues}`);
79
+ }
80
+ return result.data;
81
+ }
82
+ /**
83
+ * Read the configured schema, if any. Returns `undefined` only when no schema is
84
+ * configured at all; a configured-but-broken schema is an error, never an empty
85
+ * manifest, so invalid input can never silently replace a valid snapshot.
86
+ */
87
+ async function readInlineSchemaAsync(canonicalRoot, appConfig) {
88
+ const configKey = CONFIG_KEYS.find((key) => appConfig[key] !== undefined);
89
+ if (configKey === undefined) {
90
+ return undefined;
91
+ }
92
+ const schemaPath = appConfig[configKey].schemaPath;
93
+ const resolved = await resolveSchemaPathAsync(canonicalRoot, schemaPath, configKey);
94
+ const content = await readBoundedUtf8FileAsync({
95
+ filePath: resolved,
96
+ displayName: `${configKey}.schemaPath "${schemaPath}"`,
97
+ maxBytes: LOCAL_DEV_INIT_MAX_BODY_BYTES,
98
+ unreadableCode: SnapshotReadErrorCode.SchemaUnreadable,
99
+ invalidCode: SnapshotReadErrorCode.SchemaInvalid,
100
+ });
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(content);
104
+ }
105
+ catch (error) {
106
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaInvalid, `${configKey}.schemaPath "${schemaPath}" must contain valid JSON: ${error.message}`);
107
+ }
108
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
109
+ throw new SnapshotReadError(SnapshotReadErrorCode.SchemaInvalid, `${configKey}.schemaPath "${schemaPath}" must contain a JSON object`);
110
+ }
111
+ return parsed;
112
+ }
113
+ /**
114
+ * Freeze a JSON value in place at every depth. `Object.freeze` is shallow, so a
115
+ * shallowly frozen snapshot still lets a caller mutate `hostConfig.appId`,
116
+ * `oob.environmentId` or `oob.body.app.manifest.data`. Because the source stores
117
+ * and hands back the same object, such a mutation would corrupt retained state
118
+ * and desynchronize the snapshot from its already-serialized init body.
119
+ *
120
+ * The value is known-acyclic here: it comes from `JSON.parse` and object
121
+ * literals, then through a Zod parse, so no cycle tracking is needed.
122
+ */
123
+ export function deepFreezeJson(value) {
124
+ if (value === null || typeof value !== 'object' || Object.isFrozen(value))
125
+ return value;
126
+ for (const entry of Object.values(value))
127
+ deepFreezeJson(entry);
128
+ return Object.freeze(value);
129
+ }
130
+ /**
131
+ * Produce one coherent snapshot from the project's current files. Both portions
132
+ * derive from a single config read, so the host config and the OOB body can never
133
+ * describe different apps. The OOB body carries app identity and the inline
134
+ * manifest only: connection references, connections and deployment fields stay in
135
+ * the host config and are never sent to initialization.
136
+ */
137
+ export async function readDevelopmentSnapshotAsync(options) {
138
+ const canonicalRoot = await canonicalizeRootAsync(options.projectRoot);
139
+ const { fileName, content } = await readBaseConfigAsync(canonicalRoot);
140
+ const hostConfig = parseConfig(fileName, content);
141
+ const schema = await readInlineSchemaAsync(canonicalRoot, hostConfig);
142
+ const dataEnabled = hostConfig.data?.enabled ?? hostConfig.db?.enabled;
143
+ const body = {
144
+ app: {
145
+ id: hostConfig.appId ?? '',
146
+ manifest: {
147
+ manifestVersion: LOCAL_DEV_OOB_MANIFEST_VERSION,
148
+ ...(dataEnabled !== undefined ? { dataEnabled } : {}),
149
+ ...(schema !== undefined ? { data: schema } : {}),
150
+ },
151
+ },
152
+ };
153
+ const environmentId = hostConfig.environmentId ?? '';
154
+ let initBody;
155
+ try {
156
+ // Also enforces the 1 MiB whole-body limit before anything is published.
157
+ initBody = serializeInitDevSessionBody(body);
158
+ }
159
+ catch (error) {
160
+ throw new SnapshotReadError(SnapshotReadErrorCode.SnapshotInvalid, `${fileName} does not describe a usable development session: ${error.message}`);
161
+ }
162
+ const candidate = {
163
+ version: 1,
164
+ revision: options.revision,
165
+ hostConfig,
166
+ oob: {
167
+ version: 1,
168
+ revision: computeOobRevision({ environmentId, body }),
169
+ environmentId,
170
+ body,
171
+ },
172
+ };
173
+ const validated = DevelopmentSnapshotSchema.safeParse(candidate);
174
+ if (!validated.success) {
175
+ const issues = validated.error.issues
176
+ .map((issue) => `${issue.path.length > 0 ? issue.path.join('.') : '(root)'}: ${issue.message}`)
177
+ .join('\n');
178
+ throw new SnapshotReadError(SnapshotReadErrorCode.SnapshotInvalid, `${fileName} does not describe a usable development session:\n${issues}`);
179
+ }
180
+ return { snapshot: deepFreezeJson(validated.data), initBody };
181
+ }
182
+ //# sourceMappingURL=SnapshotReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SnapshotReader.js","sourceRoot":"","sources":["../../src/snapshot/SnapshotReader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import type { DevelopmentSnapshot } from '@microsoft/managed-apps-common/gateway';
5
+ import { validateDevelopmentSnapshotUpdate } from '@microsoft/managed-apps-common/gateway';
6
+ import type { DevelopmentSnapshotRead } from './SnapshotReader.js';
7
+ /**
8
+ * Holds the last snapshot that read and validated cleanly, and mints the combined
9
+ * revision for one bootstrap lifetime.
10
+ *
11
+ * A failed refresh leaves the previous snapshot installed and rethrows, so invalid
12
+ * input can never downgrade a working session to an empty manifest. This is state
13
+ * for a caller that decides *when* to refresh; it is not a watcher, debouncer or
14
+ * session coordinator, and it performs no HTTP work.
15
+ */
16
+ export declare class DevelopmentSnapshotSource {
17
+ private readonly projectRoot;
18
+ private revision;
19
+ private current;
20
+ /**
21
+ * Tail of the serialized refresh queue. Refreshes must not interleave: the
22
+ * combined revision is allocated from `this.revision`, and an `await` between
23
+ * allocation and installation would let two overlapping refreshes both mint
24
+ * `N + 1` and hand back distinct snapshots carrying the same revision. Each
25
+ * caller installs a fulfilled gate before awaiting the previous one, preserving
26
+ * FIFO order without letting a failed refresh poison the callers behind it.
27
+ */
28
+ private pending;
29
+ constructor(projectRoot: string);
30
+ /** The last successful read, or `undefined` before the first one. */
31
+ get snapshot(): DevelopmentSnapshot | undefined;
32
+ /** Init bytes matching {@link snapshot}, retained for exact-body retries. */
33
+ get initBody(): string | undefined;
34
+ /**
35
+ * Re-read the project. On success the new snapshot is installed and classified
36
+ * relative to the previous one, so a caller can tell a connector-only metadata
37
+ * update from an OOB or identity change. On failure the previous snapshot stays
38
+ * installed and the error propagates.
39
+ *
40
+ * Concurrent calls are serialized, not coalesced: every call performs its own
41
+ * read, in call order, and observes the state left by the one before it.
42
+ */
43
+ refreshAsync(): Promise<{
44
+ read: DevelopmentSnapshotRead;
45
+ change: ReturnType<typeof validateDevelopmentSnapshotUpdate>['change'];
46
+ }>;
47
+ private readAndInstallAsync;
48
+ }
49
+ //# sourceMappingURL=SnapshotSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SnapshotSource.d.ts","sourceRoot":"","sources":["../../src/snapshot/SnapshotSource.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAClF,OAAO,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAC;AAE3F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;;;;GAQG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,OAAO,CAAsC;IACrD;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,CAAoC;gBAEvC,WAAW,EAAE,MAAM;IAI/B,qEAAqE;IACrE,IAAW,QAAQ,IAAI,mBAAmB,GAAG,SAAS,CAErD;IAED,6EAA6E;IAC7E,IAAW,QAAQ,IAAI,MAAM,GAAG,SAAS,CAExC;IAED;;;;;;;;OAQG;IACU,YAAY,IAAI,OAAO,CAAC;QACnC,IAAI,EAAE,uBAAuB,CAAC;QAC9B,MAAM,EAAE,UAAU,CAAC,OAAO,iCAAiC,CAAC,CAAC,QAAQ,CAAC,CAAC;KACxE,CAAC;YAeY,mBAAmB;CAyBlC"}