@bleedingdev/modern-js-create 3.4.0-ultramodern.2 → 3.4.0-ultramodern.20
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/README.md +13 -11
- package/dist/cjs/index.cjs +37 -4
- package/dist/cjs/locale/en.cjs +12 -2
- package/dist/cjs/locale/zh.cjs +12 -2
- package/dist/cjs/ultramodern-tooling/commands.cjs +185 -0
- package/dist/cjs/ultramodern-tooling/config.cjs +172 -0
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +79 -61
- package/dist/cjs/ultramodern-workspace/bridge-config.cjs +327 -0
- package/dist/cjs/ultramodern-workspace/codesmith.cjs +1 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +121 -648
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +26 -33
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +4 -4
- package/dist/cjs/ultramodern-workspace/effect-api.cjs +230 -100
- package/dist/cjs/ultramodern-workspace/fs-io.cjs +117 -0
- package/dist/cjs/ultramodern-workspace/generation-result.cjs +1 -1
- package/dist/cjs/ultramodern-workspace/index.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/mf-validation.cjs +571 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +51 -20
- package/dist/cjs/ultramodern-workspace/package-json.cjs +62 -19
- package/dist/cjs/ultramodern-workspace/policy.cjs +2 -2
- package/dist/cjs/ultramodern-workspace/public-api.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/versions.cjs +5 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +80 -43
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +63 -20
- package/dist/esm/index.js +37 -4
- package/dist/esm/locale/en.js +12 -2
- package/dist/esm/locale/zh.js +12 -2
- package/dist/esm/ultramodern-tooling/commands.js +132 -0
- package/dist/esm/ultramodern-tooling/config.js +120 -0
- package/dist/esm/ultramodern-workspace/add-vertical.js +81 -66
- package/dist/esm/ultramodern-workspace/bridge-config.js +270 -0
- package/dist/esm/ultramodern-workspace/codesmith.js +1 -0
- package/dist/esm/ultramodern-workspace/contracts.js +124 -603
- package/dist/esm/ultramodern-workspace/demo-components.js +26 -33
- package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
- package/dist/esm/ultramodern-workspace/effect-api.js +230 -100
- package/dist/esm/ultramodern-workspace/fs-io.js +116 -2
- package/dist/esm/ultramodern-workspace/generation-result.js +2 -2
- package/dist/esm/ultramodern-workspace/index.js +1 -0
- package/dist/esm/ultramodern-workspace/mf-validation.js +516 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +49 -21
- package/dist/esm/ultramodern-workspace/package-json.js +60 -20
- package/dist/esm/ultramodern-workspace/policy.js +2 -2
- package/dist/esm/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm/ultramodern-workspace/versions.js +3 -1
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +78 -26
- package/dist/esm/ultramodern-workspace/write-workspace.js +67 -27
- package/dist/esm-node/index.js +37 -4
- package/dist/esm-node/locale/en.js +12 -2
- package/dist/esm-node/locale/zh.js +12 -2
- package/dist/esm-node/ultramodern-tooling/commands.js +133 -0
- package/dist/esm-node/ultramodern-tooling/config.js +121 -0
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +81 -66
- package/dist/esm-node/ultramodern-workspace/bridge-config.js +271 -0
- package/dist/esm-node/ultramodern-workspace/codesmith.js +1 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +124 -603
- package/dist/esm-node/ultramodern-workspace/demo-components.js +26 -33
- package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
- package/dist/esm-node/ultramodern-workspace/effect-api.js +230 -100
- package/dist/esm-node/ultramodern-workspace/fs-io.js +116 -2
- package/dist/esm-node/ultramodern-workspace/generation-result.js +2 -2
- package/dist/esm-node/ultramodern-workspace/index.js +1 -0
- package/dist/esm-node/ultramodern-workspace/mf-validation.js +517 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +49 -21
- package/dist/esm-node/ultramodern-workspace/package-json.js +60 -20
- package/dist/esm-node/ultramodern-workspace/policy.js +2 -2
- package/dist/esm-node/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm-node/ultramodern-workspace/versions.js +3 -1
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +78 -26
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +67 -27
- package/dist/types/locale/en.d.ts +10 -0
- package/dist/types/locale/index.d.ts +20 -0
- package/dist/types/locale/zh.d.ts +10 -0
- package/dist/types/ultramodern-tooling/commands.d.ts +1 -0
- package/dist/types/ultramodern-tooling/config.d.ts +43 -0
- package/dist/types/ultramodern-workspace/add-vertical.d.ts +5 -2
- package/dist/types/ultramodern-workspace/bridge-config.d.ts +64 -0
- package/dist/types/ultramodern-workspace/codesmith.d.ts +1 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +2 -17
- package/dist/types/ultramodern-workspace/descriptors.d.ts +1 -1
- package/dist/types/ultramodern-workspace/fs-io.d.ts +1 -0
- package/dist/types/ultramodern-workspace/index.d.ts +2 -0
- package/dist/types/ultramodern-workspace/mf-validation.d.ts +27 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +1 -0
- package/dist/types/ultramodern-workspace/package-json.d.ts +5 -3
- package/dist/types/ultramodern-workspace/public-api.d.ts +2 -0
- package/dist/types/ultramodern-workspace/types.d.ts +2 -0
- package/dist/types/ultramodern-workspace/versions.d.ts +2 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +2 -7
- package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -2
- package/package.json +5 -3
- package/template-workspace/{.agents → .codex}/skills-lock.json +12 -12
- package/template-workspace/.gitignore.handlebars +3 -0
- package/template-workspace/AGENTS.md.handlebars +11 -5
- package/template-workspace/README.md.handlebars +15 -12
- package/template-workspace/oxfmt.config.ts +1 -0
- package/template-workspace/oxlint.config.ts +1 -0
- package/template-workspace/patches/@tanstack__router-core@1.171.13.patch +51 -0
- package/template-workspace/pnpm-workspace.yaml.handlebars +12 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +96 -69
- package/templates/app/shell-frame.tsx +1 -2
- package/templates/app/ultramodern-route-head.tsx.handlebars +5 -6
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +59 -7
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mjs +339 -0
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +245 -8
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +276 -8
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +35 -10
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +164 -1
- package/templates/workspace-scripts/ultramodern-typecheck.mjs +4 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +949 -63
- /package/template-workspace/{.agents → .codex}/rstackjs-agent-skills-LICENSE +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsbuild-best-practices/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/command-map.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/common-analysis-patterns.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rslib-best-practices/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rslib-modern-package/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-best-practices/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/SKILL.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/references/bottlenecks.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/references/tracing-guide.md +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/scripts/analyze_trace.js +0 -0
- /package/template-workspace/{.agents → .codex}/skills/rstest-best-practices/SKILL.md +0 -0
|
@@ -2,29 +2,28 @@ import node_fs from "node:fs";
|
|
|
2
2
|
import node_os from "node:os";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { WORKSPACE_PACKAGE_VERSION } from "../ultramodern-package-source.js";
|
|
5
|
+
import { readUltramodernConfig } from "../ultramodern-tooling/config.js";
|
|
5
6
|
import { createAppEnvDts, createAppRuntimeConfig, createShellFrameComponent } from "./app-files.js";
|
|
6
|
-
import { createGeneratedContract } from "./contracts.js";
|
|
7
7
|
import { createShellPage, createShellRemoteComponents } from "./demo-components.js";
|
|
8
|
-
import {
|
|
8
|
+
import { ULTRAMODERN_CONFIG_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
|
|
9
9
|
import { createShellEffectClient, effectApiTopologyMetadata } from "./effect-api.js";
|
|
10
|
-
import { readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
|
|
10
|
+
import { formatGeneratedWorkspaceFiles, readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
|
|
11
11
|
import { createFileSnapshot, createGenerationResult, diffFileSnapshots } from "./generation-result.js";
|
|
12
12
|
import { createAppPublicLocaleMessages } from "./locales.js";
|
|
13
13
|
import { createShellModuleFederationConfig } from "./module-federation.js";
|
|
14
14
|
import { assertUniqueTailwindPrefixes, normalizePath, packageName, toEnvSegment, toKebabCase, toPackageScope, toPascalCase } from "./naming.js";
|
|
15
15
|
import { runCodeSmithOverlays } from "./overlays.js";
|
|
16
|
-
import { createAppPackage, createAppTsConfig, createRootPackageJson, createRootTsConfig } from "./package-json.js";
|
|
16
|
+
import { createAppMfTypesTsConfig, createAppPackage, createAppTsConfig, createRootPackageJson, createRootTsConfig } from "./package-json.js";
|
|
17
17
|
import { resolvePackageSource } from "./package-source.js";
|
|
18
18
|
import { createCloudflareDeployContract } from "./policy.js";
|
|
19
19
|
import { createPublicWebAppArtifacts, rewriteWorkspaceAssetsForApp } from "./public-surface.js";
|
|
20
20
|
import { isRecord } from "./types.js";
|
|
21
21
|
import { writeGeneratedWorkspaceScripts } from "./workspace-scripts.js";
|
|
22
|
-
import { writeApp } from "./write-workspace.js";
|
|
22
|
+
import { createCompactUltramodernConfig, writeApp } from "./write-workspace.js";
|
|
23
23
|
const FIRST_VERTICAL_PORT = 4101;
|
|
24
24
|
const TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
25
25
|
const OWNERSHIP_PATH = 'topology/ownership.json';
|
|
26
26
|
const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
|
|
27
|
-
const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
|
|
28
27
|
function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
29
28
|
if (packageSource) return resolvePackageSource({
|
|
30
29
|
targetDir: workspaceRoot,
|
|
@@ -32,35 +31,21 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
|
32
31
|
modernVersion,
|
|
33
32
|
packageSource
|
|
34
33
|
});
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
const metadata = readJsonFile(metadataPath);
|
|
42
|
-
const aliases = metadata.modernPackages?.aliases ?? {};
|
|
43
|
-
const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
|
|
44
|
-
const firstPackage = Object.keys(aliases)[0];
|
|
45
|
-
const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
|
|
46
|
-
const unscopedName = firstPackage?.split('/').at(-1) ?? '';
|
|
47
|
-
const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
|
|
48
|
-
const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
|
|
49
|
-
return {
|
|
50
|
-
strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
|
|
51
|
-
modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : modernVersion,
|
|
52
|
-
registry: metadata.modernPackages?.registry,
|
|
53
|
-
aliasScope,
|
|
54
|
-
aliasPackageNamePrefix
|
|
55
|
-
};
|
|
34
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
35
|
+
if (node_fs.existsSync(compactPath)) {
|
|
36
|
+
const compactConfig = readUltramodernConfig(workspaceRoot);
|
|
37
|
+
if (compactConfig.packageSource) return compactConfig.packageSource;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Missing UltraModern workspace file: ${compactPath}`);
|
|
56
40
|
}
|
|
57
41
|
function existingTailwindEnabled(workspaceRoot) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
43
|
+
if (node_fs.existsSync(compactPath)) return readUltramodernConfig(workspaceRoot).features.tailwind;
|
|
44
|
+
throw new Error(`Missing UltraModern workspace file: ${compactPath}`);
|
|
45
|
+
}
|
|
46
|
+
function existingBridgeConfig(workspaceRoot) {
|
|
47
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
48
|
+
return node_fs.existsSync(compactPath) ? readUltramodernConfig(workspaceRoot).bridge : void 0;
|
|
64
49
|
}
|
|
65
50
|
function assertValidVerticalName(name) {
|
|
66
51
|
const normalized = toKebabCase(name);
|
|
@@ -74,18 +59,19 @@ function nextAvailablePort(ports) {
|
|
|
74
59
|
function assertCanCreate(workspaceRoot, relativePath) {
|
|
75
60
|
if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
|
|
76
61
|
}
|
|
77
|
-
function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes) {
|
|
62
|
+
function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes, bridge) {
|
|
78
63
|
const packagePath = node_path.join(workspaceRoot, 'package.json');
|
|
79
64
|
const rootPackage = readJsonFile(packagePath);
|
|
80
|
-
const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes);
|
|
65
|
+
const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes, bridge);
|
|
81
66
|
rootPackage.scripts = generatedRootPackage.scripts;
|
|
82
67
|
writeJsonFile(packagePath, rootPackage);
|
|
83
68
|
}
|
|
84
|
-
function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes) {
|
|
69
|
+
function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes, bridge) {
|
|
85
70
|
const shellHost = createShellHost(remotes);
|
|
86
71
|
const publicWeb = createPublicWebAppArtifacts(shellHost);
|
|
87
|
-
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
|
|
72
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes, bridge));
|
|
88
73
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/tsconfig.json`), createAppTsConfig(shellHost, remotes));
|
|
74
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/tsconfig.mf-types.json`), createAppMfTypesTsConfig(shellHost));
|
|
89
75
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
|
|
90
76
|
writeFileReplacing(workspaceRoot, publicWeb.jsonLdHelperFile.path, publicWeb.jsonLdHelperFile.content);
|
|
91
77
|
writeFileReplacing(workspaceRoot, publicWeb.routeMetadataFile.path, publicWeb.routeMetadataFile.content);
|
|
@@ -101,7 +87,7 @@ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwin
|
|
|
101
87
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/[lang]/page.tsx`, createShellPage(remotes));
|
|
102
88
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/vertical-components.tsx`, createShellRemoteComponents(scope, remotes));
|
|
103
89
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
|
|
104
|
-
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/
|
|
90
|
+
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/api/vertical-clients.ts`, createShellEffectClient(scope, remotes));
|
|
105
91
|
}
|
|
106
92
|
function addShellZephyrDependency(workspaceRoot, scope, remote) {
|
|
107
93
|
const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
|
|
@@ -157,10 +143,11 @@ function verticalsFromTopology(topology, ports) {
|
|
|
157
143
|
return (topology.verticals ?? []).map((vertical)=>{
|
|
158
144
|
const domain = vertical.domain ?? String(vertical.id);
|
|
159
145
|
const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
146
|
+
const apiTopology = vertical.api;
|
|
147
|
+
const effectApi = apiTopology?.runtime === 'effect' ? {
|
|
148
|
+
stem: 'string' == typeof apiTopology.basePath ? apiTopology.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
|
|
149
|
+
prefix: apiTopology.bff?.prefix ?? `/${domain}-api`,
|
|
150
|
+
consumedBy: Array.isArray(apiTopology.consumedBy) ? apiTopology.consumedBy : [
|
|
164
151
|
shellApp.id,
|
|
165
152
|
vertical.id
|
|
166
153
|
]
|
|
@@ -202,8 +189,7 @@ function prepareAddUltramodernVertical(options) {
|
|
|
202
189
|
const topology = readRequiredJsonObject(topologyPath);
|
|
203
190
|
const ownership = readRequiredJsonObject(ownershipPath);
|
|
204
191
|
const overlay = readRequiredJsonObject(overlayPath);
|
|
205
|
-
|
|
206
|
-
readRequiredJsonObject(node_path.join(options.workspaceRoot, PACKAGE_SOURCE_METADATA_PATH));
|
|
192
|
+
readRequiredWorkspaceConfig(options.workspaceRoot);
|
|
207
193
|
assertOptionalJsonObject(topology.shell, 'topology.shell', topologyPath);
|
|
208
194
|
assertOptionalJsonArray(topology.verticals, 'topology.verticals', topologyPath);
|
|
209
195
|
assertOptionalJsonArray(ownership.owners, 'ownership.owners', ownershipPath);
|
|
@@ -214,6 +200,7 @@ function prepareAddUltramodernVertical(options) {
|
|
|
214
200
|
const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
|
|
215
201
|
const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
|
|
216
202
|
const enableTailwind = options.enableTailwind ?? existingTailwindEnabled(options.workspaceRoot);
|
|
203
|
+
const bridge = existingBridgeConfig(options.workspaceRoot);
|
|
217
204
|
const existingVerticals = verticalsFromTopology(topology, overlay.ports);
|
|
218
205
|
const port = nextAvailablePort(overlay.ports);
|
|
219
206
|
const vertical = createVerticalDescriptor(name, port);
|
|
@@ -241,6 +228,7 @@ function prepareAddUltramodernVertical(options) {
|
|
|
241
228
|
overlay,
|
|
242
229
|
packageSource,
|
|
243
230
|
enableTailwind,
|
|
231
|
+
bridge,
|
|
244
232
|
vertical,
|
|
245
233
|
updatedVerticals
|
|
246
234
|
};
|
|
@@ -251,6 +239,10 @@ function readRequiredJsonObject(filePath) {
|
|
|
251
239
|
if (!isRecord(value)) throw new Error(`UltraModern workspace file must contain a JSON object: ${filePath}`);
|
|
252
240
|
return value;
|
|
253
241
|
}
|
|
242
|
+
function readRequiredWorkspaceConfig(workspaceRoot) {
|
|
243
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
244
|
+
readRequiredJsonObject(compactPath);
|
|
245
|
+
}
|
|
254
246
|
function assertOptionalJsonObject(value, label, filePath) {
|
|
255
247
|
if (void 0 !== value && !isRecord(value)) throw new Error(`${label} in ${filePath} must be a JSON object`);
|
|
256
248
|
}
|
|
@@ -270,8 +262,8 @@ function validateWorkspaceAppDescriptors(apps) {
|
|
|
270
262
|
if ('number' != typeof app.port || !Number.isFinite(app.port) || app.port <= 0) throw new Error(`Invalid development port for ${appLabel}`);
|
|
271
263
|
assertNonEmptyString(app.mfName, `Module Federation name for ${appLabel}`);
|
|
272
264
|
if (app.effectApi) {
|
|
273
|
-
assertNonEmptyString(app.effectApi.prefix, `
|
|
274
|
-
if (!app.effectApi.prefix.startsWith('/')) throw new Error(`
|
|
265
|
+
assertNonEmptyString(app.effectApi.prefix, `API prefix for ${appLabel}`);
|
|
266
|
+
if (!app.effectApi.prefix.startsWith('/')) throw new Error(`API prefix for ${appLabel} must start with "/"`);
|
|
275
267
|
}
|
|
276
268
|
}
|
|
277
269
|
}
|
|
@@ -281,7 +273,7 @@ function validateUniqueWorkspaceAppDescriptors(apps) {
|
|
|
281
273
|
assertUniqueAppField(apps, 'output path', (app)=>normalizePath(app.directory));
|
|
282
274
|
assertUniqueAppField(apps, 'Module Federation name', (app)=>app.mfName);
|
|
283
275
|
assertUniqueAppField(apps, 'development port', (app)=>String(app.port));
|
|
284
|
-
assertUniqueAppField(apps, '
|
|
276
|
+
assertUniqueAppField(apps, 'API prefix', (app)=>app.effectApi?.prefix);
|
|
285
277
|
assertUniqueAppField(apps, 'manifest environment name', (app)=>'vertical' === app.kind ? createRemoteManifestEnv(app) : void 0);
|
|
286
278
|
}
|
|
287
279
|
function assertUniqueAppField(apps, label, readValue) {
|
|
@@ -355,7 +347,7 @@ function createVerticalPlan(preflight, result) {
|
|
|
355
347
|
shellDependencyChanges: createShellDependencyChanges(scope, vertical),
|
|
356
348
|
generatedContractChanges: [
|
|
357
349
|
{
|
|
358
|
-
path:
|
|
350
|
+
path: ULTRAMODERN_CONFIG_PATH,
|
|
359
351
|
addedAppIds: [
|
|
360
352
|
vertical.id
|
|
361
353
|
],
|
|
@@ -370,7 +362,7 @@ function createDryRunJsonMutations(preflight, manifestUrl) {
|
|
|
370
362
|
{
|
|
371
363
|
path: DEVELOPMENT_OVERLAY_PATH,
|
|
372
364
|
pointer: `/apis/${vertical.id}`,
|
|
373
|
-
description: `Add local
|
|
365
|
+
description: `Add local API URL for ${vertical.id}`,
|
|
374
366
|
value: `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`
|
|
375
367
|
}
|
|
376
368
|
] : [];
|
|
@@ -437,9 +429,14 @@ function createDryRunJsonMutations(preflight, manifestUrl) {
|
|
|
437
429
|
description: `Add ${vertical.id} to the shell TS-Go project references`
|
|
438
430
|
},
|
|
439
431
|
{
|
|
440
|
-
path:
|
|
441
|
-
pointer: '/
|
|
442
|
-
description:
|
|
432
|
+
path: `${shellApp.directory}/tsconfig.mf-types.json`,
|
|
433
|
+
pointer: '/include',
|
|
434
|
+
description: 'Keep shell Module Federation DTS compilation scoped'
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
path: ULTRAMODERN_CONFIG_PATH,
|
|
438
|
+
pointer: '/topology/apps',
|
|
439
|
+
description: `Regenerate compact config with ${vertical.id}`
|
|
443
440
|
}
|
|
444
441
|
];
|
|
445
442
|
}
|
|
@@ -463,8 +460,8 @@ function createShellDependencyChanges(scope, vertical) {
|
|
|
463
460
|
}
|
|
464
461
|
function addUltramodernVertical(options) {
|
|
465
462
|
const beforeFiles = createFileSnapshot(options.workspaceRoot);
|
|
466
|
-
const { scope, topologyPath, ownershipPath, overlayPath, topology, ownership, overlay, packageSource, enableTailwind, vertical, updatedVerticals } = prepareAddUltramodernVertical(options);
|
|
467
|
-
writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind);
|
|
463
|
+
const { scope, topologyPath, ownershipPath, overlayPath, topology, ownership, overlay, packageSource, enableTailwind, bridge, vertical, updatedVerticals } = prepareAddUltramodernVertical(options);
|
|
464
|
+
writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind, updatedVerticals, bridge);
|
|
468
465
|
topology.shell ??= {};
|
|
469
466
|
topology.shell.verticalRefs ??= [];
|
|
470
467
|
topology.shell.verticalRefs.push(vertical.id);
|
|
@@ -487,18 +484,18 @@ function addUltramodernVertical(options) {
|
|
|
487
484
|
writeJsonFile(topologyPath, topology);
|
|
488
485
|
writeJsonFile(ownershipPath, ownership);
|
|
489
486
|
writeJsonFile(overlayPath, overlay);
|
|
490
|
-
writeJsonFile(node_path.join(options.workspaceRoot,
|
|
487
|
+
writeJsonFile(node_path.join(options.workspaceRoot, ULTRAMODERN_CONFIG_PATH), createCompactUltramodernConfig(scope, options.modernVersion, packageSource, [
|
|
491
488
|
{
|
|
492
489
|
...shellApp,
|
|
493
490
|
verticalRefs: updatedVerticals.map((vertical)=>vertical.id)
|
|
494
491
|
},
|
|
495
492
|
...updatedVerticals
|
|
496
|
-
], enableTailwind));
|
|
497
|
-
rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals);
|
|
493
|
+
], enableTailwind, bridge));
|
|
494
|
+
rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals, bridge);
|
|
498
495
|
writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
|
|
499
496
|
addShellZephyrDependency(options.workspaceRoot, scope, vertical);
|
|
500
497
|
addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
|
|
501
|
-
updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals);
|
|
498
|
+
updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals, bridge);
|
|
502
499
|
writeJsonFile(node_path.join(options.workspaceRoot, 'tsconfig.json'), createRootTsConfig([
|
|
503
500
|
{
|
|
504
501
|
...shellApp,
|
|
@@ -506,9 +503,9 @@ function addUltramodernVertical(options) {
|
|
|
506
503
|
},
|
|
507
504
|
...updatedVerticals
|
|
508
505
|
]));
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
const
|
|
506
|
+
const preliminaryAfterFiles = createFileSnapshot(options.workspaceRoot);
|
|
507
|
+
const preliminaryDiff = diffFileSnapshots(beforeFiles, preliminaryAfterFiles);
|
|
508
|
+
const preliminaryResult = createGenerationResult({
|
|
512
509
|
operation: 'vertical',
|
|
513
510
|
workspaceRoot: options.workspaceRoot,
|
|
514
511
|
packageScope: scope,
|
|
@@ -516,14 +513,32 @@ function addUltramodernVertical(options) {
|
|
|
516
513
|
createdApps: [
|
|
517
514
|
vertical
|
|
518
515
|
],
|
|
519
|
-
createdPaths,
|
|
520
|
-
rewrittenPaths
|
|
516
|
+
createdPaths: preliminaryDiff.createdPaths,
|
|
517
|
+
rewrittenPaths: preliminaryDiff.rewrittenPaths
|
|
521
518
|
});
|
|
522
519
|
runCodeSmithOverlays({
|
|
523
520
|
workspaceRoot: options.workspaceRoot,
|
|
524
521
|
overlays: options.overlays,
|
|
525
|
-
result
|
|
522
|
+
result: preliminaryResult
|
|
523
|
+
});
|
|
524
|
+
const afterOverlaysFiles = createFileSnapshot(options.workspaceRoot);
|
|
525
|
+
const changedPaths = diffFileSnapshots(beforeFiles, afterOverlaysFiles);
|
|
526
|
+
formatGeneratedWorkspaceFiles(options.workspaceRoot, [
|
|
527
|
+
...changedPaths.createdPaths,
|
|
528
|
+
...changedPaths.rewrittenPaths
|
|
529
|
+
]);
|
|
530
|
+
const afterFiles = createFileSnapshot(options.workspaceRoot);
|
|
531
|
+
const { createdPaths, rewrittenPaths } = diffFileSnapshots(beforeFiles, afterFiles);
|
|
532
|
+
return createGenerationResult({
|
|
533
|
+
operation: 'vertical',
|
|
534
|
+
workspaceRoot: options.workspaceRoot,
|
|
535
|
+
packageScope: scope,
|
|
536
|
+
packageSource,
|
|
537
|
+
createdApps: [
|
|
538
|
+
vertical
|
|
539
|
+
],
|
|
540
|
+
createdPaths,
|
|
541
|
+
rewrittenPaths
|
|
526
542
|
});
|
|
527
|
-
return result;
|
|
528
543
|
}
|
|
529
|
-
export { addShellWorkspaceDependency, addShellZephyrDependency, addUltramodernVertical, assertCanCreate, assertValidVerticalName, existingPackageSource, existingTailwindEnabled, nextAvailablePort, ownershipEntry, planUltramodernVertical, prepareAddUltramodernVertical, rewriteShellAppFiles, updateRootWorkspaceScripts, verticalTopologyEntry, verticalsFromTopology };
|
|
544
|
+
export { addShellWorkspaceDependency, addShellZephyrDependency, addUltramodernVertical, assertCanCreate, assertValidVerticalName, existingBridgeConfig, existingPackageSource, existingTailwindEnabled, nextAvailablePort, ownershipEntry, planUltramodernVertical, prepareAddUltramodernVertical, rewriteShellAppFiles, updateRootWorkspaceScripts, verticalTopologyEntry, verticalsFromTopology };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const ultramodernBridgeCliFlags = {
|
|
2
|
+
enabled: '--bridge',
|
|
3
|
+
parentRoot: '--bridge-parent-root',
|
|
4
|
+
workspacePackage: '--bridge-workspace-package',
|
|
5
|
+
workspacePackageName: '--bridge-workspace-package-name',
|
|
6
|
+
testAlias: '--bridge-test-alias',
|
|
7
|
+
dependency: '--bridge-dependency',
|
|
8
|
+
lockfilePolicy: '--bridge-lockfile-policy',
|
|
9
|
+
gate: '--bridge-gate',
|
|
10
|
+
gateCwd: '--bridge-gate-cwd',
|
|
11
|
+
reactSingleton: '--bridge-react-singleton'
|
|
12
|
+
};
|
|
13
|
+
const ultramodernBridgeCliValueFlags = [
|
|
14
|
+
ultramodernBridgeCliFlags.parentRoot,
|
|
15
|
+
ultramodernBridgeCliFlags.workspacePackage,
|
|
16
|
+
ultramodernBridgeCliFlags.workspacePackageName,
|
|
17
|
+
ultramodernBridgeCliFlags.testAlias,
|
|
18
|
+
ultramodernBridgeCliFlags.dependency,
|
|
19
|
+
ultramodernBridgeCliFlags.lockfilePolicy,
|
|
20
|
+
ultramodernBridgeCliFlags.gate,
|
|
21
|
+
ultramodernBridgeCliFlags.gateCwd,
|
|
22
|
+
ultramodernBridgeCliFlags.reactSingleton
|
|
23
|
+
];
|
|
24
|
+
const ultramodernBridgeCliBooleanFlags = [
|
|
25
|
+
ultramodernBridgeCliFlags.enabled
|
|
26
|
+
];
|
|
27
|
+
const ultramodernBridgeLockfilePolicies = [
|
|
28
|
+
'nested',
|
|
29
|
+
'parent'
|
|
30
|
+
];
|
|
31
|
+
const defaultReactSingletons = [
|
|
32
|
+
'react',
|
|
33
|
+
'react-dom'
|
|
34
|
+
];
|
|
35
|
+
function hasUltramodernBridgeCliOptions(args) {
|
|
36
|
+
return args.some((arg)=>[
|
|
37
|
+
...ultramodernBridgeCliBooleanFlags,
|
|
38
|
+
...ultramodernBridgeCliValueFlags
|
|
39
|
+
].some((flag)=>arg === flag || arg.startsWith(`${flag}=`)));
|
|
40
|
+
}
|
|
41
|
+
function parseUltramodernBridgeCliOptions(args) {
|
|
42
|
+
rejectBooleanFlagValues(args);
|
|
43
|
+
if (!hasUltramodernBridgeCliOptions(args)) return;
|
|
44
|
+
const parentRoot = readSingleValue(args, ultramodernBridgeCliFlags.parentRoot);
|
|
45
|
+
const lockfilePolicy = readSingleValue(args, ultramodernBridgeCliFlags.lockfilePolicy) ?? 'nested';
|
|
46
|
+
const workspacePackages = readWorkspacePackages(args);
|
|
47
|
+
const gates = readBridgeGates(args);
|
|
48
|
+
const reactSingletons = readCsvOptionValues(args, ultramodernBridgeCliFlags.reactSingleton);
|
|
49
|
+
return normalizeUltramodernBridgeConfig({
|
|
50
|
+
enabled: true,
|
|
51
|
+
parentRoot: requireNonEmptyValue(parentRoot, ultramodernBridgeCliFlags.parentRoot),
|
|
52
|
+
workspacePackages,
|
|
53
|
+
dependencies: readCsvOptionValues(args, ultramodernBridgeCliFlags.dependency),
|
|
54
|
+
lockfilePolicy: parseLockfilePolicy(lockfilePolicy),
|
|
55
|
+
gates,
|
|
56
|
+
reactSingletons: reactSingletons.length > 0 ? reactSingletons : void 0
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function normalizeUltramodernBridgeConfig(bridge) {
|
|
60
|
+
if (!bridge || false === bridge.enabled) return;
|
|
61
|
+
const parentRoot = requireNonEmptyValue(bridge.parentRoot, 'bridge.parentRoot');
|
|
62
|
+
const workspacePackages = normalizeWorkspacePackages(bridge.workspacePackages);
|
|
63
|
+
const dependencies = uniqueNonEmptyValues(bridge.dependencies, 'bridge.dependencies');
|
|
64
|
+
const gates = normalizeBridgeGates(bridge.gates);
|
|
65
|
+
const reactSingletons = bridge.reactSingletons && bridge.reactSingletons.length > 0 ? uniqueNonEmptyValues(bridge.reactSingletons, 'bridge.reactSingletons') : [
|
|
66
|
+
...defaultReactSingletons
|
|
67
|
+
];
|
|
68
|
+
if (0 === workspacePackages.length) throw new Error('Bridge mode requires at least one bridge.workspacePackages entry.');
|
|
69
|
+
if (0 === dependencies.length) throw new Error('Bridge mode requires at least one explicit bridge.dependencies package name.');
|
|
70
|
+
if (0 === gates.length) throw new Error('Bridge mode requires at least one bridge.gates delegated parent command.');
|
|
71
|
+
validateParentPackageCoverage(workspacePackages, dependencies);
|
|
72
|
+
validateReactSingletons(reactSingletons);
|
|
73
|
+
return {
|
|
74
|
+
enabled: true,
|
|
75
|
+
parentRoot,
|
|
76
|
+
workspacePackages,
|
|
77
|
+
dependencies,
|
|
78
|
+
lockfilePolicy: parseLockfilePolicy(bridge.lockfilePolicy ?? 'nested'),
|
|
79
|
+
gates,
|
|
80
|
+
reactSingletons
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function rejectBooleanFlagValues(args) {
|
|
84
|
+
for (const flag of ultramodernBridgeCliBooleanFlags)if (args.some((arg)=>arg.startsWith(`${flag}=`))) throw new Error(`${flag} does not accept a value.`);
|
|
85
|
+
}
|
|
86
|
+
function readRepeatedOptionValues(args, flag) {
|
|
87
|
+
const values = [];
|
|
88
|
+
for(let index = 0; index < args.length; index++){
|
|
89
|
+
const arg = args[index];
|
|
90
|
+
if (arg === flag) {
|
|
91
|
+
const value = args[index + 1];
|
|
92
|
+
if (!value || value.startsWith('-')) throw new Error(`${flag} requires a value.`);
|
|
93
|
+
values.push(value);
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg.startsWith(`${flag}=`)) values.push(arg.slice(flag.length + 1));
|
|
98
|
+
}
|
|
99
|
+
return values;
|
|
100
|
+
}
|
|
101
|
+
function readSingleValue(args, flag) {
|
|
102
|
+
const values = readRepeatedOptionValues(args, flag);
|
|
103
|
+
if (values.length > 1) throw new Error(`${flag} can be provided only once.`);
|
|
104
|
+
return values[0];
|
|
105
|
+
}
|
|
106
|
+
function readCsvOptionValues(args, flag) {
|
|
107
|
+
return readRepeatedOptionValues(args, flag).flatMap((value)=>value.split(',').map((entry)=>entry.trim()).filter(Boolean));
|
|
108
|
+
}
|
|
109
|
+
function requireNonEmptyValue(value, label) {
|
|
110
|
+
if ('string' != typeof value || 0 === value.trim().length) throw new Error(`${label} must be a non-empty string.`);
|
|
111
|
+
return value.trim();
|
|
112
|
+
}
|
|
113
|
+
function parseLockfilePolicy(value) {
|
|
114
|
+
if (!ultramodernBridgeLockfilePolicies.includes(value)) throw new Error(`${ultramodernBridgeCliFlags.lockfilePolicy} must be "nested" or "parent".`);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function readWorkspacePackages(args) {
|
|
118
|
+
const drafts = new Map();
|
|
119
|
+
const getDraft = (pattern)=>{
|
|
120
|
+
const normalizedPattern = requireNonEmptyValue(pattern, ultramodernBridgeCliFlags.workspacePackage);
|
|
121
|
+
const existing = drafts.get(normalizedPattern);
|
|
122
|
+
if (existing) return existing;
|
|
123
|
+
const draft = {
|
|
124
|
+
pattern: normalizedPattern,
|
|
125
|
+
packageNames: [],
|
|
126
|
+
testAliases: []
|
|
127
|
+
};
|
|
128
|
+
drafts.set(normalizedPattern, draft);
|
|
129
|
+
return draft;
|
|
130
|
+
};
|
|
131
|
+
for (const pattern of readRepeatedOptionValues(args, ultramodernBridgeCliFlags.workspacePackage))getDraft(pattern);
|
|
132
|
+
for (const value of readRepeatedOptionValues(args, ultramodernBridgeCliFlags.workspacePackageName)){
|
|
133
|
+
const { left: pattern, right: packageNames } = splitAssignment(value, ultramodernBridgeCliFlags.workspacePackageName, '<pattern>=<package-name>[,<package-name>]');
|
|
134
|
+
getDraft(pattern).packageNames.push(...packageNames.split(',').map((packageName)=>packageName.trim()).filter(Boolean));
|
|
135
|
+
}
|
|
136
|
+
for (const value of readRepeatedOptionValues(args, ultramodernBridgeCliFlags.testAlias)){
|
|
137
|
+
const separator = value.indexOf(':');
|
|
138
|
+
if (-1 === separator) throw new Error(`${ultramodernBridgeCliFlags.testAlias} must use <pattern>:<alias>=<target>.`);
|
|
139
|
+
const pattern = value.slice(0, separator);
|
|
140
|
+
const aliasDefinition = value.slice(separator + 1);
|
|
141
|
+
const { left: alias, right: target } = splitAssignment(aliasDefinition, ultramodernBridgeCliFlags.testAlias, '<pattern>:<alias>=<target>');
|
|
142
|
+
getDraft(pattern).testAliases.push({
|
|
143
|
+
alias: requireNonEmptyValue(alias, `${ultramodernBridgeCliFlags.testAlias} alias`),
|
|
144
|
+
target: requireNonEmptyValue(target, `${ultramodernBridgeCliFlags.testAlias} target`)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return normalizeWorkspacePackages([
|
|
148
|
+
...drafts.values()
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
function readBridgeGates(args) {
|
|
152
|
+
const gates = new Map();
|
|
153
|
+
const getGate = (name)=>{
|
|
154
|
+
const normalizedName = requireNonEmptyValue(name, `${ultramodernBridgeCliFlags.gate} name`);
|
|
155
|
+
const existing = gates.get(normalizedName);
|
|
156
|
+
if (existing) return existing;
|
|
157
|
+
const draft = {
|
|
158
|
+
name: normalizedName
|
|
159
|
+
};
|
|
160
|
+
gates.set(normalizedName, draft);
|
|
161
|
+
return draft;
|
|
162
|
+
};
|
|
163
|
+
for (const value of readRepeatedOptionValues(args, ultramodernBridgeCliFlags.gate)){
|
|
164
|
+
const { left: name, right: command } = splitAssignment(value, ultramodernBridgeCliFlags.gate, '<name>=<command>');
|
|
165
|
+
getGate(name).command = requireNonEmptyValue(command, `${ultramodernBridgeCliFlags.gate} command`);
|
|
166
|
+
}
|
|
167
|
+
for (const value of readRepeatedOptionValues(args, ultramodernBridgeCliFlags.gateCwd)){
|
|
168
|
+
const { left: name, right: cwd } = splitAssignment(value, ultramodernBridgeCliFlags.gateCwd, '<name>=<cwd>');
|
|
169
|
+
getGate(name).cwd = requireNonEmptyValue(cwd, `${ultramodernBridgeCliFlags.gateCwd} cwd`);
|
|
170
|
+
}
|
|
171
|
+
const missingCommand = [
|
|
172
|
+
...gates.values()
|
|
173
|
+
].find((gate)=>!gate.command);
|
|
174
|
+
if (missingCommand) throw new Error(`${ultramodernBridgeCliFlags.gateCwd} references "${missingCommand.name}" without a matching ${ultramodernBridgeCliFlags.gate}.`);
|
|
175
|
+
return normalizeBridgeGates([
|
|
176
|
+
...gates.values()
|
|
177
|
+
].map((gate)=>({
|
|
178
|
+
name: gate.name,
|
|
179
|
+
command: gate.command,
|
|
180
|
+
cwd: gate.cwd
|
|
181
|
+
})));
|
|
182
|
+
}
|
|
183
|
+
function splitAssignment(value, flag, usage) {
|
|
184
|
+
const separator = value.indexOf('=');
|
|
185
|
+
if (-1 === separator) throw new Error(`${flag} must use ${usage}.`);
|
|
186
|
+
return {
|
|
187
|
+
left: value.slice(0, separator),
|
|
188
|
+
right: value.slice(separator + 1)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function normalizeWorkspacePackages(workspacePackages) {
|
|
192
|
+
return workspacePackages.map((entry, index)=>{
|
|
193
|
+
const label = `bridge.workspacePackages[${index}]`;
|
|
194
|
+
const packageNames = entry.packageNames && entry.packageNames.length > 0 ? uniqueNonEmptyValues(entry.packageNames, `${label}.packageNames`) : void 0;
|
|
195
|
+
const testAliases = entry.testAliases && entry.testAliases.length > 0 ? normalizeTestAliases(entry.testAliases, label) : void 0;
|
|
196
|
+
return {
|
|
197
|
+
pattern: requireNonEmptyValue(entry.pattern, `${label}.pattern`),
|
|
198
|
+
...packageNames ? {
|
|
199
|
+
packageNames
|
|
200
|
+
} : {},
|
|
201
|
+
...testAliases ? {
|
|
202
|
+
testAliases
|
|
203
|
+
} : {}
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function validateParentPackageCoverage(workspacePackages, dependencies) {
|
|
208
|
+
const declaredPackages = new Set();
|
|
209
|
+
for (const [index, entry] of workspacePackages.entries()){
|
|
210
|
+
const label = `bridge.workspacePackages[${index}]`;
|
|
211
|
+
const packageNames = entry.packageNames ?? [];
|
|
212
|
+
if (0 === packageNames.length && (entry.testAliases ?? []).length > 0) throw new Error(`${label}.packageNames must declare the parent package names covered by "${entry.pattern}" before testAliases can be checked.`);
|
|
213
|
+
for (const packageName of packageNames)declaredPackages.add(packageName);
|
|
214
|
+
for (const alias of entry.testAliases ?? []){
|
|
215
|
+
if (!packageNames.some((packageName)=>aliasMatchesPackage(alias.alias, packageName))) throw new Error(`${label}.testAliases entry "${alias.alias}" must match one of ${label}.packageNames.`);
|
|
216
|
+
if (pointsAtBuildOutput(alias.target)) throw new Error(`${label}.testAliases target "${alias.target}" must point at source files, not dist output.`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const missingDependencies = dependencies.filter((dependency)=>!declaredPackages.has(dependency));
|
|
220
|
+
if (declaredPackages.size > 0 && missingDependencies.length > 0) throw new Error(`Bridge mode dependencies must be declared by bridge.workspacePackages[].packageNames: ${missingDependencies.join(', ')}.`);
|
|
221
|
+
}
|
|
222
|
+
function aliasMatchesPackage(alias, packageName) {
|
|
223
|
+
return alias === packageName || alias.startsWith(`${packageName}/`);
|
|
224
|
+
}
|
|
225
|
+
function pointsAtBuildOutput(target) {
|
|
226
|
+
return target.split(/[\\/]/u).includes('dist');
|
|
227
|
+
}
|
|
228
|
+
function validateReactSingletons(reactSingletons) {
|
|
229
|
+
const missing = defaultReactSingletons.filter((packageName)=>!reactSingletons.includes(packageName));
|
|
230
|
+
if (missing.length > 0) throw new Error(`Bridge mode React singleton/dedupe declarations must include ${defaultReactSingletons.join(' and ')}. Missing: ${missing.join(', ')}.`);
|
|
231
|
+
}
|
|
232
|
+
function normalizeTestAliases(testAliases, parentLabel) {
|
|
233
|
+
const aliases = new Map();
|
|
234
|
+
for (const [index, alias] of testAliases.entries()){
|
|
235
|
+
const label = `${parentLabel}.testAliases[${index}]`;
|
|
236
|
+
const normalizedAlias = requireNonEmptyValue(alias.alias, `${label}.alias`);
|
|
237
|
+
aliases.set(normalizedAlias, {
|
|
238
|
+
alias: normalizedAlias,
|
|
239
|
+
target: requireNonEmptyValue(alias.target, `${label}.target`)
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return [
|
|
243
|
+
...aliases.values()
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
function normalizeBridgeGates(gates) {
|
|
247
|
+
const normalized = new Map();
|
|
248
|
+
for (const [index, gate] of gates.entries()){
|
|
249
|
+
const label = `bridge.gates[${index}]`;
|
|
250
|
+
const name = requireNonEmptyValue(gate.name, `${label}.name`);
|
|
251
|
+
normalized.set(name, {
|
|
252
|
+
name,
|
|
253
|
+
command: requireNonEmptyValue(gate.command, `${label}.command`),
|
|
254
|
+
...gate.cwd ? {
|
|
255
|
+
cwd: requireNonEmptyValue(gate.cwd, `${label}.cwd`)
|
|
256
|
+
} : {}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return [
|
|
260
|
+
...normalized.values()
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
function uniqueNonEmptyValues(values, label) {
|
|
264
|
+
const unique = new Set();
|
|
265
|
+
for (const [index, value] of values.entries())unique.add(requireNonEmptyValue(value, `${label}[${index}]`));
|
|
266
|
+
return [
|
|
267
|
+
...unique
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
export { hasUltramodernBridgeCliOptions, normalizeUltramodernBridgeConfig, parseUltramodernBridgeCliOptions, ultramodernBridgeCliBooleanFlags, ultramodernBridgeCliFlags, ultramodernBridgeCliValueFlags, ultramodernBridgeLockfilePolicies };
|