@bleedingdev/modern-js-create 3.5.0-ultramodern.0 → 3.5.0-ultramodern.1

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.
Files changed (28) hide show
  1. package/README.md +24 -0
  2. package/dist/cjs/ultramodern-tooling/commands.cjs +246 -0
  3. package/dist/cjs/ultramodern-workspace/contracts.cjs +1 -0
  4. package/dist/cjs/ultramodern-workspace/module-federation.cjs +6 -0
  5. package/dist/cjs/ultramodern-workspace/package-json.cjs +1 -0
  6. package/dist/cjs/ultramodern-workspace/versions.cjs +10 -0
  7. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +1 -0
  8. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +2 -0
  9. package/dist/esm/ultramodern-tooling/commands.js +246 -0
  10. package/dist/esm/ultramodern-workspace/contracts.js +1 -0
  11. package/dist/esm/ultramodern-workspace/module-federation.js +6 -0
  12. package/dist/esm/ultramodern-workspace/package-json.js +1 -0
  13. package/dist/esm/ultramodern-workspace/versions.js +5 -1
  14. package/dist/esm/ultramodern-workspace/workspace-scripts.js +1 -0
  15. package/dist/esm/ultramodern-workspace/write-workspace.js +3 -1
  16. package/dist/esm-node/ultramodern-tooling/commands.js +246 -0
  17. package/dist/esm-node/ultramodern-workspace/contracts.js +1 -0
  18. package/dist/esm-node/ultramodern-workspace/module-federation.js +6 -0
  19. package/dist/esm-node/ultramodern-workspace/package-json.js +1 -0
  20. package/dist/esm-node/ultramodern-workspace/versions.js +5 -1
  21. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +1 -0
  22. package/dist/esm-node/ultramodern-workspace/write-workspace.js +3 -1
  23. package/dist/types/ultramodern-workspace/versions.d.ts +4 -0
  24. package/package.json +3 -3
  25. package/template-workspace/README.md.handlebars +40 -1
  26. package/template-workspace/pnpm-workspace.yaml.handlebars +3 -0
  27. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +30 -8
  28. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +2 -0
@@ -4,6 +4,7 @@ import node_os from "node:os";
4
4
  import node_path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { resolveCreatePackageRoot } from "../create-package-root.js";
7
+ import { BLEEDINGDEV_PACKAGE_NAME_PREFIX, BLEEDINGDEV_PACKAGE_SCOPE, ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES, ULTRAMODERN_WORKSPACE_MODERN_PACKAGES, WORKSPACE_PACKAGE_VERSION, modernPackageSpecifier } from "../ultramodern-package-source.js";
7
8
  import { validateModuleFederationTypes } from "../ultramodern-workspace/mf-validation.js";
8
9
  import { createWorkspaceValidationScript } from "../ultramodern-workspace/workspace-scripts.js";
9
10
  import { readUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
@@ -17,6 +18,7 @@ Commands:
17
18
  validate
18
19
  typecheck
19
20
  mf-types
21
+ migrate-strict-effect
20
22
  public-surface
21
23
  cloudflare-proof
22
24
  performance-readiness
@@ -82,6 +84,248 @@ Checks real Module Federation config files and DTS archives for exposed apps.
82
84
  });
83
85
  return 0;
84
86
  }
