@pwa-platform/core 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/compile.d.ts +8 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +170 -0
- package/dist/compile.js.map +1 -0
- package/dist/host-output.d.ts +13 -0
- package/dist/host-output.d.ts.map +1 -0
- package/dist/host-output.js +96 -0
- package/dist/host-output.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/input.d.ts +25 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +2 -0
- package/dist/input.js.map +1 -0
- package/dist/internal/diagnostics.d.ts +10 -0
- package/dist/internal/diagnostics.d.ts.map +1 -0
- package/dist/internal/diagnostics.js +18 -0
- package/dist/internal/diagnostics.js.map +1 -0
- package/dist/internal/order.d.ts +3 -0
- package/dist/internal/order.d.ts.map +1 -0
- package/dist/internal/order.js +13 -0
- package/dist/internal/order.js.map +1 -0
- package/dist/internal/path-key.d.ts +10 -0
- package/dist/internal/path-key.d.ts.map +1 -0
- package/dist/internal/path-key.js +37 -0
- package/dist/internal/path-key.js.map +1 -0
- package/dist/internal/paths.d.ts +5 -0
- package/dist/internal/paths.d.ts.map +1 -0
- package/dist/internal/paths.js +17 -0
- package/dist/internal/paths.js.map +1 -0
- package/dist/internal/shape.d.ts +5 -0
- package/dist/internal/shape.d.ts.map +1 -0
- package/dist/internal/shape.js +17 -0
- package/dist/internal/shape.js.map +1 -0
- package/dist/precache.d.ts +20 -0
- package/dist/precache.d.ts.map +1 -0
- package/dist/precache.js +96 -0
- package/dist/precache.js.map +1 -0
- package/dist/rules.d.ts +13 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +51 -0
- package/dist/rules.js.map +1 -0
- package/dist/topology.d.ts +25 -0
- package/dist/topology.d.ts.map +1 -0
- package/dist/topology.js +74 -0
- package/dist/topology.js.map +1 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PWA Platform contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @pwa-platform/core
|
|
2
|
+
|
|
3
|
+
Compile PWA Platform policies into deployment plans.
|
|
4
|
+
|
|
5
|
+
Policy compiler used by the build integration. Applications should configure the host adapter instead of calling this package directly.
|
|
6
|
+
|
|
7
|
+
This is a `0.1.0` prerelease. It provides online use, installation integration, static precaching, a safe offline fallback, and controlled updates. It does not enable runtime business API caching, private data caching, automatic write replay, or Push for applications. Production deployment requires the application and infrastructure checks described in the PWA Platform release runbook.
|
|
8
|
+
|
|
9
|
+
License: MIT.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PwaPlan, PwaValidationResult } from "@pwa-platform/contracts";
|
|
2
|
+
import type { PwaCompileInput } from "./input.js";
|
|
3
|
+
/**
|
|
4
|
+
* Compiles platform identity, install metadata, policy, topology and host build output into a
|
|
5
|
+
* `PwaPlan`. Never throws; a successful plan always passes contracts `validatePlan`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function compilePlan(input: PwaCompileInput): PwaValidationResult<PwaPlan>;
|
|
8
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAIV,OAAO,EAEP,mBAAmB,EAEpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAalD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAOhF"}
|
package/dist/compile.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { REQUEST_BASELINE_DENIALS, cacheNamespacePrefix, validateIdentity, validateInstallMetadata, validatePlan, validatePolicy, } from "@pwa-platform/contracts";
|
|
2
|
+
import { checkHostOutput } from "./host-output.js";
|
|
3
|
+
import { diagnostic, withinField } from "./internal/diagnostics.js";
|
|
4
|
+
import { isPlainRecord } from "./internal/shape.js";
|
|
5
|
+
import { compilePrecache } from "./precache.js";
|
|
6
|
+
import { compilePathRules } from "./rules.js";
|
|
7
|
+
import { decodedPathKey, isWithinKey } from "./internal/path-key.js";
|
|
8
|
+
import { resolvePrefix } from "./rules.js";
|
|
9
|
+
import { installStartUrlInChildScope, policyRulesInChildScope, resolveTopology } from "./topology.js";
|
|
10
|
+
const INPUT_FIELDS = ["identity", "install", "policy", "topology", "hostBuildOutput"];
|
|
11
|
+
/**
|
|
12
|
+
* Compiles platform identity, install metadata, policy, topology and host build output into a
|
|
13
|
+
* `PwaPlan`. Never throws; a successful plan always passes contracts `validatePlan`.
|
|
14
|
+
*/
|
|
15
|
+
export function compilePlan(input) {
|
|
16
|
+
try {
|
|
17
|
+
return compile(input);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Hostile inputs (revoked proxies, throwing traps, extreme nesting) must still produce a result.
|
|
21
|
+
return failure([diagnostic("value.not-serializable", [])]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Reads each input field exactly once through its property descriptor, so accessors never run. */
|
|
25
|
+
function readInput(input) {
|
|
26
|
+
if (!isPlainRecord(input))
|
|
27
|
+
return [diagnostic("schema.invalid-type", [])];
|
|
28
|
+
const findings = [];
|
|
29
|
+
if (Reflect.ownKeys(input).some((key) => !INPUT_FIELDS.includes(key))) {
|
|
30
|
+
findings.push(diagnostic("schema.unknown-field", []));
|
|
31
|
+
}
|
|
32
|
+
const fields = {};
|
|
33
|
+
for (const field of INPUT_FIELDS) {
|
|
34
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, field);
|
|
35
|
+
if (descriptor === undefined) {
|
|
36
|
+
findings.push(diagnostic("schema.missing-field", [field]));
|
|
37
|
+
}
|
|
38
|
+
else if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
39
|
+
findings.push(diagnostic("value.not-serializable", [field]));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
fields[field] = descriptor.value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return findings.length > 0 ? findings : fields;
|
|
46
|
+
}
|
|
47
|
+
function compile(input) {
|
|
48
|
+
const fields = readInput(input);
|
|
49
|
+
if (Array.isArray(fields))
|
|
50
|
+
return failure(fields);
|
|
51
|
+
const errors = [];
|
|
52
|
+
const warnings = [];
|
|
53
|
+
const identityResult = validateIdentity(fields.identity);
|
|
54
|
+
if (!identityResult.ok)
|
|
55
|
+
errors.push(...identityResult.diagnostics.map((finding) => withinField("identity", finding)));
|
|
56
|
+
const identity = identityResult.ok ? identityResult.value : undefined;
|
|
57
|
+
const policyResult = validatePolicy(fields.policy);
|
|
58
|
+
if (!policyResult.ok)
|
|
59
|
+
errors.push(...policyResult.diagnostics.map((finding) => withinField("policy", finding)));
|
|
60
|
+
const policy = policyResult.ok ? policyResult.value : undefined;
|
|
61
|
+
// v2 policy validation lands before its plan and worker contract. Never discard an accepted offline-write
|
|
62
|
+
// declaration and emit a v1 plan: until the coordinated v2 compiler slice exists, reject it explicitly.
|
|
63
|
+
const installEnabled = policy?.install.enabled === true;
|
|
64
|
+
let install = null;
|
|
65
|
+
if (fields.install === null) {
|
|
66
|
+
if (installEnabled)
|
|
67
|
+
errors.push(diagnostic("compile.install-metadata-missing", ["install"]));
|
|
68
|
+
}
|
|
69
|
+
else if (identity !== undefined) {
|
|
70
|
+
const installResult = validateInstallMetadata(fields.install, identity);
|
|
71
|
+
if (installResult.ok) {
|
|
72
|
+
install = installResult.value;
|
|
73
|
+
// Metadata for a disabled install is still validated, but its warnings do not reach the plan.
|
|
74
|
+
if (installEnabled)
|
|
75
|
+
warnings.push(...installResult.diagnostics.map((finding) => withinField("install", finding)));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
errors.push(...installResult.diagnostics.map((finding) => withinField("install", finding)));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const topologyResolution = resolveTopology(fields.topology, identity);
|
|
82
|
+
errors.push(...topologyResolution.findings);
|
|
83
|
+
const resolvedTopology = topologyResolution.resolved;
|
|
84
|
+
if (identity !== undefined && policy !== undefined && resolvedTopology !== undefined && resolvedTopology.childPrefixKeys.length > 0) {
|
|
85
|
+
errors.push(...policyRulesInChildScope(policy, identity.mountPath, resolvedTopology.childPrefixKeys));
|
|
86
|
+
// Install metadata for a disabled install still reaches the plan as `null`, so only the value that will
|
|
87
|
+
// actually be published needs checking (mirrors contracts' plan-level `plan.start-url-in-child-scope`).
|
|
88
|
+
if (installEnabled && install !== null) {
|
|
89
|
+
errors.push(...installStartUrlInChildScope(install, resolvedTopology.childPrefixKeys));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const host = checkHostOutput(fields.hostBuildOutput, identity);
|
|
93
|
+
errors.push(...host.findings);
|
|
94
|
+
const rules = identity !== undefined && policy !== undefined ? compilePathRules(policy, identity.mountPath) : undefined;
|
|
95
|
+
if (rules !== undefined)
|
|
96
|
+
errors.push(...rules.findings);
|
|
97
|
+
if (identity !== undefined && policy?.schemaVersion === 2 && rules !== undefined) {
|
|
98
|
+
for (const [index, target] of policy.offlineWrites.targets.entries()) {
|
|
99
|
+
const key = decodedPathKey(resolvePrefix(identity.mountPath, target.pathPrefix));
|
|
100
|
+
const matched = rules.pathRules.find((rule) => isWithinKey(key, decodedPathKey(rule.pathPrefix)));
|
|
101
|
+
if (matched?.resourceClass !== "mutation") {
|
|
102
|
+
errors.push(diagnostic("compile.offline-write-target-invalid", ["policy", "offlineWrites", "targets", index, "pathPrefix"]));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Precache selection needs conflict-free rules and a valid build output manifest.
|
|
107
|
+
const precache = identity !== undefined && policy !== undefined && rules !== undefined && rules.findings.length === 0 && host.output !== undefined
|
|
108
|
+
? compilePrecache(policy, identity.mountPath, host.output, rules, resolvedTopology?.childPrefixKeys ?? [])
|
|
109
|
+
: undefined;
|
|
110
|
+
if (precache !== undefined) {
|
|
111
|
+
errors.push(...precache.errors);
|
|
112
|
+
warnings.push(...precache.warnings);
|
|
113
|
+
}
|
|
114
|
+
if (errors.length > 0 ||
|
|
115
|
+
identity === undefined ||
|
|
116
|
+
policy === undefined ||
|
|
117
|
+
rules === undefined ||
|
|
118
|
+
host.output === undefined ||
|
|
119
|
+
precache === undefined ||
|
|
120
|
+
resolvedTopology === undefined) {
|
|
121
|
+
return failure(errors);
|
|
122
|
+
}
|
|
123
|
+
const planFields = {
|
|
124
|
+
identity,
|
|
125
|
+
install: installEnabled ? install : null,
|
|
126
|
+
hostBuildOutput: { publicPath: host.output.publicPath },
|
|
127
|
+
topology: resolvedTopology.value,
|
|
128
|
+
artifacts: { serviceWorkerFile: host.output.serviceWorkerFile, manifestFile: host.output.manifestFile },
|
|
129
|
+
precache: precache.precache,
|
|
130
|
+
cacheNamespace: { prefix: cacheNamespacePrefix(identity) },
|
|
131
|
+
requestBaselineDenials: [...REQUEST_BASELINE_DENIALS],
|
|
132
|
+
pathRules: [...resolvedTopology.excludeRules, ...rules.pathRules],
|
|
133
|
+
offlineFallback: precache.offlineFallback,
|
|
134
|
+
updateMode: policy.updateMode,
|
|
135
|
+
diagnostics: warnings,
|
|
136
|
+
};
|
|
137
|
+
const plan = policy.schemaVersion === 2
|
|
138
|
+
? {
|
|
139
|
+
...planFields,
|
|
140
|
+
schemaVersion: 2,
|
|
141
|
+
planVersion: 2,
|
|
142
|
+
policyVersion: 2,
|
|
143
|
+
offlineWrites: compileOfflineWrites(policy.offlineWrites, identity),
|
|
144
|
+
}
|
|
145
|
+
: { ...planFields, schemaVersion: 1, planVersion: 1, policyVersion: 1 };
|
|
146
|
+
// Guarantees the documented contract: a successful compilation always passes validatePlan.
|
|
147
|
+
const checked = validatePlan(plan);
|
|
148
|
+
if (!checked.ok)
|
|
149
|
+
return checked;
|
|
150
|
+
return { ok: true, value: checked.value, diagnostics: warnings };
|
|
151
|
+
}
|
|
152
|
+
function compileOfflineWrites(policy, identity) {
|
|
153
|
+
if (!policy.enabled)
|
|
154
|
+
return { enabled: false };
|
|
155
|
+
const resolve = (prefix) => (identity.mountPath === "/" ? prefix : prefix === "/" ? identity.mountPath : `${identity.mountPath}${prefix}`);
|
|
156
|
+
return {
|
|
157
|
+
enabled: true,
|
|
158
|
+
databaseName: `pwa-offline-write:${encodeURIComponent(identity.appId)}:${encodeURIComponent(identity.environment)}:${encodeURIComponent(identity.cacheNamespaceSeed)}`,
|
|
159
|
+
maxEntries: policy.maxEntries,
|
|
160
|
+
maxTotalBodyBytes: policy.maxTotalBodyBytes,
|
|
161
|
+
targets: policy.targets.map((target) => ({ id: target.id, pathPrefix: resolve(target.pathPrefix), maxBodyBytes: target.maxBodyBytes })),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function failure(diagnostics) {
|
|
165
|
+
const [first, ...rest] = diagnostics;
|
|
166
|
+
if (!first)
|
|
167
|
+
throw new Error("A failed compilation must carry at least one diagnostic.");
|
|
168
|
+
return { ok: false, diagnostics: [first, ...rest] };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PwaDiagnostic, PwaIdentity } from "@pwa-platform/contracts";
|
|
2
|
+
import type { PwaCompileHostOutput } from "./input.js";
|
|
3
|
+
export type CheckedHostOutput = {
|
|
4
|
+
readonly findings: PwaDiagnostic[];
|
|
5
|
+
/** A fresh copy of the manifest, present only when `findings` is empty. */
|
|
6
|
+
readonly output: PwaCompileHostOutput | undefined;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Validates the host build output manifest against the identity (when it is valid) and returns a
|
|
10
|
+
* snapshot, so later compilation steps never read the caller's objects again.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkHostOutput(value: unknown, identity: Pick<PwaIdentity, "scope" | "serviceWorkerUrl" | "manifestUrl"> | undefined): CheckedHostOutput;
|
|
13
|
+
//# sourceMappingURL=host-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-output.d.ts","sourceRoot":"","sources":["../src/host-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,aAAa,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACxF,OAAO,KAAK,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAYzE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC;IACnC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACnD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,GAAG,aAAa,CAAC,GAAG,SAAS,GACpF,iBAAiB,CAyEnB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { diagnostic } from "./internal/diagnostics.js";
|
|
2
|
+
import { decodedPathKey } from "./internal/path-key.js";
|
|
3
|
+
import { isCanonicalPath, isRelativeFilePath } from "./internal/paths.js";
|
|
4
|
+
import { hasExactKeys } from "./internal/shape.js";
|
|
5
|
+
const HOST_FIELDS = ["publicPath", "serviceWorkerFile", "manifestFile", "files"];
|
|
6
|
+
const FILE_FIELDS = ["path", "fingerprinted", "contentHash"];
|
|
7
|
+
const CONTENT_HASH = /^[A-Za-z0-9_-]{8,128}$/;
|
|
8
|
+
const ACCESSOR = Symbol("accessor");
|
|
9
|
+
/**
|
|
10
|
+
* Validates the host build output manifest against the identity (when it is valid) and returns a
|
|
11
|
+
* snapshot, so later compilation steps never read the caller's objects again.
|
|
12
|
+
*/
|
|
13
|
+
export function checkHostOutput(value, identity) {
|
|
14
|
+
const invalid = (...segments) => diagnostic("compile.invalid-host-output", ["hostBuildOutput", ...segments]);
|
|
15
|
+
if (!hasExactKeys(value, HOST_FIELDS))
|
|
16
|
+
return { findings: [invalid()], output: undefined };
|
|
17
|
+
const findings = [];
|
|
18
|
+
const { publicPath, serviceWorkerFile, manifestFile, files } = value;
|
|
19
|
+
const publicPathValid = typeof publicPath === "string" && isCanonicalPath(publicPath) && publicPath.endsWith("/");
|
|
20
|
+
const publicPathInScope = publicPathValid && (identity === undefined || publicPath.startsWith(identity.scope));
|
|
21
|
+
if (!publicPathValid) {
|
|
22
|
+
findings.push(invalid("publicPath"));
|
|
23
|
+
}
|
|
24
|
+
else if (!publicPathInScope) {
|
|
25
|
+
findings.push(diagnostic("compile.public-path-outside-scope", ["hostBuildOutput", "publicPath"]));
|
|
26
|
+
}
|
|
27
|
+
// The worker and the manifest must be the very files the identity registers and links. A public
|
|
28
|
+
// path outside the scope is already reported, so their URLs are only compared when it is in scope.
|
|
29
|
+
const artifacts = [
|
|
30
|
+
["serviceWorkerFile", serviceWorkerFile, identity?.serviceWorkerUrl],
|
|
31
|
+
["manifestFile", manifestFile, identity?.manifestUrl],
|
|
32
|
+
];
|
|
33
|
+
for (const [field, fileName, identityUrl] of artifacts) {
|
|
34
|
+
if (typeof fileName !== "string" || !isRelativeFilePath(fileName) || !isCanonicalPath(`/${fileName}`)) {
|
|
35
|
+
findings.push(invalid(field));
|
|
36
|
+
}
|
|
37
|
+
else if (publicPathInScope &&
|
|
38
|
+
identityUrl !== undefined &&
|
|
39
|
+
decodedPathKey(`${publicPath}${fileName}`) !== decodedPathKey(identityUrl)) {
|
|
40
|
+
findings.push(invalid(field));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const elements = denseArrayElements(files);
|
|
44
|
+
if (elements === undefined) {
|
|
45
|
+
findings.push(invalid("files"));
|
|
46
|
+
return { findings, output: undefined };
|
|
47
|
+
}
|
|
48
|
+
const seenKeys = new Set();
|
|
49
|
+
const checkedFiles = [];
|
|
50
|
+
elements.forEach((element, index) => {
|
|
51
|
+
if (element === ACCESSOR || !hasExactKeys(element, FILE_FIELDS)) {
|
|
52
|
+
findings.push(invalid("files", index));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const { path, fingerprinted, contentHash } = element;
|
|
56
|
+
// A file path must stay URL-canonical once served, and unique however it is spelled.
|
|
57
|
+
const key = typeof path === "string" ? decodedPathKey(`/${path}`) : undefined;
|
|
58
|
+
const pathValid = typeof path === "string" && isRelativeFilePath(path) && isCanonicalPath(`/${path}`) && !seenKeys.has(key ?? "");
|
|
59
|
+
if (key !== undefined)
|
|
60
|
+
seenKeys.add(key);
|
|
61
|
+
if (!pathValid)
|
|
62
|
+
findings.push(invalid("files", index, "path"));
|
|
63
|
+
if (typeof fingerprinted !== "boolean")
|
|
64
|
+
findings.push(invalid("files", index, "fingerprinted"));
|
|
65
|
+
const contentHashValid = typeof contentHash === "string" && CONTENT_HASH.test(contentHash);
|
|
66
|
+
if (!contentHashValid)
|
|
67
|
+
findings.push(invalid("files", index, "contentHash"));
|
|
68
|
+
if (pathValid && typeof fingerprinted === "boolean" && contentHashValid) {
|
|
69
|
+
checkedFiles.push({ path, fingerprinted, contentHash });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (findings.length > 0)
|
|
73
|
+
return { findings, output: undefined };
|
|
74
|
+
return {
|
|
75
|
+
findings,
|
|
76
|
+
output: {
|
|
77
|
+
publicPath: publicPath,
|
|
78
|
+
serviceWorkerFile: serviceWorkerFile,
|
|
79
|
+
manifestFile: manifestFile,
|
|
80
|
+
files: checkedFiles,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Elements of a dense plain array read through property descriptors; `undefined` for anything else. */
|
|
85
|
+
function denseArrayElements(value) {
|
|
86
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype)
|
|
87
|
+
return undefined;
|
|
88
|
+
// Own keys are the indices plus `length`: holes or extra properties change the count.
|
|
89
|
+
if (Reflect.ownKeys(value).length !== value.length + 1)
|
|
90
|
+
return undefined;
|
|
91
|
+
return Array.from({ length: value.length }, (_, index) => {
|
|
92
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, index);
|
|
93
|
+
return descriptor !== undefined && "value" in descriptor ? descriptor.value : ACCESSOR;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=host-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-output.js","sourceRoot":"","sources":["../src/host-output.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AbsolutePath, PwaIdentity, PwaInstallMetadata, PwaPolicy, PwaTopology } from "@pwa-platform/contracts";
|
|
2
|
+
export type PwaHostBuildFile = {
|
|
3
|
+
/** POSIX path relative to the build output directory. */
|
|
4
|
+
readonly path: string;
|
|
5
|
+
/** Whether the file name already carries a content fingerprint. */
|
|
6
|
+
readonly fingerprinted: boolean;
|
|
7
|
+
/** Host-computed content hash: 8–128 URL-safe characters. */
|
|
8
|
+
readonly contentHash: string;
|
|
9
|
+
};
|
|
10
|
+
export type PwaCompileHostOutput = {
|
|
11
|
+
/** Absolute URL path ending with `/`, under which build files are served. */
|
|
12
|
+
readonly publicPath: AbsolutePath;
|
|
13
|
+
readonly serviceWorkerFile: string;
|
|
14
|
+
readonly manifestFile: string;
|
|
15
|
+
readonly files: readonly PwaHostBuildFile[];
|
|
16
|
+
};
|
|
17
|
+
/** Typed for callers; `compilePlan` still validates every field at runtime. */
|
|
18
|
+
export type PwaCompileInput = {
|
|
19
|
+
readonly identity: PwaIdentity;
|
|
20
|
+
readonly install: PwaInstallMetadata | null;
|
|
21
|
+
readonly policy: PwaPolicy;
|
|
22
|
+
readonly topology: PwaTopology;
|
|
23
|
+
readonly hostBuildOutput: PwaCompileHostOutput;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,WAAW,EACZ,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,oBAAoB,CAAC;CAChD,CAAC"}
|
package/dist/input.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PwaDiagnostic, PwaDiagnosticCode, PwaDiagnosticSeverity, PwaWarningDiagnostic } from "@pwa-platform/contracts";
|
|
2
|
+
/** Field names of `PwaCompileInput`; diagnostic paths are built only from these and array indices. */
|
|
3
|
+
export type InputField = "identity" | "install" | "policy" | "topology" | "hostBuildOutput" | "publicPath" | "serviceWorkerFile" | "manifestFile" | "files" | "path" | "fingerprinted" | "contentHash" | "resources" | "pathPrefix" | "offlineFallback" | "offlineWrites" | "targets" | "schemaVersion" | "registry" | "startUrl";
|
|
4
|
+
export declare function diagnostic(code: PwaDiagnosticCode, segments: readonly (InputField | number)[], severity?: PwaDiagnosticSeverity): PwaDiagnostic;
|
|
5
|
+
export declare function warningDiagnostic(code: PwaDiagnosticCode, segments: readonly (InputField | number)[]): PwaWarningDiagnostic;
|
|
6
|
+
/** Re-roots a contracts diagnostic, whose path is already sanitized, under an input field. */
|
|
7
|
+
export declare function withinField<T extends PwaDiagnostic>(field: "identity" | "install" | "policy", finding: T): T;
|
|
8
|
+
/** Re-roots a contracts diagnostic, whose path is already sanitized, under a sequence of fields. */
|
|
9
|
+
export declare function withinPath<T extends PwaDiagnostic>(prefix: readonly InputField[], finding: T): T;
|
|
10
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/internal/diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AAEjC,sGAAsG;AACtG,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,SAAS,GACT,QAAQ,GACR,UAAU,GACV,iBAAiB,GACjB,YAAY,GACZ,mBAAmB,GACnB,cAAc,GACd,OAAO,GACP,MAAM,GACN,eAAe,GACf,aAAa,GACb,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,eAAe,GACf,SAAS,GACT,eAAe,GACf,UAAU,GACV,UAAU,CAAC;AAEf,wBAAgB,UAAU,CACxB,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE,EAC1C,QAAQ,GAAE,qBAA+B,GACxC,aAAa,CAGf;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE,GACzC,oBAAoB,CAEtB;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAE5G;AAED,oGAAoG;AACpG,wBAAgB,UAAU,CAAC,CAAC,SAAS,aAAa,EAAE,MAAM,EAAE,SAAS,UAAU,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAGhG"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DIAGNOSTIC_MESSAGES } from "@pwa-platform/contracts";
|
|
2
|
+
export function diagnostic(code, segments, severity = "error") {
|
|
3
|
+
const path = segments.length === 0 ? "" : `/${segments.join("/")}`;
|
|
4
|
+
return { code, severity, path, message: DIAGNOSTIC_MESSAGES[code] };
|
|
5
|
+
}
|
|
6
|
+
export function warningDiagnostic(code, segments) {
|
|
7
|
+
return { ...diagnostic(code, segments), severity: "warning" };
|
|
8
|
+
}
|
|
9
|
+
/** Re-roots a contracts diagnostic, whose path is already sanitized, under an input field. */
|
|
10
|
+
export function withinField(field, finding) {
|
|
11
|
+
return { ...finding, path: `/${field}${finding.path}` };
|
|
12
|
+
}
|
|
13
|
+
/** Re-roots a contracts diagnostic, whose path is already sanitized, under a sequence of fields. */
|
|
14
|
+
export function withinPath(prefix, finding) {
|
|
15
|
+
const prefixPath = prefix.length === 0 ? "" : `/${prefix.join("/")}`;
|
|
16
|
+
return { ...finding, path: `${prefixPath}${finding.path}` };
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/internal/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAgC9D,MAAM,UAAU,UAAU,CACxB,IAAuB,EACvB,QAA0C,EAC1C,WAAkC,OAAO;IAEzC,MAAM,IAAI,GAAoB,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACpF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAuB,EACvB,QAA0C;IAE1C,OAAO,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAChE,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,WAAW,CAA0B,KAAwC,EAAE,OAAU;IACvG,OAAO,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;AAC1D,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,UAAU,CAA0B,MAA6B,EAAE,OAAU;IAC3F,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,OAAO,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,EAAqB,EAAE,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.d.ts","sourceRoot":"","sources":["../../src/internal/order.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CASrE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Compares strings by Unicode code point, not UTF-16 code unit, so ordering is stable for any text. */
|
|
2
|
+
export function compareCodePoints(left, right) {
|
|
3
|
+
const leftPoints = [...left];
|
|
4
|
+
const rightPoints = [...right];
|
|
5
|
+
const length = Math.min(leftPoints.length, rightPoints.length);
|
|
6
|
+
for (let index = 0; index < length; index += 1) {
|
|
7
|
+
const difference = (leftPoints[index]?.codePointAt(0) ?? 0) - (rightPoints[index]?.codePointAt(0) ?? 0);
|
|
8
|
+
if (difference !== 0)
|
|
9
|
+
return difference;
|
|
10
|
+
}
|
|
11
|
+
return leftPoints.length - rightPoints.length;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","sourceRoot":"","sources":["../../src/internal/order.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAa;IAC3D,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7B,MAAM,WAAW,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/D,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxG,IAAI,UAAU,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;IAC1C,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison key for a canonical path, decoded per segment like the URL standard: valid `%XX` escapes
|
|
3
|
+
* become bytes, invalid escapes stay literal, and bytes that are not UTF-8 become U+FFFD. Distinct
|
|
4
|
+
* spellings can therefore only collapse into one key, never split, so conflicts fail closed.
|
|
5
|
+
* Never emitted in plans.
|
|
6
|
+
*/
|
|
7
|
+
export declare function decodedPathKey(path: string): string;
|
|
8
|
+
/** Whole-segment containment of decoded keys: `/app/api/x` is within `/app/api`, `/app/apis` is not. */
|
|
9
|
+
export declare function isWithinKey(key: string, prefixKey: string): boolean;
|
|
10
|
+
//# sourceMappingURL=path-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-key.d.ts","sourceRoot":"","sources":["../../src/internal/path-key.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wGAAwG;AACxG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEnE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// `%2F` must not become a segment boundary the URL does not have, so it is replaced by a lone
|
|
2
|
+
// surrogate: UTF-8 decoding never produces one, so it cannot collide with decoded text.
|
|
3
|
+
const ENCODED_SLASH = "\uD800";
|
|
4
|
+
const UTF8_DECODER = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
5
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
6
|
+
/**
|
|
7
|
+
* Comparison key for a canonical path, decoded per segment like the URL standard: valid `%XX` escapes
|
|
8
|
+
* become bytes, invalid escapes stay literal, and bytes that are not UTF-8 become U+FFFD. Distinct
|
|
9
|
+
* spellings can therefore only collapse into one key, never split, so conflicts fail closed.
|
|
10
|
+
* Never emitted in plans.
|
|
11
|
+
*/
|
|
12
|
+
export function decodedPathKey(path) {
|
|
13
|
+
return path
|
|
14
|
+
.split("/")
|
|
15
|
+
.map((segment) => segment.split(/%2f/i).map(percentDecode).join(ENCODED_SLASH))
|
|
16
|
+
.join("/");
|
|
17
|
+
}
|
|
18
|
+
/** Whole-segment containment of decoded keys: `/app/api/x` is within `/app/api`, `/app/apis` is not. */
|
|
19
|
+
export function isWithinKey(key, prefixKey) {
|
|
20
|
+
return prefixKey === "/" || key === prefixKey || key.startsWith(`${prefixKey}/`);
|
|
21
|
+
}
|
|
22
|
+
function percentDecode(text) {
|
|
23
|
+
const bytes = [];
|
|
24
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
25
|
+
const escape = text.slice(index + 1, index + 3);
|
|
26
|
+
if (text[index] === "%" && /^[0-9A-Fa-f]{2}$/.test(escape)) {
|
|
27
|
+
bytes.push(Number.parseInt(escape, 16));
|
|
28
|
+
index += 2;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const character = String.fromCodePoint(text.codePointAt(index) ?? 0);
|
|
32
|
+
bytes.push(...UTF8_ENCODER.encode(character));
|
|
33
|
+
index += character.length - 1;
|
|
34
|
+
}
|
|
35
|
+
return UTF8_DECODER.decode(Uint8Array.from(bytes));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=path-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-key.js","sourceRoot":"","sources":["../../src/internal/path-key.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,wFAAwF;AACxF,MAAM,aAAa,GAAG,QAAQ,CAAC;AAC/B,MAAM,YAAY,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACnE,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;SAC9E,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,SAAiB;IACxD,OAAO,SAAS,KAAK,GAAG,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3D,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;YACxC,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9C,KAAK,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Same-origin absolute path already in WHATWG URL serialized form (mirrors contracts). */
|
|
2
|
+
export declare function isCanonicalPath(value: string): boolean;
|
|
3
|
+
/** POSIX path relative to a build output directory, without dot or empty segments (mirrors contracts). */
|
|
4
|
+
export declare function isRelativeFilePath(value: string): boolean;
|
|
5
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/internal/paths.ts"],"names":[],"mappings":"AAEA,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQtD;AAED,0GAA0G;AAC1G,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOzD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const URL_BASE = "https://core.invalid";
|
|
2
|
+
/** Same-origin absolute path already in WHATWG URL serialized form (mirrors contracts). */
|
|
3
|
+
export function isCanonicalPath(value) {
|
|
4
|
+
return (value.startsWith("/") &&
|
|
5
|
+
!value.includes("//") &&
|
|
6
|
+
!value.includes("\\") &&
|
|
7
|
+
URL.canParse(value, URL_BASE) &&
|
|
8
|
+
new URL(value, URL_BASE).pathname === value);
|
|
9
|
+
}
|
|
10
|
+
/** POSIX path relative to a build output directory, without dot or empty segments (mirrors contracts). */
|
|
11
|
+
export function isRelativeFilePath(value) {
|
|
12
|
+
return (value !== "" &&
|
|
13
|
+
!value.startsWith("/") &&
|
|
14
|
+
!value.includes("\\") &&
|
|
15
|
+
value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/internal/paths.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,sBAAsB,CAAC;AAExC,2FAA2F;AAC3F,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,CACL,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACrB,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;QAC7B,IAAI,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,KAAK,KAAK,CAC5C,CAAC;AACJ,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,CACL,KAAK,KAAK,EAAE;QACZ,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACtB,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC,CAC3F,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Plain (or null-prototype) object; arrays and class instances are excluded. */
|
|
2
|
+
export declare function isPlainRecord(value: unknown): value is Record<string, unknown>;
|
|
3
|
+
/** Plain object whose own keys are exactly `keys`, all enumerable data properties (getters are never invoked). */
|
|
4
|
+
export declare function hasExactKeys<K extends string>(value: unknown, keys: readonly K[]): value is Record<K, unknown>;
|
|
5
|
+
//# sourceMappingURL=shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../../src/internal/shape.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED,kHAAkH;AAClH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,KAAK,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAM9G"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Plain (or null-prototype) object; arrays and class instances are excluded. */
|
|
2
|
+
export function isPlainRecord(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4
|
+
return false;
|
|
5
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6
|
+
return prototype === Object.prototype || prototype === null;
|
|
7
|
+
}
|
|
8
|
+
/** Plain object whose own keys are exactly `keys`, all enumerable data properties (getters are never invoked). */
|
|
9
|
+
export function hasExactKeys(value, keys) {
|
|
10
|
+
if (!isPlainRecord(value) || Reflect.ownKeys(value).length !== keys.length)
|
|
11
|
+
return false;
|
|
12
|
+
return keys.every((key) => {
|
|
13
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
14
|
+
return descriptor !== undefined && descriptor.enumerable === true && "value" in descriptor;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape.js","sourceRoot":"","sources":["../../src/internal/shape.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtF,MAAM,SAAS,GAAY,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACxD,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,YAAY,CAAmB,KAAc,EAAE,IAAkB;IAC/E,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACzF,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACxB,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC/D,OAAO,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,IAAI,UAAU,CAAC;IAC7F,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AbsolutePath, PwaDiagnostic, PwaPathRule, PwaPlanOfflineFallback, PwaPolicy, PwaPrecacheEntry, PwaWarningDiagnostic } from "@pwa-platform/contracts";
|
|
2
|
+
import type { PwaCompileHostOutput } from "./input.js";
|
|
3
|
+
import type { CompiledPathRules } from "./rules.js";
|
|
4
|
+
export type CompiledPrecache = {
|
|
5
|
+
/** Sorted by URL code point, one entry per URL. */
|
|
6
|
+
readonly precache: PwaPrecacheEntry[];
|
|
7
|
+
readonly offlineFallback: PwaPlanOfflineFallback;
|
|
8
|
+
readonly errors: PwaDiagnostic[];
|
|
9
|
+
readonly warnings: PwaWarningDiagnostic[];
|
|
10
|
+
};
|
|
11
|
+
/** Selects precache entries from the host build output and validates the offline fallback. */
|
|
12
|
+
export declare function compilePrecache(policy: PwaPolicy, mountPath: AbsolutePath, host: PwaCompileHostOutput, rules: CompiledPathRules,
|
|
13
|
+
/** Decoded path keys of child scopes owned by other applications (root app of a `shared-origin` registry only). */
|
|
14
|
+
childPrefixKeys?: readonly string[]): CompiledPrecache;
|
|
15
|
+
/**
|
|
16
|
+
* Only asset rules whose action is an actual caching strategy contribute build files. Exported only for a
|
|
17
|
+
* white-box unit test (not part of the public API — `index.ts` never re-exports this module).
|
|
18
|
+
*/
|
|
19
|
+
export declare function isPrecacheSource(rule: PwaPathRule): boolean;
|
|
20
|
+
//# sourceMappingURL=precache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precache.d.ts","sourceRoot":"","sources":["../src/precache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAKzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IACtC,QAAQ,CAAC,eAAe,EAAE,sBAAsB,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAC3C,CAAC;AAEF,8FAA8F;AAC9F,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,iBAAiB;AACxB,mHAAmH;AACnH,eAAe,GAAE,SAAS,MAAM,EAAO,GACtC,gBAAgB,CAuFlB;AAQD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAE3D"}
|
package/dist/precache.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { CACHE_STRATEGIES } from "@pwa-platform/contracts";
|
|
2
|
+
import { diagnostic, warningDiagnostic } from "./internal/diagnostics.js";
|
|
3
|
+
import { compareCodePoints } from "./internal/order.js";
|
|
4
|
+
import { decodedPathKey, isWithinKey } from "./internal/path-key.js";
|
|
5
|
+
import { resolvePrefix } from "./rules.js";
|
|
6
|
+
/** Selects precache entries from the host build output and validates the offline fallback. */
|
|
7
|
+
export function compilePrecache(policy, mountPath, host, rules,
|
|
8
|
+
/** Decoded path keys of child scopes owned by other applications (root app of a `shared-origin` registry only). */
|
|
9
|
+
childPrefixKeys = []) {
|
|
10
|
+
const ruleKeys = rules.pathRules.map((rule) => decodedPathKey(rule.pathPrefix));
|
|
11
|
+
const firstMatch = (url) => {
|
|
12
|
+
const key = decodedPathKey(url);
|
|
13
|
+
const index = ruleKeys.findIndex((ruleKey) => isWithinKey(key, ruleKey));
|
|
14
|
+
return rules.pathRules[index];
|
|
15
|
+
};
|
|
16
|
+
// The worker, the manifest (however they are spelled) and source maps are never precached.
|
|
17
|
+
const excludedKeys = new Set([`${host.publicPath}${host.serviceWorkerFile}`, `${host.publicPath}${host.manifestFile}`].map(decodedPathKey));
|
|
18
|
+
const isSpecialFile = (file, url) => excludedKeys.has(decodedPathKey(url)) || /\.map$/i.test(file.path);
|
|
19
|
+
const isInChildScope = (url) => {
|
|
20
|
+
const key = decodedPathKey(url);
|
|
21
|
+
return childPrefixKeys.some((childKey) => isWithinKey(key, childKey));
|
|
22
|
+
};
|
|
23
|
+
// Files inside a child scope are still "built" for the offline fallback's own existence check;
|
|
24
|
+
// they are only ever excluded from precache selection, below, with a warning.
|
|
25
|
+
const nonSpecialFiles = host.files
|
|
26
|
+
.map((file) => ({ file, url: `${host.publicPath}${file.path}` }))
|
|
27
|
+
.filter(({ file, url }) => !isSpecialFile(file, url));
|
|
28
|
+
const hostFileWarnings = [];
|
|
29
|
+
host.files.forEach((file, index) => {
|
|
30
|
+
const url = `${host.publicPath}${file.path}`;
|
|
31
|
+
if (!isSpecialFile(file, url) && isInChildScope(url)) {
|
|
32
|
+
hostFileWarnings.push(warningDiagnostic("compile.host-file-in-child-scope", ["hostBuildOutput", "files", index, "path"]));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const buildFiles = nonSpecialFiles.filter(({ url }) => !isInChildScope(url));
|
|
36
|
+
const entries = new Map();
|
|
37
|
+
const add = (file, url) => {
|
|
38
|
+
entries.set(url, { url, revision: file.fingerprinted ? null : file.contentHash });
|
|
39
|
+
};
|
|
40
|
+
const coveringRules = new Set();
|
|
41
|
+
for (const { file, url } of buildFiles) {
|
|
42
|
+
const rule = firstMatch(url);
|
|
43
|
+
if (rule !== undefined && isPrecacheSource(rule)) {
|
|
44
|
+
coveringRules.add(rule);
|
|
45
|
+
add(file, url);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const warnings = [
|
|
49
|
+
...hostFileWarnings,
|
|
50
|
+
...rules.pathRules
|
|
51
|
+
.map((rule, position) => ({ rule, resourceIndex: rules.resourceIndexes[position] ?? position }))
|
|
52
|
+
.filter(({ rule }) => isPrecacheSource(rule) && !coveringRules.has(rule))
|
|
53
|
+
.sort((left, right) => left.resourceIndex - right.resourceIndex)
|
|
54
|
+
.map(({ resourceIndex }) => warningDiagnostic("compile.asset-rule-unmatched", ["policy", "resources", resourceIndex, "pathPrefix"])),
|
|
55
|
+
];
|
|
56
|
+
const errors = [];
|
|
57
|
+
let offlineFallback = { enabled: false };
|
|
58
|
+
if (policy.offlineFallback.enabled) {
|
|
59
|
+
const path = resolvePrefix(mountPath, policy.offlineFallback.path);
|
|
60
|
+
const pathKey = decodedPathKey(path);
|
|
61
|
+
const built = nonSpecialFiles.find((candidate) => decodedPathKey(candidate.url) === pathKey);
|
|
62
|
+
if (built === undefined) {
|
|
63
|
+
errors.push(diagnostic("compile.offline-fallback-not-built", ["policy", "offlineFallback", "path"]));
|
|
64
|
+
}
|
|
65
|
+
if (isInChildScope(path)) {
|
|
66
|
+
errors.push(diagnostic("compile.offline-fallback-in-child-scope", ["policy", "offlineFallback", "path"]));
|
|
67
|
+
}
|
|
68
|
+
if (firstMatch(path)?.action === "deny") {
|
|
69
|
+
errors.push(diagnostic("compile.offline-fallback-denied", ["policy", "offlineFallback", "path"]));
|
|
70
|
+
}
|
|
71
|
+
if (built !== undefined && errors.length === 0) {
|
|
72
|
+
add(built.file, built.url);
|
|
73
|
+
// Record the build file's own spelling so a literal cache lookup at runtime finds the entry.
|
|
74
|
+
offlineFallback = { enabled: true, path: built.url };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
precache: [...entries.values()].sort((left, right) => compareCodePoints(left.url, right.url)),
|
|
79
|
+
offlineFallback,
|
|
80
|
+
errors,
|
|
81
|
+
warnings,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// An explicit allow-list, not "not deny and not none": `PwaPathRuleAction` also has `exclude`, and a rule whose
|
|
85
|
+
// action is ever anything other than one of these three strategies must never be mistaken for a caching rule here,
|
|
86
|
+
// independent of what `resourceClass` happens to be (review #5 — `resourceClass` is a second, redundant guard, not
|
|
87
|
+
// the only one).
|
|
88
|
+
const CACHING_STRATEGIES = new Set(CACHE_STRATEGIES.filter((strategy) => strategy !== "none"));
|
|
89
|
+
/**
|
|
90
|
+
* Only asset rules whose action is an actual caching strategy contribute build files. Exported only for a
|
|
91
|
+
* white-box unit test (not part of the public API — `index.ts` never re-exports this module).
|
|
92
|
+
*/
|
|
93
|
+
export function isPrecacheSource(rule) {
|
|
94
|
+
return rule.resourceClass === "asset" && CACHING_STRATEGIES.has(rule.action);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=precache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precache.js","sourceRoot":"","sources":["../src/precache.ts"],"names":[],"mappings":"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"}
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AbsolutePath, MountRelativePath, PwaDiagnostic, PwaPathRule, PwaPolicy } from "@pwa-platform/contracts";
|
|
2
|
+
export type CompiledPathRules = {
|
|
3
|
+
/** First-match evaluation order: deny rules first, then longer prefixes, then code point order. */
|
|
4
|
+
readonly pathRules: PwaPathRule[];
|
|
5
|
+
/** Index of the policy resource each entry of `pathRules` was compiled from. */
|
|
6
|
+
readonly resourceIndexes: number[];
|
|
7
|
+
readonly findings: PwaDiagnostic[];
|
|
8
|
+
};
|
|
9
|
+
/** Resolves policy rules against the mount path, rejects conflicts and orders the rules for evaluation. */
|
|
10
|
+
export declare function compilePathRules(policy: PwaPolicy, mountPath: AbsolutePath): CompiledPathRules;
|
|
11
|
+
/** `/api` under mount `/app` resolves to `/app/api`; the root prefix resolves to the mount path itself. */
|
|
12
|
+
export declare function resolvePrefix(mountPath: AbsolutePath, prefix: MountRelativePath): AbsolutePath;
|
|
13
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,aAAa,EAEb,WAAW,EACX,SAAS,EAEV,MAAM,yBAAyB,CAAC;AAcjC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mGAAmG;IACnG,QAAQ,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC;IAClC,gFAAgF;IAChF,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC;CACpC,CAAC;AAEF,2GAA2G;AAC3G,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,GAAG,iBAAiB,CA6B9F;AAED,2GAA2G;AAC3G,wBAAgB,aAAa,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,GAAG,YAAY,CAI9F"}
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { diagnostic } from "./internal/diagnostics.js";
|
|
2
|
+
import { compareCodePoints } from "./internal/order.js";
|
|
3
|
+
import { decodedPathKey, isWithinKey } from "./internal/path-key.js";
|
|
4
|
+
const DENY_CLASSES = new Set(["session-data", "mutation", "stream", "unclassified"]);
|
|
5
|
+
/** Resolves policy rules against the mount path, rejects conflicts and orders the rules for evaluation. */
|
|
6
|
+
export function compilePathRules(policy, mountPath) {
|
|
7
|
+
const duplicates = [];
|
|
8
|
+
const seenKeys = new Set();
|
|
9
|
+
const candidates = [];
|
|
10
|
+
policy.resources.forEach((resource, index) => {
|
|
11
|
+
const pathPrefix = resolvePrefix(mountPath, resource.pathPrefix);
|
|
12
|
+
const key = decodedPathKey(pathPrefix);
|
|
13
|
+
if (seenKeys.has(key)) {
|
|
14
|
+
duplicates.push(conflict("compile.duplicate-path-prefix", index));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
seenKeys.add(key);
|
|
18
|
+
const action = DENY_CLASSES.has(resource.resourceClass) ? "deny" : resource.cache;
|
|
19
|
+
candidates.push({ index, key, rule: { pathPrefix, resourceClass: resource.resourceClass, action, source: "policy" } });
|
|
20
|
+
});
|
|
21
|
+
// Business allow rules can never override a deny rule, so an allow rule inside a deny prefix is an error.
|
|
22
|
+
const denyKeys = candidates.filter((candidate) => candidate.rule.action === "deny").map((candidate) => candidate.key);
|
|
23
|
+
const allowsUnderDeny = candidates
|
|
24
|
+
.filter((candidate) => candidate.rule.action !== "deny" && denyKeys.some((key) => isWithinKey(candidate.key, key)))
|
|
25
|
+
.map((candidate) => conflict("compile.allow-under-deny", candidate.index));
|
|
26
|
+
const ordered = [...candidates].sort(byEvaluationOrder);
|
|
27
|
+
return {
|
|
28
|
+
pathRules: ordered.map((candidate) => candidate.rule),
|
|
29
|
+
resourceIndexes: ordered.map((candidate) => candidate.index),
|
|
30
|
+
findings: [...duplicates, ...allowsUnderDeny],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** `/api` under mount `/app` resolves to `/app/api`; the root prefix resolves to the mount path itself. */
|
|
34
|
+
export function resolvePrefix(mountPath, prefix) {
|
|
35
|
+
if (mountPath === "/")
|
|
36
|
+
return prefix;
|
|
37
|
+
const mount = mountPath.endsWith("/") ? mountPath.slice(1, -1) : mountPath.slice(1);
|
|
38
|
+
return prefix === "/" ? `/${mount}` : `/${mount}${prefix}`;
|
|
39
|
+
}
|
|
40
|
+
function conflict(code, index) {
|
|
41
|
+
return diagnostic(code, ["policy", "resources", index, "pathPrefix"]);
|
|
42
|
+
}
|
|
43
|
+
function byEvaluationOrder(left, right) {
|
|
44
|
+
const leftDeny = left.rule.action === "deny";
|
|
45
|
+
const rightDeny = right.rule.action === "deny";
|
|
46
|
+
if (leftDeny !== rightDeny)
|
|
47
|
+
return leftDeny ? -1 : 1;
|
|
48
|
+
const lengthDifference = [...right.key].length - [...left.key].length;
|
|
49
|
+
return lengthDifference !== 0 ? lengthDifference : compareCodePoints(left.key, right.key);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AbsolutePath, PwaDiagnostic, PwaIdentity, PwaInstallMetadata, PwaPathRule, PwaPolicy, PwaTopology } from "@pwa-platform/contracts";
|
|
2
|
+
export type ResolvedTopology = {
|
|
3
|
+
/** Recorded as-is in the plan's `topology` field. */
|
|
4
|
+
readonly value: PwaTopology;
|
|
5
|
+
/** Decoded path keys of every child scope; empty unless this app is the root of a `shared-origin` registry. */
|
|
6
|
+
readonly childPrefixKeys: readonly string[];
|
|
7
|
+
/** Exclude rules to place ahead of every other path rule; empty unless this app is the root. */
|
|
8
|
+
readonly excludeRules: readonly PwaPathRule[];
|
|
9
|
+
};
|
|
10
|
+
export type TopologyResolution = {
|
|
11
|
+
readonly findings: PwaDiagnostic[];
|
|
12
|
+
/** `undefined` when the topology could not be resolved (findings then explains why, or the identity was invalid). */
|
|
13
|
+
readonly resolved: ResolvedTopology | undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the compiler's `topology` input: validates a `shared-origin` registry, matches the
|
|
17
|
+
* building app's identity to exactly one registry entry, and (for the root app) computes the
|
|
18
|
+
* exclude rules and child scope keys the rest of compilation needs.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveTopology(topology: unknown, identity: PwaIdentity | undefined): TopologyResolution;
|
|
21
|
+
/** Root-app check: a policy resource (any action, including deny) must not reach into a child scope. */
|
|
22
|
+
export declare function policyRulesInChildScope(policy: PwaPolicy, mountPath: AbsolutePath, childPrefixKeys: readonly string[]): PwaDiagnostic[];
|
|
23
|
+
/** Root-app check: the install start URL must not reach into a child scope (contracts' `plan.start-url-in-child-scope`). */
|
|
24
|
+
export declare function installStartUrlInChildScope(install: PwaInstallMetadata, childPrefixKeys: readonly string[]): PwaDiagnostic[];
|
|
25
|
+
//# sourceMappingURL=topology.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology.d.ts","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,SAAS,EAET,WAAW,EACZ,MAAM,yBAAyB,CAAC;AAOjC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,+GAA+G;IAC/G,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC;IACnC,qHAAqH;IACrH,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACjD,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,GAAG,SAAS,GAAG,kBAAkB,CAsCxG;AAED,wGAAwG;AACxG,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,YAAY,EACvB,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,aAAa,EAAE,CAOjB;AAED,4HAA4H;AAC5H,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kBAAkB,EAC3B,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,aAAa,EAAE,CAKjB"}
|
package/dist/topology.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { validateOriginRegistry } from "@pwa-platform/contracts";
|
|
2
|
+
import { diagnostic, withinPath } from "./internal/diagnostics.js";
|
|
3
|
+
import { compareCodePoints } from "./internal/order.js";
|
|
4
|
+
import { decodedPathKey, isWithinKey } from "./internal/path-key.js";
|
|
5
|
+
import { hasExactKeys } from "./internal/shape.js";
|
|
6
|
+
import { resolvePrefix } from "./rules.js";
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the compiler's `topology` input: validates a `shared-origin` registry, matches the
|
|
9
|
+
* building app's identity to exactly one registry entry, and (for the root app) computes the
|
|
10
|
+
* exclude rules and child scope keys the rest of compilation needs.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveTopology(topology, identity) {
|
|
13
|
+
if (hasExactKeys(topology, ["kind"]) && topology.kind === "standalone-origin") {
|
|
14
|
+
return { findings: [], resolved: { value: { kind: "standalone-origin" }, childPrefixKeys: [], excludeRules: [] } };
|
|
15
|
+
}
|
|
16
|
+
if (hasExactKeys(topology, ["kind", "registry"]) && topology.kind === "shared-origin") {
|
|
17
|
+
const registryResult = validateOriginRegistry(topology.registry);
|
|
18
|
+
if (!registryResult.ok) {
|
|
19
|
+
return {
|
|
20
|
+
findings: registryResult.diagnostics.map((finding) => withinPath(["topology", "registry"], finding)),
|
|
21
|
+
resolved: undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const registry = registryResult.value;
|
|
25
|
+
// Identity failed validation: compilation already fails, and there is nothing meaningful to match.
|
|
26
|
+
if (identity === undefined)
|
|
27
|
+
return { findings: [], resolved: undefined };
|
|
28
|
+
const matches = [registry.root, ...registry.children].filter((entry) => matchesIdentity(entry, identity));
|
|
29
|
+
if (registry.origin !== identity.origin || registry.environment !== identity.environment || matches.length !== 1) {
|
|
30
|
+
return { findings: [diagnostic("plan.registry-identity-mismatch", ["topology", "registry"])], resolved: undefined };
|
|
31
|
+
}
|
|
32
|
+
const value = { kind: "shared-origin", registry };
|
|
33
|
+
if (!matchesIdentity(registry.root, identity)) {
|
|
34
|
+
return { findings: [], resolved: { value, childPrefixKeys: [], excludeRules: [] } };
|
|
35
|
+
}
|
|
36
|
+
const childPrefixes = [...registry.children.map((child) => scopeToPathPrefix(child.scope))].sort(compareCodePoints);
|
|
37
|
+
const excludeRules = childPrefixes.map((pathPrefix) => ({
|
|
38
|
+
pathPrefix,
|
|
39
|
+
resourceClass: "unclassified",
|
|
40
|
+
action: "exclude",
|
|
41
|
+
source: "platform",
|
|
42
|
+
}));
|
|
43
|
+
return { findings: [], resolved: { value, childPrefixKeys: childPrefixes.map(decodedPathKey), excludeRules } };
|
|
44
|
+
}
|
|
45
|
+
return { findings: [diagnostic("compile.unsupported-topology", ["topology"])], resolved: undefined };
|
|
46
|
+
}
|
|
47
|
+
/** Root-app check: a policy resource (any action, including deny) must not reach into a child scope. */
|
|
48
|
+
export function policyRulesInChildScope(policy, mountPath, childPrefixKeys) {
|
|
49
|
+
return policy.resources.flatMap((resource, index) => {
|
|
50
|
+
const key = decodedPathKey(resolvePrefix(mountPath, resource.pathPrefix));
|
|
51
|
+
return childPrefixKeys.some((childKey) => isWithinKey(key, childKey))
|
|
52
|
+
? [diagnostic("compile.policy-rule-in-child-scope", ["policy", "resources", index, "pathPrefix"])]
|
|
53
|
+
: [];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Root-app check: the install start URL must not reach into a child scope (contracts' `plan.start-url-in-child-scope`). */
|
|
57
|
+
export function installStartUrlInChildScope(install, childPrefixKeys) {
|
|
58
|
+
const key = decodedPathKey(install.startUrl);
|
|
59
|
+
return childPrefixKeys.some((childKey) => isWithinKey(key, childKey))
|
|
60
|
+
? [diagnostic("compile.start-url-in-child-scope", ["install", "startUrl"])]
|
|
61
|
+
: [];
|
|
62
|
+
}
|
|
63
|
+
function matchesIdentity(entry, identity) {
|
|
64
|
+
return (entry.appId === identity.appId &&
|
|
65
|
+
entry.scope === identity.scope &&
|
|
66
|
+
entry.serviceWorkerUrl === identity.serviceWorkerUrl &&
|
|
67
|
+
entry.manifestId === identity.manifestId &&
|
|
68
|
+
entry.manifestUrl === identity.manifestUrl);
|
|
69
|
+
}
|
|
70
|
+
/** Canonical path-prefix form of a scope: the scope without its trailing slash (matches `resolvePrefix`). */
|
|
71
|
+
function scopeToPathPrefix(scope) {
|
|
72
|
+
return scope === "/" ? "/" : scope.slice(0, -1);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=topology.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology.js","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pwa-platform/core",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Compile PWA Platform policies into deployment plans.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@pwa-platform/contracts": "0.1.0-beta.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.0.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org/",
|
|
26
|
+
"access": "public",
|
|
27
|
+
"tag": "next"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc --project tsconfig.build.json",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
33
|
+
}
|
|
34
|
+
}
|