@bleedingdev/modern-js-create 3.4.0-ultramodern.11 → 3.4.0-ultramodern.13
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 +36 -3
- package/dist/cjs/locale/en.cjs +10 -0
- package/dist/cjs/locale/zh.cjs +10 -0
- package/dist/cjs/ultramodern-tooling/commands.cjs +185 -0
- package/dist/cjs/ultramodern-tooling/config.cjs +248 -0
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +39 -18
- 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 +119 -653
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +4 -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 +572 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +14 -0
- package/dist/cjs/ultramodern-workspace/package-json.cjs +26 -12
- package/dist/cjs/ultramodern-workspace/public-api.cjs +4 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +74 -44
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +37 -8
- package/dist/esm/index.js +36 -3
- package/dist/esm/locale/en.js +10 -0
- package/dist/esm/locale/zh.js +10 -0
- package/dist/esm/ultramodern-tooling/commands.js +132 -0
- package/dist/esm/ultramodern-tooling/config.js +196 -0
- package/dist/esm/ultramodern-workspace/add-vertical.js +39 -21
- 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 +122 -608
- package/dist/esm/ultramodern-workspace/descriptors.js +2 -1
- 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 +517 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +14 -0
- package/dist/esm/ultramodern-workspace/package-json.js +26 -12
- package/dist/esm/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +75 -27
- package/dist/esm/ultramodern-workspace/write-workspace.js +38 -12
- package/dist/esm-node/index.js +36 -3
- package/dist/esm-node/locale/en.js +10 -0
- package/dist/esm-node/locale/zh.js +10 -0
- package/dist/esm-node/ultramodern-tooling/commands.js +133 -0
- package/dist/esm-node/ultramodern-tooling/config.js +197 -0
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +39 -21
- 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 +122 -608
- package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -1
- 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 +518 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +14 -0
- package/dist/esm-node/ultramodern-workspace/package-json.js +26 -12
- package/dist/esm-node/ultramodern-workspace/public-api.js +1 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +75 -27
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +38 -12
- 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 -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/package-json.d.ts +4 -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/workspace-scripts.d.ts +1 -7
- package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -2
- package/package.json +3 -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/scripts/bootstrap-agent-skills.mjs +77 -67
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +56 -3
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +239 -4
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +282 -4
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +172 -1
- package/templates/workspace-scripts/ultramodern-typecheck.mjs +4 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +783 -26
- /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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import node_fs from "node:fs";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { normalizeUltramodernBridgeConfig } from "../ultramodern-workspace/bridge-config.js";
|
|
5
|
+
import { GENERATED_CONTRACT_PATH, ULTRAMODERN_CONFIG_PATH, createNeutralOwnership, shellApp } from "../ultramodern-workspace/descriptors.js";
|
|
6
|
+
import { toKebabCase } from "../ultramodern-workspace/naming.js";
|
|
7
|
+
const PACKAGE_SOURCE_METADATA_PATH = '.modernjs/ultramodern-package-source.json';
|
|
8
|
+
const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
|
|
9
|
+
function readJsonObject(filePath) {
|
|
10
|
+
const value = JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
|
|
11
|
+
if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error(`UltraModern config must contain a JSON object: ${filePath}`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function readOptionalJsonObject(filePath) {
|
|
15
|
+
return node_fs.existsSync(filePath) ? readJsonObject(filePath) : {};
|
|
16
|
+
}
|
|
17
|
+
function packageScopeFromRoot(workspaceRoot) {
|
|
18
|
+
const rootPackage = readOptionalJsonObject(node_path.join(workspaceRoot, 'package.json'));
|
|
19
|
+
return 'string' == typeof rootPackage.name && rootPackage.name.length > 0 ? rootPackage.name : node_path.basename(workspaceRoot);
|
|
20
|
+
}
|
|
21
|
+
function packageSourceFromMetadata(workspaceRoot) {
|
|
22
|
+
const metadataPath = node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH);
|
|
23
|
+
if (!node_fs.existsSync(metadataPath)) return;
|
|
24
|
+
const metadata = readJsonObject(metadataPath);
|
|
25
|
+
const aliases = metadata.modernPackages?.aliases ?? {};
|
|
26
|
+
const firstAlias = Object.values(aliases).find((value)=>'string' == typeof value);
|
|
27
|
+
const firstPackage = Object.keys(aliases)[0];
|
|
28
|
+
const aliasScope = firstAlias?.match(/^@([^/]+)\//)?.[1];
|
|
29
|
+
const unscopedName = firstPackage?.split('/').at(-1) ?? '';
|
|
30
|
+
const aliasUnscopedName = firstAlias?.split('/').at(-1) ?? '';
|
|
31
|
+
const aliasPackageNamePrefix = aliasUnscopedName && unscopedName && aliasUnscopedName.endsWith(unscopedName) ? aliasUnscopedName.slice(0, -unscopedName.length) : void 0;
|
|
32
|
+
return {
|
|
33
|
+
strategy: 'install' === metadata.strategy ? 'install' : 'workspace',
|
|
34
|
+
modernPackageVersion: 'string' == typeof metadata.modernPackages?.specifier ? metadata.modernPackages.specifier : 'workspace:*',
|
|
35
|
+
registry: 'string' == typeof metadata.modernPackages?.registry ? metadata.modernPackages.registry : void 0,
|
|
36
|
+
aliasScope,
|
|
37
|
+
aliasPackageNamePrefix
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function readOverlayPorts(workspaceRoot) {
|
|
41
|
+
const overlayPath = node_path.join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
|
|
42
|
+
if (!node_fs.existsSync(overlayPath)) return {};
|
|
43
|
+
const overlay = readJsonObject(overlayPath);
|
|
44
|
+
const ports = overlay.ports;
|
|
45
|
+
if (null === ports || 'object' != typeof ports || Array.isArray(ports)) return {};
|
|
46
|
+
return Object.fromEntries(Object.entries(ports).filter((entry)=>'number' == typeof entry[1]));
|
|
47
|
+
}
|
|
48
|
+
function normalizeCompactConfig(workspaceRoot, sourcePath, config) {
|
|
49
|
+
const packageSource = config.packageSource && 'object' == typeof config.packageSource ? {
|
|
50
|
+
strategy: 'install' === config.packageSource.strategy ? 'install' : 'workspace',
|
|
51
|
+
modernPackageVersion: 'string' == typeof config.packageSource.modernPackageVersion ? config.packageSource.modernPackageVersion : 'workspace:*',
|
|
52
|
+
registry: 'string' == typeof config.packageSource.registry ? config.packageSource.registry : void 0,
|
|
53
|
+
aliasScope: 'string' == typeof config.packageSource.aliasScope ? config.packageSource.aliasScope : void 0,
|
|
54
|
+
aliasPackageNamePrefix: 'string' == typeof config.packageSource.aliasPackageNamePrefix ? config.packageSource.aliasPackageNamePrefix : void 0
|
|
55
|
+
} : packageSourceFromMetadata(workspaceRoot);
|
|
56
|
+
return {
|
|
57
|
+
schemaVersion: 'number' == typeof config.schemaVersion ? config.schemaVersion : 1,
|
|
58
|
+
profile: 'string' == typeof config.profile ? config.profile : void 0,
|
|
59
|
+
source: 'compact',
|
|
60
|
+
sourcePath,
|
|
61
|
+
workspace: {
|
|
62
|
+
packageScope: 'string' == typeof config.workspace?.packageScope ? config.workspace.packageScope : packageScopeFromRoot(workspaceRoot)
|
|
63
|
+
},
|
|
64
|
+
packageSource,
|
|
65
|
+
features: {
|
|
66
|
+
tailwind: config.features?.tailwind !== false
|
|
67
|
+
},
|
|
68
|
+
bridge: normalizeUltramodernBridgeConfig(config.bridge),
|
|
69
|
+
topology: {
|
|
70
|
+
apps: Array.isArray(config.topology?.apps) ? config.topology.apps.map((app)=>({
|
|
71
|
+
id: String(app.id),
|
|
72
|
+
kind: 'vertical' === app.kind ? 'vertical' : 'shell',
|
|
73
|
+
path: 'string' == typeof app.path ? app.path : '.',
|
|
74
|
+
package: 'string' == typeof app.package ? app.package : void 0,
|
|
75
|
+
packageSuffix: 'string' == typeof app.packageSuffix ? app.packageSuffix : void 0,
|
|
76
|
+
displayName: 'string' == typeof app.displayName ? app.displayName : void 0,
|
|
77
|
+
domain: 'string' == typeof app.domain ? app.domain : void 0,
|
|
78
|
+
port: 'number' == typeof app.port ? app.port : void 0,
|
|
79
|
+
portEnv: 'string' == typeof app.portEnv ? app.portEnv : void 0,
|
|
80
|
+
moduleFederation: app.moduleFederation && 'object' == typeof app.moduleFederation ? {
|
|
81
|
+
role: 'remote' === app.moduleFederation.role ? 'remote' : 'host',
|
|
82
|
+
name: 'string' == typeof app.moduleFederation.name ? app.moduleFederation.name : void 0,
|
|
83
|
+
exposes: Array.isArray(app.moduleFederation.exposes) ? app.moduleFederation.exposes.filter((expose)=>'string' == typeof expose) : void 0,
|
|
84
|
+
verticalRefs: Array.isArray(app.moduleFederation.verticalRefs) ? app.moduleFederation.verticalRefs.filter((ref)=>'string' == typeof ref) : void 0,
|
|
85
|
+
hostOnly: true === app.moduleFederation.hostOnly,
|
|
86
|
+
noExposes: true === app.moduleFederation.noExposes
|
|
87
|
+
} : void 0,
|
|
88
|
+
effectApi: app.effectApi && 'object' == typeof app.effectApi ? {
|
|
89
|
+
stem: 'string' == typeof app.effectApi.stem ? app.effectApi.stem : String(app.id),
|
|
90
|
+
prefix: 'string' == typeof app.effectApi.prefix ? app.effectApi.prefix : `/${String(app.id)}-api`,
|
|
91
|
+
consumedBy: Array.isArray(app.effectApi.consumedBy) ? app.effectApi.consumedBy.filter((consumer)=>'string' == typeof consumer) : [
|
|
92
|
+
shellApp.id,
|
|
93
|
+
String(app.id)
|
|
94
|
+
]
|
|
95
|
+
} : void 0
|
|
96
|
+
})) : []
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function adaptLegacyContract(workspaceRoot, sourcePath, contract) {
|
|
101
|
+
const ports = readOverlayPorts(workspaceRoot);
|
|
102
|
+
const apps = Array.isArray(contract.apps) ? contract.apps : [];
|
|
103
|
+
const shell = apps.find((app)=>app?.id === shellApp.id);
|
|
104
|
+
return {
|
|
105
|
+
schemaVersion: 'number' == typeof contract.schemaVersion ? contract.schemaVersion : 1,
|
|
106
|
+
profile: 'string' == typeof contract.profile ? contract.profile : void 0,
|
|
107
|
+
source: 'legacy',
|
|
108
|
+
sourcePath,
|
|
109
|
+
workspace: {
|
|
110
|
+
packageScope: packageScopeFromRoot(workspaceRoot)
|
|
111
|
+
},
|
|
112
|
+
packageSource: packageSourceFromMetadata(workspaceRoot),
|
|
113
|
+
features: {
|
|
114
|
+
tailwind: shell?.styling?.tailwind !== false
|
|
115
|
+
},
|
|
116
|
+
topology: {
|
|
117
|
+
apps: apps.map((app)=>{
|
|
118
|
+
const id = String(app.id);
|
|
119
|
+
const appPath = 'string' == typeof app.path ? app.path : id === shellApp.id ? shellApp.directory : `verticals/${toKebabCase(id)}`;
|
|
120
|
+
const domain = 'string' == typeof app.i18n?.namespace && 'shell' !== app.i18n.namespace ? app.i18n.namespace : appPath.split('/').at(-1);
|
|
121
|
+
return {
|
|
122
|
+
id,
|
|
123
|
+
kind: 'vertical' === app.kind ? 'vertical' : 'shell',
|
|
124
|
+
path: appPath,
|
|
125
|
+
package: 'string' == typeof app.package ? app.package : void 0,
|
|
126
|
+
packageSuffix: 'string' == typeof app.package ? app.package.split('/').at(-1) : appPath.split('/').at(-1),
|
|
127
|
+
displayName: id === shellApp.id ? shellApp.displayName : void 0,
|
|
128
|
+
domain,
|
|
129
|
+
port: ports[id],
|
|
130
|
+
moduleFederation: app.moduleFederation && 'object' == typeof app.moduleFederation ? {
|
|
131
|
+
role: 'vertical' === app.kind ? 'remote' : 'host',
|
|
132
|
+
name: 'string' == typeof app.moduleFederation.name ? app.moduleFederation.name : void 0,
|
|
133
|
+
exposes: Array.isArray(app.moduleFederation.exposes) ? app.moduleFederation.exposes.filter((expose)=>'string' == typeof expose) : void 0,
|
|
134
|
+
verticalRefs: Array.isArray(app.moduleFederation.verticalRefs) ? app.moduleFederation.verticalRefs.filter((ref)=>'string' == typeof ref) : void 0,
|
|
135
|
+
hostOnly: 'vertical' !== app.kind
|
|
136
|
+
} : void 0,
|
|
137
|
+
effectApi: app.effect && 'object' == typeof app.effect ? {
|
|
138
|
+
stem: 'string' == typeof app.effect.prefix ? app.effect.prefix.split('/').filter(Boolean).at(-1) ?? domain ?? id : domain ?? id,
|
|
139
|
+
prefix: 'string' == typeof app.effect.prefix ? app.effect.prefix : `/${domain ?? id}-api`,
|
|
140
|
+
consumedBy: [
|
|
141
|
+
shellApp.id,
|
|
142
|
+
id
|
|
143
|
+
]
|
|
144
|
+
} : void 0
|
|
145
|
+
};
|
|
146
|
+
})
|
|
147
|
+
},
|
|
148
|
+
legacyContract: contract
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function readUltramodernConfig(workspaceRoot = process.cwd()) {
|
|
152
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
153
|
+
if (node_fs.existsSync(compactPath)) return normalizeCompactConfig(workspaceRoot, compactPath, readJsonObject(compactPath));
|
|
154
|
+
const legacyPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
|
|
155
|
+
if (node_fs.existsSync(legacyPath)) return adaptLegacyContract(workspaceRoot, legacyPath, readJsonObject(legacyPath));
|
|
156
|
+
throw new Error(`Missing UltraModern config. Expected ${ULTRAMODERN_CONFIG_PATH} or ${GENERATED_CONTRACT_PATH}.`);
|
|
157
|
+
}
|
|
158
|
+
function workspaceAppsFromToolingConfig(config) {
|
|
159
|
+
return config.topology.apps.map((app)=>{
|
|
160
|
+
if ('shell' === app.kind) return {
|
|
161
|
+
...shellApp,
|
|
162
|
+
directory: app.path,
|
|
163
|
+
packageSuffix: app.packageSuffix ?? shellApp.packageSuffix,
|
|
164
|
+
displayName: app.displayName ?? shellApp.displayName,
|
|
165
|
+
port: app.port ?? shellApp.port,
|
|
166
|
+
portEnv: app.portEnv ?? shellApp.portEnv,
|
|
167
|
+
mfName: app.moduleFederation?.name ?? shellApp.mfName,
|
|
168
|
+
verticalRefs: app.moduleFederation?.verticalRefs ?? []
|
|
169
|
+
};
|
|
170
|
+
const domain = app.domain ?? app.id;
|
|
171
|
+
const packageSuffix = app.packageSuffix ?? domain;
|
|
172
|
+
const exposes = Object.fromEntries((app.moduleFederation?.exposes ?? []).map((expose)=>[
|
|
173
|
+
expose,
|
|
174
|
+
'./Route' === expose ? './src/federation-entry.tsx' : './Widget' === expose ? `./src/components/${domain}-widget.tsx` : ''
|
|
175
|
+
]));
|
|
176
|
+
return {
|
|
177
|
+
id: app.id,
|
|
178
|
+
directory: app.path,
|
|
179
|
+
packageSuffix,
|
|
180
|
+
displayName: app.displayName ?? `${domain} Vertical`,
|
|
181
|
+
kind: 'vertical',
|
|
182
|
+
domain,
|
|
183
|
+
portEnv: app.portEnv ?? `VERTICAL_${domain.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase()}_PORT`,
|
|
184
|
+
port: app.port ?? 0,
|
|
185
|
+
mfName: app.moduleFederation?.name ?? app.id,
|
|
186
|
+
exposes,
|
|
187
|
+
...app.moduleFederation?.verticalRefs ? {
|
|
188
|
+
verticalRefs: app.moduleFederation.verticalRefs
|
|
189
|
+
} : {},
|
|
190
|
+
...app.effectApi ? {
|
|
191
|
+
effectApi: app.effectApi
|
|
192
|
+
} : {},
|
|
193
|
+
ownership: createNeutralOwnership(app.id)
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
export { readUltramodernConfig, workspaceAppsFromToolingConfig };
|
|
@@ -3,10 +3,10 @@ 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 { GENERATED_CONTRACT_PATH, appHasEffectApi, appI18nNamespace, createModuleFederationRemoteContracts, createNeutralOwnership, createRemoteManifestEnv, createShellHost, createVerticalDescriptor, effectApiPrefix, remoteDependencyAlias, shellApp, zephyrRemoteDependency } from "./descriptors.js";
|
|
9
|
+
import { GENERATED_CONTRACT_PATH, 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
11
|
import { readJsonFile, writeFileReplacing, writeJsonFile } from "./fs-io.js";
|
|
12
12
|
import { createFileSnapshot, createGenerationResult, diffFileSnapshots } from "./generation-result.js";
|
|
@@ -20,7 +20,7 @@ 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';
|
|
@@ -33,7 +33,12 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
|
33
33
|
modernVersion,
|
|
34
34
|
packageSource
|
|
35
35
|
});
|
|
36
|
-
const
|
|
36
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
37
|
+
if (node_fs.existsSync(compactPath)) {
|
|
38
|
+
const compactConfig = readUltramodernConfig(workspaceRoot);
|
|
39
|
+
if (compactConfig.packageSource) return compactConfig.packageSource;
|
|
40
|
+
}
|
|
41
|
+
const metadataPath = node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH);
|
|
37
42
|
if (!node_fs.existsSync(metadataPath)) return resolvePackageSource({
|
|
38
43
|
targetDir: workspaceRoot,
|
|
39
44
|
packageName: node_path.basename(workspaceRoot),
|
|
@@ -56,6 +61,8 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
|
|
|
56
61
|
};
|
|
57
62
|
}
|
|
58
63
|
function existingTailwindEnabled(workspaceRoot) {
|
|
64
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
65
|
+
if (node_fs.existsSync(compactPath)) return readUltramodernConfig(workspaceRoot).features.tailwind;
|
|
59
66
|
const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
|
|
60
67
|
if (!node_fs.existsSync(contractPath)) return true;
|
|
61
68
|
const contract = readJsonFile(contractPath);
|
|
@@ -63,6 +70,10 @@ function existingTailwindEnabled(workspaceRoot) {
|
|
|
63
70
|
const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
|
|
64
71
|
return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
|
|
65
72
|
}
|
|
73
|
+
function existingBridgeConfig(workspaceRoot) {
|
|
74
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
75
|
+
return node_fs.existsSync(compactPath) ? readUltramodernConfig(workspaceRoot).bridge : void 0;
|
|
76
|
+
}
|
|
66
77
|
function assertValidVerticalName(name) {
|
|
67
78
|
const normalized = toKebabCase(name);
|
|
68
79
|
if (!normalized || normalized !== name) throw new Error(`Invalid Vertical name "${name}". Use lowercase kebab-case.`);
|
|
@@ -75,17 +86,17 @@ function nextAvailablePort(ports) {
|
|
|
75
86
|
function assertCanCreate(workspaceRoot, relativePath) {
|
|
76
87
|
if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
|
|
77
88
|
}
|
|
78
|
-
function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes) {
|
|
89
|
+
function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes, bridge) {
|
|
79
90
|
const packagePath = node_path.join(workspaceRoot, 'package.json');
|
|
80
91
|
const rootPackage = readJsonFile(packagePath);
|
|
81
|
-
const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes);
|
|
92
|
+
const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes, bridge);
|
|
82
93
|
rootPackage.scripts = generatedRootPackage.scripts;
|
|
83
94
|
writeJsonFile(packagePath, rootPackage);
|
|
84
95
|
}
|
|
85
|
-
function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes) {
|
|
96
|
+
function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes, bridge) {
|
|
86
97
|
const shellHost = createShellHost(remotes);
|
|
87
98
|
const publicWeb = createPublicWebAppArtifacts(shellHost);
|
|
88
|
-
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
|
|
99
|
+
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes, bridge));
|
|
89
100
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/tsconfig.json`), createAppTsConfig(shellHost, remotes));
|
|
90
101
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/tsconfig.mf-types.json`), createAppMfTypesTsConfig(shellHost));
|
|
91
102
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
|
|
@@ -204,8 +215,7 @@ function prepareAddUltramodernVertical(options) {
|
|
|
204
215
|
const topology = readRequiredJsonObject(topologyPath);
|
|
205
216
|
const ownership = readRequiredJsonObject(ownershipPath);
|
|
206
217
|
const overlay = readRequiredJsonObject(overlayPath);
|
|
207
|
-
|
|
208
|
-
readRequiredJsonObject(node_path.join(options.workspaceRoot, PACKAGE_SOURCE_METADATA_PATH));
|
|
218
|
+
readRequiredWorkspaceConfig(options.workspaceRoot);
|
|
209
219
|
assertOptionalJsonObject(topology.shell, 'topology.shell', topologyPath);
|
|
210
220
|
assertOptionalJsonArray(topology.verticals, 'topology.verticals', topologyPath);
|
|
211
221
|
assertOptionalJsonArray(ownership.owners, 'ownership.owners', ownershipPath);
|
|
@@ -216,6 +226,7 @@ function prepareAddUltramodernVertical(options) {
|
|
|
216
226
|
const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
|
|
217
227
|
const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
|
|
218
228
|
const enableTailwind = options.enableTailwind ?? existingTailwindEnabled(options.workspaceRoot);
|
|
229
|
+
const bridge = existingBridgeConfig(options.workspaceRoot);
|
|
219
230
|
const existingVerticals = verticalsFromTopology(topology, overlay.ports);
|
|
220
231
|
const port = nextAvailablePort(overlay.ports);
|
|
221
232
|
const vertical = createVerticalDescriptor(name, port);
|
|
@@ -243,6 +254,7 @@ function prepareAddUltramodernVertical(options) {
|
|
|
243
254
|
overlay,
|
|
244
255
|
packageSource,
|
|
245
256
|
enableTailwind,
|
|
257
|
+
bridge,
|
|
246
258
|
vertical,
|
|
247
259
|
updatedVerticals
|
|
248
260
|
};
|
|
@@ -253,6 +265,12 @@ function readRequiredJsonObject(filePath) {
|
|
|
253
265
|
if (!isRecord(value)) throw new Error(`UltraModern workspace file must contain a JSON object: ${filePath}`);
|
|
254
266
|
return value;
|
|
255
267
|
}
|
|
268
|
+
function readRequiredWorkspaceConfig(workspaceRoot) {
|
|
269
|
+
const compactPath = node_path.join(workspaceRoot, ULTRAMODERN_CONFIG_PATH);
|
|
270
|
+
if (node_fs.existsSync(compactPath)) return void readRequiredJsonObject(compactPath);
|
|
271
|
+
readRequiredJsonObject(node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH));
|
|
272
|
+
readRequiredJsonObject(node_path.join(workspaceRoot, PACKAGE_SOURCE_METADATA_PATH));
|
|
273
|
+
}
|
|
256
274
|
function assertOptionalJsonObject(value, label, filePath) {
|
|
257
275
|
if (void 0 !== value && !isRecord(value)) throw new Error(`${label} in ${filePath} must be a JSON object`);
|
|
258
276
|
}
|
|
@@ -357,7 +375,7 @@ function createVerticalPlan(preflight, result) {
|
|
|
357
375
|
shellDependencyChanges: createShellDependencyChanges(scope, vertical),
|
|
358
376
|
generatedContractChanges: [
|
|
359
377
|
{
|
|
360
|
-
path:
|
|
378
|
+
path: ULTRAMODERN_CONFIG_PATH,
|
|
361
379
|
addedAppIds: [
|
|
362
380
|
vertical.id
|
|
363
381
|
],
|
|
@@ -444,9 +462,9 @@ function createDryRunJsonMutations(preflight, manifestUrl) {
|
|
|
444
462
|
description: 'Keep shell Module Federation DTS compilation scoped'
|
|
445
463
|
},
|
|
446
464
|
{
|
|
447
|
-
path:
|
|
448
|
-
pointer: '/apps',
|
|
449
|
-
description: `Regenerate
|
|
465
|
+
path: ULTRAMODERN_CONFIG_PATH,
|
|
466
|
+
pointer: '/topology/apps',
|
|
467
|
+
description: `Regenerate compact config with ${vertical.id}`
|
|
450
468
|
}
|
|
451
469
|
];
|
|
452
470
|
}
|
|
@@ -470,8 +488,8 @@ function createShellDependencyChanges(scope, vertical) {
|
|
|
470
488
|
}
|
|
471
489
|
function addUltramodernVertical(options) {
|
|
472
490
|
const beforeFiles = createFileSnapshot(options.workspaceRoot);
|
|
473
|
-
const { scope, topologyPath, ownershipPath, overlayPath, topology, ownership, overlay, packageSource, enableTailwind, vertical, updatedVerticals } = prepareAddUltramodernVertical(options);
|
|
474
|
-
writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind);
|
|
491
|
+
const { scope, topologyPath, ownershipPath, overlayPath, topology, ownership, overlay, packageSource, enableTailwind, bridge, vertical, updatedVerticals } = prepareAddUltramodernVertical(options);
|
|
492
|
+
writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind, updatedVerticals, bridge);
|
|
475
493
|
topology.shell ??= {};
|
|
476
494
|
topology.shell.verticalRefs ??= [];
|
|
477
495
|
topology.shell.verticalRefs.push(vertical.id);
|
|
@@ -494,18 +512,18 @@ function addUltramodernVertical(options) {
|
|
|
494
512
|
writeJsonFile(topologyPath, topology);
|
|
495
513
|
writeJsonFile(ownershipPath, ownership);
|
|
496
514
|
writeJsonFile(overlayPath, overlay);
|
|
497
|
-
writeJsonFile(node_path.join(options.workspaceRoot,
|
|
515
|
+
writeJsonFile(node_path.join(options.workspaceRoot, ULTRAMODERN_CONFIG_PATH), createCompactUltramodernConfig(scope, options.modernVersion, packageSource, [
|
|
498
516
|
{
|
|
499
517
|
...shellApp,
|
|
500
518
|
verticalRefs: updatedVerticals.map((vertical)=>vertical.id)
|
|
501
519
|
},
|
|
502
520
|
...updatedVerticals
|
|
503
|
-
], enableTailwind));
|
|
504
|
-
rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals);
|
|
521
|
+
], enableTailwind, bridge));
|
|
522
|
+
rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals, bridge);
|
|
505
523
|
writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
|
|
506
524
|
addShellZephyrDependency(options.workspaceRoot, scope, vertical);
|
|
507
525
|
addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
|
|
508
|
-
updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals);
|
|
526
|
+
updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals, bridge);
|
|
509
527
|
writeJsonFile(node_path.join(options.workspaceRoot, 'tsconfig.json'), createRootTsConfig([
|
|
510
528
|
{
|
|
511
529
|
...shellApp,
|
|
@@ -533,4 +551,4 @@ function addUltramodernVertical(options) {
|
|
|
533
551
|
});
|
|
534
552
|
return result;
|
|
535
553
|
}
|
|
536
|
-
export { addShellWorkspaceDependency, addShellZephyrDependency, addUltramodernVertical, assertCanCreate, assertValidVerticalName, existingPackageSource, existingTailwindEnabled, nextAvailablePort, ownershipEntry, planUltramodernVertical, prepareAddUltramodernVertical, rewriteShellAppFiles, updateRootWorkspaceScripts, verticalTopologyEntry, verticalsFromTopology };
|
|
554
|
+
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 };
|