87
+ const modernPackageNames = new Set([
88
+ ...ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
89
+ ...ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
90
+ ]);
91
+ function readJsonFile(filePath) {
92
+ return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
93
+ }
94
+ function writeJsonFile(filePath, value) {
95
+ node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
96
+ }
97
+ function readOption(args, name) {
98
+ const prefix = `${name}=`;
99
+ const inline = args.find((arg)=>arg.startsWith(prefix));
100
+ if (inline) {
101
+ const value = inline.slice(prefix.length);
102
+ if (!value) throw new Error(`${name} needs a value.`);
103
+ return value;
104
+ }
105
+ const index = args.indexOf(name);
106
+ if (-1 === index) return;
107
+ const value = args[index + 1];
108
+ if (!value || value.startsWith('--')) throw new Error(`${name} needs a value.`);
109
+ return value;
110
+ }
111
+ function hasFlag(args, name) {
112
+ return args.includes(name);
113
+ }
114
+ function listWorkspacePackageFiles(workspaceRoot) {
115
+ const packageFiles = [
116
+ 'package.json'
117
+ ];
118
+ for (const directory of [
119
+ 'apps',
120
+ 'verticals',
121
+ 'packages'
122
+ ]){
123
+ const absoluteDirectory = node_path.join(workspaceRoot, directory);
124
+ if (node_fs.existsSync(absoluteDirectory)) for (const entry of node_fs.readdirSync(absoluteDirectory, {
125
+ withFileTypes: true
126
+ })){
127
+ if (!entry.isDirectory()) continue;
128
+ const packageFile = node_path.join(directory, entry.name, 'package.json');
129
+ if (node_fs.existsSync(node_path.join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
130
+ }
131
+ }
132
+ return packageFiles;
133
+ }
134
+ function updateModernDependencies(packageJson, packageSource) {
135
+ let changed = false;
136
+ for (const section of [
137
+ 'dependencies',
138
+ 'devDependencies',
139
+ 'peerDependencies',
140
+ 'optionalDependencies'
141
+ ]){
142
+ const dependencies = packageJson[section];
143
+ if (!(!dependencies || 'object' != typeof dependencies || Array.isArray(dependencies))) for (const packageName of Object.keys(dependencies)){
144
+ if (!modernPackageNames.has(packageName)) continue;
145
+ const nextSpecifier = modernPackageSpecifier(packageName, packageSource);
146
+ if (dependencies[packageName] !== nextSpecifier) {
147
+ dependencies[packageName] = nextSpecifier;
148
+ changed = true;
149
+ }
150
+ }
151
+ }
152
+ return changed;
153
+ }
154
+ function normalizeStrictEffectApiMetadata(value) {
155
+ const api = value.api;
156
+ if (!api || 'object' != typeof api || Array.isArray(api)) return false;
157
+ let changed = false;
158
+ const oldEffect = api.effect;
159
+ if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
160
+ if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
161
+ api.stem = oldEffect.stem;
162
+ changed = true;
163
+ }
164
+ if (void 0 === api.prefix && 'string' == typeof oldEffect.prefix) {
165
+ api.prefix = oldEffect.prefix;
166
+ changed = true;
167
+ }
168
+ if (void 0 === api.consumedBy && Array.isArray(oldEffect.consumedBy)) {
169
+ api.consumedBy = oldEffect.consumedBy;
170
+ changed = true;
171
+ }
172
+ delete api.effect;
173
+ changed = true;
174
+ }
175
+ if (void 0 !== api.runtime && 'effect' !== api.runtime) {
176
+ api.runtime = 'effect';
177
+ changed = true;
178
+ }
179
+ if (api.bff && 'object' == typeof api.bff && !Array.isArray(api.bff)) {
180
+ if (true !== api.bff.strictEffectApproach) {
181
+ api.bff.strictEffectApproach = true;
182
+ changed = true;
183
+ }
184
+ }
185
+ if ('string' == typeof value.path) {
186
+ const directServerEntry = `${value.path}/api/index.ts`;
187
+ if ('string' == typeof api.serverEntry && /\/api\/effect\/index\.[cm]?[jt]sx?$/u.test(api.serverEntry)) {
188
+ api.serverEntry = directServerEntry;
189
+ changed = true;
190
+ }
191
+ if (api.contract && 'object' == typeof api.contract && !Array.isArray(api.contract)) {
192
+ if ('./shared/effect/api' === api.contract.export) {
193
+ api.contract.export = './api';
194
+ changed = true;
195
+ }
196
+ if ('string' == typeof api.contract.path && /\/shared\/effect\/api\.[cm]?[jt]sx?$/u.test(api.contract.path)) {
197
+ api.contract.path = `${value.path}/shared/api.ts`;
198
+ changed = true;
199
+ }
200
+ }
201
+ if (api.client && 'object' == typeof api.client && !Array.isArray(api.client)) {
202
+ if ('./effect/client' === api.client.export) {
203
+ api.client.export = './api/client';
204
+ changed = true;
205
+ }
206
+ if ('string' == typeof api.client.path && /\/src\/effect\/[^/]+-client\.[cm]?ts$/u.test(api.client.path)) {
207
+ const basename = node_path.basename(api.client.path);
208
+ api.client.path = `${value.path}/src/api/${basename}`;
209
+ changed = true;
210
+ }
211
+ }
212
+ if (void 0 === api.serverEntry && 'effect' === api.runtime) {
213
+ api.serverEntry = directServerEntry;
214
+ changed = true;
215
+ }
216
+ }
217
+ return changed;
218
+ }
219
+ function updateUltramodernConfig(workspaceRoot, packageSource) {
220
+ const configPath = node_path.join(workspaceRoot, '.modernjs/ultramodern.json');
221
+ const config = readJsonFile(configPath);
222
+ config.packageSource = {
223
+ strategy: packageSource.strategy,
224
+ modernPackageVersion: packageSource.modernPackageVersion,
225
+ ...packageSource.registry ? {
226
+ registry: packageSource.registry
227
+ } : {},
228
+ ...packageSource.aliasScope ? {
229
+ aliasScope: packageSource.aliasScope
230
+ } : {},
231
+ ...packageSource.aliasPackageNamePrefix ? {
232
+ aliasPackageNamePrefix: packageSource.aliasPackageNamePrefix
233
+ } : {}
234
+ };
235
+ for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
236
+ writeJsonFile(configPath, config);
237
+ }
238
+ function updateReferenceTopology(workspaceRoot) {
239
+ const topologyPath = node_path.join(workspaceRoot, 'topology/reference-topology.json');
240
+ if (!node_fs.existsSync(topologyPath)) return false;
241
+ const topology = readJsonFile(topologyPath);
242
+ let changed = false;
243
+ for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
244
+ if (changed) writeJsonFile(topologyPath, topology);
245
+ return changed;
246
+ }
247
+ function createMigrationPackageSource(args, current) {
248
+ const strategy = hasFlag(args, '--workspace') ? 'workspace' : 'install';
249
+ const registry = readOption(args, '--registry') ?? readOption(args, '--ultramodern-package-registry');
250
+ const explicitAliasScope = readOption(args, '--alias-scope') ?? readOption(args, '--ultramodern-package-scope');
251
+ const aliasScope = explicitAliasScope ?? ('install' === strategy && void 0 === registry ? current.packageSource?.aliasScope ?? BLEEDINGDEV_PACKAGE_SCOPE : current.packageSource?.aliasScope);
252
+ const aliasPackageNamePrefix = readOption(args, '--alias-package-name-prefix') ?? readOption(args, '--ultramodern-package-name-prefix') ?? current.packageSource?.aliasPackageNamePrefix ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0);
253
+ if ('workspace' === strategy) return {
254
+ strategy,
255
+ modernPackageVersion: WORKSPACE_PACKAGE_VERSION,
256
+ ...registry ? {
257
+ registry
258
+ } : {},
259
+ ...aliasScope ? {
260
+ aliasScope
261
+ } : {},
262
+ ...aliasPackageNamePrefix ? {
263
+ aliasPackageNamePrefix
264
+ } : {}
265
+ };
266
+ const version = readOption(args, '--version') ?? readOption(args, '--ultramodern-package-version') ?? current.packageSource?.modernPackageVersion;
267
+ if (!version || version === WORKSPACE_PACKAGE_VERSION) throw new Error('migrate-strict-effect needs --version <published-ultramodern-version> for install package source.');
268
+ return {
269
+ strategy,
270
+ modernPackageVersion: version,
271
+ ...registry ? {
272
+ registry
273
+ } : {},
274
+ ...aliasScope ? {
275
+ aliasScope
276
+ } : {},
277
+ ...aliasPackageNamePrefix ? {
278
+ aliasPackageNamePrefix
279
+ } : {}
280
+ };
281
+ }
282
+ function runPnpmLockfileRefresh(context) {
283
+ const result = spawnSync('pnpm', [
284
+ 'install',
285
+ '--lockfile-only',
286
+ "--ignore-scripts"
287
+ ], {
288
+ cwd: context.workspaceRoot,
289
+ stdio: 'inherit'
290
+ });
291
+ if (result.error) throw result.error;
292
+ return result.status ?? 1;
293
+ }
294
+ function runMigrateStrictEffect(args, context) {
295
+ if (args.includes('--help') || args.includes('-h')) {
296
+ process.stdout.write(`Usage:
297
+ modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
298
+
299
+ Updates generated UltraModern package-source metadata, Modern package aliases,
300
+ direct Effect API topology metadata, and the pnpm lockfile for strict Effect
301
+ workspaces. Source code still has to pass pnpm api:check and pnpm contract:check.
302
+ `);
303
+ return 0;
304
+ }
305
+ const current = readUltramodernConfig(context.workspaceRoot);
306
+ const packageSource = createMigrationPackageSource(args, current);
307
+ updateUltramodernConfig(context.workspaceRoot, packageSource);
308
+ updateReferenceTopology(context.workspaceRoot);
309
+ for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
310
+ const packageFile = node_path.join(context.workspaceRoot, relativePackageFile);
311
+ const packageJson = readJsonFile(packageFile);
312
+ if ('package.json' === relativePackageFile) {
313
+ packageJson.modernjs ??= {};
314
+ packageJson.modernjs.packageSource = {
315
+ strategy: packageSource.strategy,
316
+ config: './.modernjs/ultramodern.json'
317
+ };
318
+ }
319
+ if (updateModernDependencies(packageJson, packageSource)) writeJsonFile(packageFile, packageJson);
320
+ else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
321
+ }
322
+ if (!hasFlag(args, '--skip-install')) {
323
+ const status = runPnpmLockfileRefresh(context);
324
+ if (0 !== status) return status;
325
+ }
326
+ process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
327
+ return 0;
328
+ }
85
329
  function runSkills(args, context) {
86
330
  const [subcommand, ...rest] = args;
87
331
  if ('install' === subcommand) return spawnNodeScript("template-workspace/scripts/bootstrap-agent-skills.mjs", rest, context);
@@ -112,6 +356,8 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
112
356
  });
