@remit/web-client 0.0.261 → 0.0.263
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.263",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
2
|
import { describe, it } from "node:test";
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
type AppGraph,
|
|
5
|
+
AUTH_COMPOSITIONS,
|
|
6
|
+
bundleAppGraph,
|
|
7
|
+
} from "@/test-support/test-bundle-app";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Proves the auth shell is a composition choice, not a runtime toggle inside
|
|
@@ -11,89 +13,10 @@ import { build } from "esbuild";
|
|
|
11
13
|
* app shell and screens are walked from the real entry each time, so this fails
|
|
12
14
|
* the moment any surface reaches back to a specific identity SDK.
|
|
13
15
|
*/
|
|
14
|
-
const
|
|
15
|
-
const srcDir = join(here, "..");
|
|
16
|
-
const packageDir = join(srcDir, "..");
|
|
17
|
-
|
|
18
|
-
interface Variant {
|
|
19
|
-
specifier: string;
|
|
20
|
-
name: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const VARIANTS: Record<"betterAuth" | "cognito", Variant> = {
|
|
24
|
-
betterAuth: {
|
|
25
|
-
specifier: "@/auth/better-auth-provider",
|
|
26
|
-
name: "betterAuthProvider",
|
|
27
|
-
},
|
|
28
|
-
cognito: {
|
|
29
|
-
specifier: "@/auth/cognito-provider",
|
|
30
|
-
name: "cognitoAuthProvider",
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
interface Graph {
|
|
35
|
-
inputs: string[];
|
|
36
|
-
importPaths: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Externalize third-party packages, but bundle in-repo `@remit/*` workspace
|
|
40
|
-
// source so the walk matches what the real vite build includes. A shared
|
|
41
|
-
// primitive (e.g. `@remit/ui`) that pulled in Amplify would then surface in this
|
|
42
|
-
// graph and fail the test, rather than hiding behind an external edge.
|
|
43
|
-
const externalizeThirdParty = {
|
|
44
|
-
name: "externalize-third-party",
|
|
45
|
-
setup(build: import("esbuild").PluginBuild) {
|
|
46
|
-
build.onResolve({ filter: /.*/ }, (args) => {
|
|
47
|
-
const path = args.path;
|
|
48
|
-
if (path.startsWith(".") || path.startsWith("/")) return null;
|
|
49
|
-
if (path === "@" || path.startsWith("@/")) return null;
|
|
50
|
-
if (path.startsWith("@remit/")) return null;
|
|
51
|
-
return { path, external: true };
|
|
52
|
-
});
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const bundleGraph = async (variant: Variant): Promise<Graph> => {
|
|
57
|
-
const entry = [
|
|
58
|
-
'import { mountApp } from "@/shell";',
|
|
59
|
-
`import { ${variant.name} } from "${variant.specifier}";`,
|
|
60
|
-
`mountApp({ authProvider: ${variant.name} });`,
|
|
61
|
-
"",
|
|
62
|
-
].join("\n");
|
|
63
|
-
|
|
64
|
-
const result = await build({
|
|
65
|
-
stdin: {
|
|
66
|
-
contents: entry,
|
|
67
|
-
resolveDir: packageDir,
|
|
68
|
-
loader: "tsx",
|
|
69
|
-
sourcefile: "compose-entry.tsx",
|
|
70
|
-
},
|
|
71
|
-
bundle: true,
|
|
72
|
-
write: false,
|
|
73
|
-
metafile: true,
|
|
74
|
-
logLevel: "silent",
|
|
75
|
-
format: "esm",
|
|
76
|
-
platform: "browser",
|
|
77
|
-
jsx: "automatic",
|
|
78
|
-
jsxImportSource: "react",
|
|
79
|
-
plugins: [externalizeThirdParty],
|
|
80
|
-
loader: { ".css": "empty", ".png": "empty", ".svg": "empty" },
|
|
81
|
-
absWorkingDir: packageDir,
|
|
82
|
-
alias: { "@": srcDir },
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const inputs = Object.keys(result.metafile.inputs);
|
|
86
|
-
const importPaths = new Set<string>();
|
|
87
|
-
for (const input of Object.values(result.metafile.inputs)) {
|
|
88
|
-
for (const imported of input.imports) importPaths.add(imported.path);
|
|
89
|
-
}
|
|
90
|
-
return { inputs, importPaths: [...importPaths] };
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const mentionsAmplify = ({ inputs, importPaths }: Graph): boolean =>
|
|
16
|
+
const mentionsAmplify = ({ inputs, importPaths }: AppGraph): boolean =>
|
|
94
17
|
[...inputs, ...importPaths].some((path) => /aws-amplify/.test(path));
|
|
95
18
|
|
|
96
|
-
const composesCognitoShell = ({ inputs }:
|
|
19
|
+
const composesCognitoShell = ({ inputs }: AppGraph): boolean =>
|
|
97
20
|
inputs.some(
|
|
98
21
|
(path) =>
|
|
99
22
|
path.includes("auth/cognito/") || path.includes("cognito-provider"),
|
|
@@ -101,7 +24,7 @@ const composesCognitoShell = ({ inputs }: Graph): boolean =>
|
|
|
101
24
|
|
|
102
25
|
describe("web-client composition", () => {
|
|
103
26
|
it("omits every Amplify/Cognito module when composing the better-auth provider", async () => {
|
|
104
|
-
const graph = await
|
|
27
|
+
const graph = await bundleAppGraph(AUTH_COMPOSITIONS.betterAuth);
|
|
105
28
|
|
|
106
29
|
assert.ok(
|
|
107
30
|
graph.inputs.some((path) => path.includes("shell/index")),
|
|
@@ -120,7 +43,7 @@ describe("web-client composition", () => {
|
|
|
120
43
|
});
|
|
121
44
|
|
|
122
45
|
it("includes the Amplify/Cognito modules when composing the cognito provider", async () => {
|
|
123
|
-
const graph = await
|
|
46
|
+
const graph = await bundleAppGraph(AUTH_COMPOSITIONS.cognito);
|
|
124
47
|
|
|
125
48
|
assert.equal(
|
|
126
49
|
mentionsAmplify(graph),
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type MatchMode,
|
|
13
13
|
type RuleClause,
|
|
14
14
|
type RunState,
|
|
15
|
+
ruleRestrictionFor,
|
|
15
16
|
type SearchConversion,
|
|
16
17
|
type SelectionRestriction,
|
|
17
18
|
SelectionWizard,
|
|
@@ -513,11 +514,15 @@ function SelectionWizardSession({
|
|
|
513
514
|
// preview behind a widened door has something to count against. The one-off
|
|
514
515
|
// scope and the ticked rows act on the messages themselves and are unaffected.
|
|
515
516
|
const wizardScope = wizardScopeFor(accountId, selectionRestriction);
|
|
517
|
+
// An escalated match is restricted on the same step for its own reason: the
|
|
518
|
+
// list resolved it before the wizard opened, and no door on the match step can
|
|
519
|
+
// give it a predicate to keep matching on (#1193).
|
|
520
|
+
const ruleRestriction = ruleRestrictionFor(mode, wizardScope);
|
|
516
521
|
const restrictionFor = (step: StepId): string | undefined => {
|
|
517
522
|
if (step === "folder") return wizardScope.destination;
|
|
518
523
|
if (step !== "rule") return undefined;
|
|
519
524
|
return named.scope === "standing" || named.scope === "until"
|
|
520
|
-
?
|
|
525
|
+
? ruleRestriction
|
|
521
526
|
: undefined;
|
|
522
527
|
};
|
|
523
528
|
const blockedReason =
|
|
@@ -1048,7 +1053,7 @@ function SelectionWizardSession({
|
|
|
1048
1053
|
draft: named,
|
|
1049
1054
|
onScopeChange: (scope) => setDraft((held) => ({ ...held, scope })),
|
|
1050
1055
|
onUntilChange: (until) => setDraft((held) => ({ ...held, until })),
|
|
1051
|
-
restriction:
|
|
1056
|
+
restriction: ruleRestriction,
|
|
1052
1057
|
}}
|
|
1053
1058
|
name={{
|
|
1054
1059
|
name: named.name ?? "",
|
package/src/index.css
CHANGED
|
@@ -4,13 +4,6 @@
|
|
|
4
4
|
the .dark ancestor scope used everywhere in this app. */
|
|
5
5
|
@import "@remit/ui/tokens.css";
|
|
6
6
|
|
|
7
|
-
/* The structural sheet the calendar grid's engine needs, which the kit ships
|
|
8
|
-
beside the component and every app that mounts it has to ask for. Without it
|
|
9
|
-
the week and day grids collapse into a stack and draw no events at all: the
|
|
10
|
-
engine lays its columns out against this sheet, and a column with no
|
|
11
|
-
geometry has nowhere to put an occurrence. */
|
|
12
|
-
@import "@remit/ui/calendar.css";
|
|
13
|
-
|
|
14
7
|
/* Tailwind v4 content scanning: name every source Tailwind must inspect for
|
|
15
8
|
utility class usage. Automatic detection is rooted at the Vite root, which
|
|
16
9
|
for the distributor harness build is a throwaway directory holding only the
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
AUTH_COMPOSITIONS,
|
|
8
|
+
bundleAppGraph,
|
|
9
|
+
graphPath,
|
|
10
|
+
} from "@/test-support/test-bundle-app";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A stylesheet the kit ships beside a component has to reach the app on its
|
|
14
|
+
* own. #1062: `CalendarGrid`'s structural sheet was a subpath every consuming
|
|
15
|
+
* app had to `@import` by hand, this one never did, and three releases drew the
|
|
16
|
+
* week and day views as an unstyled stack with no events in them — while
|
|
17
|
+
* Storybook, the render tests and the server render all read correctly, because
|
|
18
|
+
* each of those pulled the sheet in itself.
|
|
19
|
+
*
|
|
20
|
+
* The kit's token sheet is the exception the app asks for by name: one theme,
|
|
21
|
+
* loaded whether or not anything renders. Every other sheet is structural,
|
|
22
|
+
* belongs to the component that cannot draw without it, and is checked here
|
|
23
|
+
* against the bundler's own module graph walked from the app entry — so a sheet
|
|
24
|
+
* no component imports fails the build instead of a deployment.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const kitSrc = resolve(here, "../../ui/src");
|
|
29
|
+
|
|
30
|
+
/** The kit's theme sheet, which `src/index.css` imports directly. */
|
|
31
|
+
const THEME_SHEET = join(kitSrc, "tokens.css");
|
|
32
|
+
|
|
33
|
+
const structuralSheets = (dir: string): string[] => {
|
|
34
|
+
const found: string[] = [];
|
|
35
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
36
|
+
const path = join(dir, entry.name);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
if (entry.name !== "node_modules") found.push(...structuralSheets(path));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (entry.name.endsWith(".css") && path !== THEME_SHEET) found.push(path);
|
|
42
|
+
}
|
|
43
|
+
return found.sort();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe("kit stylesheets (#1062)", () => {
|
|
47
|
+
it("walks every structural sheet @remit/ui ships from the app entry", async () => {
|
|
48
|
+
const sheets = structuralSheets(kitSrc);
|
|
49
|
+
assert.ok(
|
|
50
|
+
sheets.length > 0,
|
|
51
|
+
"the kit ships no structural sheet, so this asserts nothing — name the sheet it should follow, or drop it",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const { inputs } = await bundleAppGraph(AUTH_COMPOSITIONS.betterAuth);
|
|
55
|
+
const reached = new Set(inputs.map(graphPath));
|
|
56
|
+
const orphaned = sheets.filter((sheet) => !reached.has(sheet));
|
|
57
|
+
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
orphaned.map((sheet) => relative(kitSrc, sheet)),
|
|
60
|
+
[],
|
|
61
|
+
"a structural sheet no component imports never reaches the deployed app",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { build } from "esbuild";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Walks the app the way a distributor's build walks it — from the shell entry,
|
|
7
|
+
* with one identity system composed in — and hands back the module graph the
|
|
8
|
+
* bundler resolved. A suite asserts on that graph rather than on import text: a
|
|
9
|
+
* specifier reads the same whether or not anything resolves it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const srcDir = join(here, "..");
|
|
14
|
+
const packageDir = join(srcDir, "..");
|
|
15
|
+
|
|
16
|
+
/** An identity system a build composes, by the export that supplies it. */
|
|
17
|
+
export interface AuthComposition {
|
|
18
|
+
specifier: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const AUTH_COMPOSITIONS: Record<
|
|
23
|
+
"betterAuth" | "cognito",
|
|
24
|
+
AuthComposition
|
|
25
|
+
> = {
|
|
26
|
+
betterAuth: {
|
|
27
|
+
specifier: "@/auth/better-auth-provider",
|
|
28
|
+
name: "betterAuthProvider",
|
|
29
|
+
},
|
|
30
|
+
cognito: {
|
|
31
|
+
specifier: "@/auth/cognito-provider",
|
|
32
|
+
name: "cognitoAuthProvider",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface AppGraph {
|
|
37
|
+
/** Every file the bundler pulled in, relative to the package root. */
|
|
38
|
+
inputs: string[];
|
|
39
|
+
/** Every specifier those files import, as the bundler resolved it. */
|
|
40
|
+
importPaths: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Externalize third-party packages, but bundle in-repo `@remit/*` workspace
|
|
44
|
+
// source so the walk matches what the real vite build includes. A shared
|
|
45
|
+
// primitive (e.g. `@remit/ui`) that pulled in Amplify would then surface in this
|
|
46
|
+
// graph and fail the test, rather than hiding behind an external edge.
|
|
47
|
+
const externalizeThirdParty = {
|
|
48
|
+
name: "externalize-third-party",
|
|
49
|
+
setup(build: import("esbuild").PluginBuild) {
|
|
50
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
51
|
+
const path = args.path;
|
|
52
|
+
if (path.startsWith(".") || path.startsWith("/")) return null;
|
|
53
|
+
if (path === "@" || path.startsWith("@/")) return null;
|
|
54
|
+
if (path.startsWith("@remit/")) return null;
|
|
55
|
+
return { path, external: true };
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Bundle the app against `auth` and report everything the build reached. */
|
|
61
|
+
export const bundleAppGraph = async (
|
|
62
|
+
auth: AuthComposition,
|
|
63
|
+
): Promise<AppGraph> => {
|
|
64
|
+
const result = await build({
|
|
65
|
+
stdin: {
|
|
66
|
+
contents: [
|
|
67
|
+
'import { mountApp } from "@/shell";',
|
|
68
|
+
`import { ${auth.name} } from "${auth.specifier}";`,
|
|
69
|
+
`mountApp({ authProvider: ${auth.name} });`,
|
|
70
|
+
"",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
resolveDir: packageDir,
|
|
73
|
+
loader: "tsx",
|
|
74
|
+
sourcefile: "compose-entry.tsx",
|
|
75
|
+
},
|
|
76
|
+
bundle: true,
|
|
77
|
+
write: false,
|
|
78
|
+
metafile: true,
|
|
79
|
+
logLevel: "silent",
|
|
80
|
+
format: "esm",
|
|
81
|
+
platform: "browser",
|
|
82
|
+
jsx: "automatic",
|
|
83
|
+
jsxImportSource: "react",
|
|
84
|
+
plugins: [externalizeThirdParty],
|
|
85
|
+
// A stylesheet is emptied rather than parsed: this walk is after which
|
|
86
|
+
// files the build reaches, and what one sheet `@import`s from another is
|
|
87
|
+
// the app stylesheet's business, not the module graph's.
|
|
88
|
+
loader: { ".css": "empty", ".png": "empty", ".svg": "empty" },
|
|
89
|
+
absWorkingDir: packageDir,
|
|
90
|
+
alias: { "@": srcDir },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const inputs = Object.keys(result.metafile.inputs);
|
|
94
|
+
const importPaths = new Set<string>();
|
|
95
|
+
for (const input of Object.values(result.metafile.inputs)) {
|
|
96
|
+
for (const imported of input.imports) importPaths.add(imported.path);
|
|
97
|
+
}
|
|
98
|
+
return { inputs, importPaths: [...importPaths] };
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** An input path from a graph, as an absolute path on this checkout. */
|
|
102
|
+
export const graphPath = (input: string): string => join(packageDir, input);
|