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