113
357
  case 'mf-types':
114
358
  return runMfTypes(rest, context);
359
+ case 'migrate-strict-effect':
360
+ return runMigrateStrictEffect(rest, context);
115
361
  case 'public-surface':
116
362
  return spawnNodeScript("templates/workspace-scripts/generate-public-surface-assets.mjs", rest, context);
117
363
  case 'cloudflare-proof':
@@ -254,6 +254,7 @@ function createUltramodernConfig(scope, modernVersion, packageSource, apps = [
254
254
  publicSurface: "scripts/generate-public-surface-assets.mts",
255
255
  cloudflareProof: "scripts/proof-cloudflare-version.mts",
256
256
  performanceReadiness: "scripts/ultramodern-performance-readiness.mts",
257
+ migrateStrictEffect: "scripts/migrate-strict-effect.mts",
257
258
  apiBoundaries: "scripts/check-ultramodern-api-boundaries.mts",
258
259
  skills: "scripts/bootstrap-agent-skills.mts"
259
260
  }
@@ -93,6 +93,8 @@ ${defaultAssetPrefixSource}
93
93
  // load remoteEntry.js and exposed chunks from the remote origin, not the host.
94
94
  const assetPrefix =
95
95
  configuredModernAssetPrefix || configuredUltramodernAssetPrefix || defaultAssetPrefix;
96
+ const buildCacheTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';
97
+ const buildCacheDirectory = \`node_modules/.cache/rspack-\${appId}-\${buildCacheTarget}\`;
96
98
 
97
99
  if (
98
100
  cloudflareDeployEnabled &&
@@ -139,6 +141,10 @@ ${bffConfig} ...(cloudflareDeployEnabled
139
141
  splitRouteChunks: true,
140
142
  },
141
143
  performance: {
144
+ buildCache: {
145
+ cacheDigest: [appId, buildCacheTarget],
146
+ cacheDirectory: buildCacheDirectory,
147
+ },
142
148
  rsdoctor: {
143
149
  disableClientServer: true,
144
150
  enabled: process.env['ULTRAMODERN_RSDOCTOR'] === 'true',
@@ -178,6 +178,7 @@ function createRootPackageJson(scope, packageSource, remotes = [], bridge) {
178
178
  'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mts --check",
179
179
  'mf:types': "node ./scripts/assert-mf-types.mts",
180
180
  'performance:readiness': "node ./scripts/ultramodern-performance-readiness.mts",
181
+ 'migrate:strict-effect': "node ./scripts/migrate-strict-effect.mts",
181
182
  'contract:check': "node ./scripts/validate-ultramodern-workspace.mts",
182
183
  'api:check': "node ./scripts/check-ultramodern-api-boundaries.mts",
183
184
  'i18n:boundaries': "node ./scripts/check-ultramodern-i18n-boundaries.mts",
@@ -8,6 +8,8 @@ const CLOUDFLARE_COMPATIBILITY_DATE = '2026-06-02';
8
8
  const TAILWIND_VERSION = '4.3.1';
9
9
  const TAILWIND_POSTCSS_VERSION = '4.3.1';
10
10
  const POSTCSS_VERSION = '8.5.15';
11
+ const EFFECT_VERSION = '4.0.0-beta.89';
12
+ const EFFECT_VITEST_VERSION = '4.0.0-beta.89';
11
13
  const EFFECT_TSGO_VERSION = '0.14.6';
12
14
  const TYPESCRIPT_STABLE_VERSION = '6.0.3';
13
15
  const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260624.1';
@@ -31,7 +33,9 @@ const ultramodernWorkspaceVersions = {
31
33
  tanstackRouter: TANSTACK_ROUTER_VERSION,
32
34
  tanstackRouterCore: TANSTACK_ROUTER_CORE_VERSION,
33
35
  moduleFederation: MODULE_FEDERATION_VERSION,
36
+ effect: EFFECT_VERSION,
37
+ effectVitest: EFFECT_VITEST_VERSION,
34
38
  tailwind: TAILWIND_VERSION,
35
39
  tailwindPostcss: TAILWIND_POSTCSS_VERSION
36
40
  };
37
- export { CLOUDFLARE_COMPATIBILITY_DATE, EFFECT_TSGO_VERSION, I18NEXT_VERSION, LEFTHOOK_VERSION, MODULE_FEDERATION_AGENT_SKILLS_COMMIT, MODULE_FEDERATION_VERSION, NODE_FETCH_VERSION, NODE_VERSION, OXFMT_VERSION, OXLINT_VERSION, PNPM_VERSION, POSTCSS_VERSION, REACT_DOM_VERSION, REACT_ROUTER_VERSION, REACT_VERSION, RSTACK_AGENT_SKILLS_COMMIT, TAILWIND_POSTCSS_VERSION, TAILWIND_VERSION, TANSTACK_ROUTER_CORE_VERSION, TANSTACK_ROUTER_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, TYPESCRIPT_STABLE_VERSION, TYPESCRIPT_VERSION, TYPES_REACT_DOM_VERSION, TYPES_REACT_VERSION, ULTRACITE_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION, ultramodernWorkspaceVersions };
41
+ export { CLOUDFLARE_COMPATIBILITY_DATE, EFFECT_TSGO_VERSION, EFFECT_VERSION, EFFECT_VITEST_VERSION, I18NEXT_VERSION, LEFTHOOK_VERSION, MODULE_FEDERATION_AGENT_SKILLS_COMMIT, MODULE_FEDERATION_VERSION, NODE_FETCH_VERSION, NODE_VERSION, OXFMT_VERSION, OXLINT_VERSION, PNPM_VERSION, POSTCSS_VERSION, REACT_DOM_VERSION, REACT_ROUTER_VERSION, REACT_VERSION, RSTACK_AGENT_SKILLS_COMMIT, TAILWIND_POSTCSS_VERSION, TAILWIND_VERSION, TANSTACK_ROUTER_CORE_VERSION, TANSTACK_ROUTER_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, TYPESCRIPT_STABLE_VERSION, TYPESCRIPT_VERSION, TYPES_REACT_DOM_VERSION, TYPES_REACT_VERSION, ULTRACITE_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION, ultramodernWorkspaceVersions };
@@ -162,6 +162,7 @@ function writeGeneratedWorkspaceScripts(targetDir, _scope, _enableTailwind, _rem
162
162
  writeWorkspaceOwnedMtsScript(targetDir, 'proof-cloudflare-version', createToolWrapperScript('cloudflare-proof'));
163
163
  writeFileReplacing(targetDir, "scripts/ultramodern-performance-readiness.config.mjs", createPerformanceReadinessConfigScript());
164
164
  writeWorkspaceOwnedMtsScript(targetDir, 'ultramodern-performance-readiness', createToolWrapperScript('performance-readiness'));
165
+ writeWorkspaceOwnedMtsScript(targetDir, 'migrate-strict-effect', createToolWrapperScript('migrate-strict-effect'));
165
166
  writeWorkspaceOwnedMtsScript(targetDir, 'ultramodern-typecheck', createToolWrapperScript('typecheck'));
166
167
  writeWorkspaceOwnedMtsScript(targetDir, 'bootstrap-agent-skills', createSkillsToolWrapperScript());
167
168
  migrateCopiedWorkspaceScriptToMts(targetDir, 'setup-agent-reference-repos');
@@ -14,7 +14,7 @@ import { runCodeSmithOverlays } from "./overlays.js";
14
14
  import { createAppMfTypesTsConfig, createAppPackage, createAppTsConfig, createRootPackageJson, createRootTsConfig, createSharedContractsIndex, createSharedPackage, createSharedPackageTsConfig, createTsConfigBase } from "./package-json.js";
15
15
  import { resolvePackageSource } from "./package-source.js";
16
16
  import { createPublicWebAppArtifacts } from "./public-surface.js";
17
- import { NODE_FETCH_VERSION, NODE_VERSION, PNPM_VERSION, TANSTACK_ROUTER_CORE_VERSION, TANSTACK_ROUTER_VERSION, TYPESCRIPT_VERSION } from "./versions.js";
17
+ import { EFFECT_VERSION, EFFECT_VITEST_VERSION, NODE_FETCH_VERSION, NODE_VERSION, PNPM_VERSION, TANSTACK_ROUTER_CORE_VERSION, TANSTACK_ROUTER_VERSION, TYPESCRIPT_VERSION } from "./versions.js";
18
18
  import { writeGeneratedWorkspaceScripts } from "./workspace-scripts.js";
19
19
  function writeApp(targetDir, scope, app, packageSource, enableTailwind, remotes = [], bridge) {
20
20
  const resolvedApp = 'shell' === app.kind ? createShellHost(remotes) : app;
@@ -130,6 +130,8 @@ function generateUltramodernWorkspace(options) {
130
130
  nodeVersion: NODE_VERSION,
131
131
  pnpmVersion: PNPM_VERSION,
132
132
  nodeFetchVersion: NODE_FETCH_VERSION,
133
+ effectVersion: EFFECT_VERSION,
134
+ effectVitestVersion: EFFECT_VITEST_VERSION,
133
135
  tanstackRouterCoreVersion: TANSTACK_ROUTER_CORE_VERSION,
134
136
  tanstackRouterVersion: TANSTACK_ROUTER_VERSION,
135
137
  typescriptVersion: TYPESCRIPT_VERSION,
@@ -5,6 +5,7 @@ import node_os from "node:os";
5
5
  import node_path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { resolveCreatePackageRoot } from "../create-package-root.js";
8
+ import { BLEEDINGDEV_PACKAGE_NAME_PREFIX, BLEEDINGDEV_PACKAGE_SCOPE, ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES, ULTRAMODERN_WORKSPACE_MODERN_PACKAGES, WORKSPACE_PACKAGE_VERSION, modernPackageSpecifier } from "../ultramodern-package-source.js";
8
9
  import { validateModuleFederationTypes } from "../ultramodern-workspace/mf-validation.js";
9
10
  import { createWorkspaceValidationScript } from "../ultramodern-workspace/workspace-scripts.js";
10
11
  import { readUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
@@ -18,6 +19,7 @@ Commands:
18
19
  validate
19
20
  typecheck
20
21
  mf-types
22
+ migrate-strict-effect
21
23
  public-surface
22
24
  cloudflare-proof
23
25
  performance-readiness
@@ -83,6 +85,248 @@ Checks real Module Federation config files and DTS archives for exposed apps.
83
85
  });
84
86
  return 0;
85
87
  }
88
+ const modernPackageNames = new Set([
89
+ ...ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
90
+ ...ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
91
+ ]);
92
+ function readJsonFile(filePath) {
93
+ return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
94
+ }
95
+ function writeJsonFile(filePath, value) {
96
+ node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
97
+ }
98
+ function readOption(args, name) {
99
+ const prefix = `${name}=`;
100
+ const inline = args.find((arg)=>arg.startsWith(prefix));
101
+ if (inline) {
102
+ const value = inline.slice(prefix.length);
103
+ if (!value) throw new Error(`${name} needs a value.`);
104
+ return value;
105
+ }
106
+ const index = args.indexOf(name);
107
+ if (-1 === index) return;
108
+ const value = args[index + 1];
109
+ if (!value || value.startsWith('--')) throw new Error(`${name} needs a value.`);
110
+ return value;
111
+ }
112
+ function hasFlag(args, name) {
113
+ return args.includes(name);
114
+ }
115
+ function listWorkspacePackageFiles(workspaceRoot) {
116
+ const packageFiles = [
117
+ 'package.json'
118
+ ];
119
+ for (const directory of [
120
+ 'apps',
121
+ 'verticals',
122
+ 'packages'
123
+ ]){
124
+ const absoluteDirectory = node_path.join(workspaceRoot, directory);
125
+ if (node_fs.existsSync(absoluteDirectory)) for (const entry of node_fs.readdirSync(absoluteDirectory, {
126
+ withFileTypes: true
127
+ })){
128
+ if (!entry.isDirectory()) continue;
129
+ const packageFile = node_path.join(directory, entry.name, 'package.json');
130
+ if (node_fs.existsSync(node_path.join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
131
+ }
132
+ }
133
+ return packageFiles;
134
+ }
135
+ function updateModernDependencies(packageJson, packageSource) {
136
+ let changed = false;
137
+ for (const section of [
138
+ 'dependencies',
139
+ 'devDependencies',
140
+ 'peerDependencies',
141
+ 'optionalDependencies'
142
+ ]){
143
+ const dependencies = packageJson[section];
144
+ if (!(!dependencies || 'object' != typeof dependencies || Array.isArray(dependencies))) for (const packageName of Object.keys(dependencies)){
145
+ if (!modernPackageNames.has(packageName)) continue;
146
+ const nextSpecifier = modernPackageSpecifier(packageName, packageSource);
147
+ if (dependencies[packageName] !== nextSpecifier) {
148
+ dependencies[packageName] = nextSpecifier;
149
+ changed = true;
150
+ }
151
+ }
152
+ }
153
+ return changed;
154
+ }
155
+ function normalizeStrictEffectApiMetadata(value) {
156
+ const api = value.api;
157
+ if (!api || 'object' != typeof api || Array.isArray(api)) return false;
158
+ let changed = false;
159
+ const oldEffect = api.effect;
160
+ if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
161
+ if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
162
+ api.stem = oldEffect.stem;
163
+ changed = true;
164
+ }
165
+ if (void 0 === api.prefix && 'string' == typeof oldEffect.prefix) {
166
+ api.prefix = oldEffect.prefix;
167
+ changed = true;
168
+ }
169
+ if (void 0 === api.consumedBy && Array.isArray(oldEffect.consumedBy)) {
170
+ api.consumedBy = oldEffect.consumedBy;
171
+ changed = true;
172
+ }
173
+ delete api.effect;
174
+ changed = true;
175
+ }
176
+ if (void 0 !== api.runtime && 'effect' !== api.runtime) {
177
+ api.runtime = 'effect';
178
+ changed = true;
179
+ }
180
+ if (api.bff && 'object' == typeof api.bff && !Array.isArray(api.bff)) {
181
+ if (true !== api.bff.strictEffectApproach) {
182
+ api.bff.strictEffectApproach = true;
183
+ changed = true;
184
+ }
185
+ }
186
+ if ('string' == typeof value.path) {
187
+ const directServerEntry = `${value.path}/api/index.ts`;
188
+ if ('string' == typeof api.serverEntry && /\/api\/effect\/index\.[cm]?[jt]sx?$/u.test(api.serverEntry)) {
189
+ api.serverEntry = directServerEntry;
190
+ changed = true;
191
+ }
192
+ if (api.contract && 'object' == typeof api.contract && !Array.isArray(api.contract)) {
193
+ if ('./shared/effect/api' === api.contract.export) {
194
+ api.contract.export = './api';
195
+ changed = true;
196
+ }
197
+ if ('string' == typeof api.contract.path && /\/shared\/effect\/api\.[cm]?[jt]sx?$/u.test(api.contract.path)) {
198
+ api.contract.path = `${value.path}/shared/api.ts`;
199
+ changed = true;
200
+ }
201
+ }
202
+ if (api.client && 'object' == typeof api.client && !Array.isArray(api.client)) {
203
+ if ('./effect/client' === api.client.export) {
204
+ api.client.export = './api/client';
205
+ changed = true;
206
+ }
207
+ if ('string' == typeof api.client.path && /\/src\/effect\/[^/]+-client\.[cm]?ts$/u.test(api.client.path)) {
208
+ const basename = node_path.basename(api.client.path);
209
+ api.client.path = `${value.path}/src/api/${basename}`;
210
+ changed = true;
211
+ }
212
+ }
213
+ if (void 0 === api.serverEntry && 'effect' === api.runtime) {
214
+ api.serverEntry = directServerEntry;
215
+ changed = true;
216
+ }
217
+ }
218
+ return changed;
219
+ }
220
+ function updateUltramodernConfig(workspaceRoot, packageSource) {
221
+ const configPath = node_path.join(workspaceRoot, '.modernjs/ultramodern.json');
222
+ const config = readJsonFile(configPath);
223
+ config.packageSource = {
224
+ strategy: packageSource.strategy,
225
+ modernPackageVersion: packageSource.modernPackageVersion,
226
+ ...packageSource.registry ? {
227
+ registry: packageSource.registry
228
+ } : {},
229
+ ...packageSource.aliasScope ? {
230
+ aliasScope: packageSource.aliasScope
231
+ } : {},
232
+ ...packageSource.aliasPackageNamePrefix ? {
233
+ aliasPackageNamePrefix: packageSource.aliasPackageNamePrefix
234
+ } : {}
235
+ };
236
+ for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
237
+ writeJsonFile(configPath, config);
238
+ }
239
+ function updateReferenceTopology(workspaceRoot) {
240
+ const topologyPath = node_path.join(workspaceRoot, 'topology/reference-topology.json');
241
+ if (!node_fs.existsSync(topologyPath)) return false;
242
+ const topology = readJsonFile(topologyPath);
243
+ let changed = false;
244
+ for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
245
+ if (changed) writeJsonFile(topologyPath, topology);
246
+ return changed;
247
+ }
248
+ function createMigrationPackageSource(args, current) {
249
+ const strategy = hasFlag(args, '--workspace') ? 'workspace' : 'install';
250
+ const registry = readOption(args, '--registry') ?? readOption(args, '--ultramodern-package-registry');
251
+ const explicitAliasScope = readOption(args, '--alias-scope') ?? readOption(args, '--ultramodern-package-scope');
252
+ const aliasScope = explicitAliasScope ?? ('install' === strategy && void 0 === registry ? current.packageSource?.aliasScope ?? BLEEDINGDEV_PACKAGE_SCOPE : current.packageSource?.aliasScope);
253
+ const aliasPackageNamePrefix = readOption(args, '--alias-package-name-prefix') ?? readOption(args, '--ultramodern-package-name-prefix') ?? current.packageSource?.aliasPackageNamePrefix ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0);
254
+ if ('workspace' === strategy) return {
255
+ strategy,
256
+ modernPackageVersion: WORKSPACE_PACKAGE_VERSION,
257
+ ...registry ? {
258
+ registry
259
+ } : {},
260
+ ...aliasScope ? {
261
+ aliasScope
262
+ } : {},
263
+ ...aliasPackageNamePrefix ? {
264
+ aliasPackageNamePrefix
265
+ } : {}
266
+ };
267
+ const version = readOption(args, '--version') ?? readOption(args, '--ultramodern-package-version') ?? current.packageSource?.modernPackageVersion;
268
+ if (!version || version === WORKSPACE_PACKAGE_VERSION) throw new Error('migrate-strict-effect needs --version <published-ultramodern-version> for install package source.');
269
+ return {
270
+ strategy,
271
+ modernPackageVersion: version,
272
+ ...registry ? {
273
+ registry
274
+ } : {},
275
+ ...aliasScope ? {
276
+ aliasScope
277
+ } : {},
278
+ ...aliasPackageNamePrefix ? {
279
+ aliasPackageNamePrefix
280
+ } : {}
281
+ };
282
+ }
283
+ function runPnpmLockfileRefresh(context) {
284
+ const result = spawnSync('pnpm', [
285
+ 'install',
286
+ '--lockfile-only',
287
+ "--ignore-scripts"
288
+ ], {
289
+ cwd: context.workspaceRoot,
290
+ stdio: 'inherit'
291
+ });
292
+ if (result.error) throw result.error;
293
+ return result.status ?? 1;
294
+ }
295
+ function runMigrateStrictEffect(args, context) {
296
+ if (args.includes('--help') || args.includes('-h')) {
297
+ process.stdout.write(`Usage:
298
+ modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
299
+
300
+ Updates generated UltraModern package-source metadata, Modern package aliases,
301
+ direct Effect API topology metadata, and the pnpm lockfile for strict Effect
302
+ workspaces. Source code still has to pass pnpm api:check and pnpm contract:check.
303
+ `);
304
+ return 0;
305
+ }
306
+ const current = readUltramodernConfig(context.workspaceRoot);
307
+ const packageSource = createMigrationPackageSource(args, current);
308
+ updateUltramodernConfig(context.workspaceRoot, packageSource);
309
+ updateReferenceTopology(context.workspaceRoot);
310
+ for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
311
+ const packageFile = node_path.join(context.workspaceRoot, relativePackageFile);
312
+ const packageJson = readJsonFile(packageFile);
313
+ if ('package.json' === relativePackageFile) {
314
+ packageJson.modernjs ??= {};
315
+ packageJson.modernjs.packageSource = {
316
+ strategy: packageSource.strategy,
317
+ config: './.modernjs/ultramodern.json'
318
+ };
319
+ }
320
+ if (updateModernDependencies(packageJson, packageSource)) writeJsonFile(packageFile, packageJson);
321
+ else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
322
+ }
323
+ if (!hasFlag(args, '--skip-install')) {
324
+ const status = runPnpmLockfileRefresh(context);
325
+ if (0 !== status) return status;
326
+ }
327
+ process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
328
+ return 0;
329
+ }
86
330
  function runSkills(args, context) {
87
331
  const [subcommand, ...rest] = args;
88
332
  if ('install' === subcommand) return spawnNodeScript("template-workspace/scripts/bootstrap-agent-skills.mjs", rest, context);
@@ -113,6 +357,8 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
113
357
  });
114
358
  case 'mf-types':
115
359
  return runMfTypes(rest, context);
360
+ case 'migrate-strict-effect':
361
+ return runMigrateStrictEffect(rest, context);
116
362
  case 'public-surface':
117
363
  return spawnNodeScript("templates/workspace-scripts/generate-public-surface-assets.mjs", rest, context);
118
364
  case 'cloudflare-proof':
@@ -255,6 +255,7 @@ function createUltramodernConfig(scope, modernVersion, packageSource, apps = [
255
255
  publicSurface: "scripts/generate-public-surface-assets.mts",
256
256
  cloudflareProof: "scripts/proof-cloudflare-version.mts",
257
257
  performanceReadiness: "scripts/ultramodern-performance-readiness.mts",
258
+ migrateStrictEffect: "scripts/migrate-strict-effect.mts",
258
259
  apiBoundaries: "scripts/check-ultramodern-api-boundaries.mts",
259
260
  skills: "scripts/bootstrap-agent-skills.mts"
260
261
  }
@@ -94,6 +94,8 @@ ${defaultAssetPrefixSource}
94
94
  // load remoteEntry.js and exposed chunks from the remote origin, not the host.
95
95
  const assetPrefix =
96
96
  configuredModernAssetPrefix || configuredUltramodernAssetPrefix || defaultAssetPrefix;
97
+ const buildCacheTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';
98
+ const buildCacheDirectory = \`node_modules/.cache/rspack-\${appId}-\${buildCacheTarget}\`;
97
99
 
98
100
  if (
99
101
  cloudflareDeployEnabled &&
@@ -140,6 +142,10 @@ ${bffConfig} ...(cloudflareDeployEnabled
140
142
  splitRouteChunks: true,
141
143
  },
142
144
  performance: {
145
+ buildCache: {
146
+ cacheDigest: [appId, buildCacheTarget],
147
+ cacheDirectory: buildCacheDirectory,
148
+ },
143
149
  rsdoctor: {
144
150
  disableClientServer: true,
145
151
  enabled: process.env['ULTRAMODERN_RSDOCTOR'] === 'true',