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

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 +28 -0
  2. package/dist/cjs/ultramodern-tooling/commands.cjs +399 -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 +2 -1
  6. package/dist/cjs/ultramodern-workspace/versions.cjs +12 -2
  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 +399 -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 +2 -1
  13. package/dist/esm/ultramodern-workspace/versions.js +7 -3
  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 +399 -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 +2 -1
  20. package/dist/esm-node/ultramodern-workspace/versions.js +7 -3
  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 +6 -2
  24. package/package.json +6 -6
  25. package/template-workspace/README.md.handlebars +44 -1
  26. package/template-workspace/pnpm-workspace.yaml.handlebars +8 -0
  27. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +30 -8
  28. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +23 -0
@@ -5,7 +5,9 @@ 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";
10
+ import { EFFECT_TSGO_VERSION, EFFECT_VERSION, EFFECT_VITEST_VERSION, OXFMT_VERSION, OXLINT_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION } from "../ultramodern-workspace/versions.js";
9
11
  import { createWorkspaceValidationScript } from "../ultramodern-workspace/workspace-scripts.js";
10
12
  import { readUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
11
13
  const commands_dirname = node_path.dirname(fileURLToPath(import.meta.url));
@@ -18,6 +20,7 @@ Commands:
18
20
  validate
19
21
  typecheck
20
22
  mf-types
23
+ migrate-strict-effect
21
24
  public-surface
22
25
  cloudflare-proof
23
26
  performance-readiness
@@ -83,6 +86,400 @@ Checks real Module Federation config files and DTS archives for exposed apps.
83
86
  });
84
87
  return 0;
85
88
  }
89
+ const modernPackageNames = new Set([
90
+ ...ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
91
+ ...ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
92
+ ]);
93
+ function readJsonFile(filePath) {
94
+ return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
95
+ }
96
+ function writeJsonFile(filePath, value) {
97
+ node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
98
+ }
99
+ function readOption(args, name) {
100
+ const prefix = `${name}=`;
101
+ const inline = args.find((arg)=>arg.startsWith(prefix));
102
+ if (inline) {
103
+ const value = inline.slice(prefix.length);
104
+ if (!value) throw new Error(`${name} needs a value.`);
105
+ return value;
106
+ }
107
+ const index = args.indexOf(name);
108
+ if (-1 === index) return;
109
+ const value = args[index + 1];
110
+ if (!value || value.startsWith('--')) throw new Error(`${name} needs a value.`);
111
+ return value;
112
+ }
113
+ function hasFlag(args, name) {
114
+ return args.includes(name);
115
+ }
116
+ function listWorkspacePackageFiles(workspaceRoot) {
117
+ const packageFiles = [
118
+ 'package.json'
119
+ ];
120
+ for (const directory of [
121
+ 'apps',
122
+ 'verticals',
123
+ 'packages'
124
+ ]){
125
+ const absoluteDirectory = node_path.join(workspaceRoot, directory);
126
+ if (node_fs.existsSync(absoluteDirectory)) for (const entry of node_fs.readdirSync(absoluteDirectory, {
127
+ withFileTypes: true
128
+ })){
129
+ if (!entry.isDirectory()) continue;
130
+ const packageFile = node_path.join(directory, entry.name, 'package.json');
131
+ if (node_fs.existsSync(node_path.join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
132
+ }
133
+ }
134
+ return packageFiles;
135
+ }
136
+ function updateModernDependencies(packageJson, packageSource) {
137
+ let changed = false;
138
+ for (const section of [
139
+ 'dependencies',
140
+ 'devDependencies',
141
+ 'peerDependencies',
142
+ 'optionalDependencies'
143
+ ]){
144
+ const dependencies = packageJson[section];
145
+ if (!(!dependencies || 'object' != typeof dependencies || Array.isArray(dependencies))) for (const packageName of Object.keys(dependencies)){
146
+ if (!modernPackageNames.has(packageName)) continue;
147
+ const nextSpecifier = modernPackageSpecifier(packageName, packageSource);
148
+ if (dependencies[packageName] !== nextSpecifier) {
149
+ dependencies[packageName] = nextSpecifier;
150
+ changed = true;
151
+ }
152
+ }
153
+ }
154
+ return changed;
155
+ }
156
+ const generatedToolingDependencyPins = new Map([
157
+ [
158
+ '@effect/tsgo',
159
+ EFFECT_TSGO_VERSION
160
+ ],
161
+ [
162
+ "@typescript/native-preview",
163
+ TYPESCRIPT_NATIVE_PREVIEW_VERSION
164
+ ],
165
+ [
166
+ 'oxfmt',
167
+ OXFMT_VERSION
168
+ ],
169
+ [
170
+ 'oxlint',
171
+ OXLINT_VERSION
172
+ ],
173
+ [
174
+ 'wrangler',
175
+ WRANGLER_VERSION
176
+ ],
177
+ [
178
+ 'zephyr-agent',
179
+ ZEPHYR_AGENT_VERSION
180
+ ],
181
+ [
182
+ 'zephyr-rspack-plugin',
183
+ ZEPHYR_RSPACK_PLUGIN_VERSION
184
+ ]
185
+ ]);
186
+ const strictEffectPackageVersionPolicyExclusions = [
187
+ `effect@${EFFECT_VERSION}`,
188
+ `@effect/opentelemetry@${EFFECT_VERSION}`
189
+ ];
190
+ function updateGeneratedToolingDependencies(packageJson) {
191
+ let changed = false;
192
+ for (const section of [
193
+ 'dependencies',
194
+ 'devDependencies',
195
+ 'peerDependencies',
196
+ 'optionalDependencies'
197
+ ]){
198
+ const dependencies = packageJson[section];
199
+ if (!(!dependencies || 'object' != typeof dependencies || Array.isArray(dependencies))) {
200
+ for (const [packageName, version] of generatedToolingDependencyPins)if (Object.prototype.hasOwnProperty.call(dependencies, packageName) && dependencies[packageName] !== version) {
201
+ dependencies[packageName] = version;
202
+ changed = true;
203
+ }
204
+ }
205
+ }
206
+ return changed;
207
+ }
208
+ const cloudflareModernDeployCommand = 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy';
209
+ const cloudflareModernDeploySkipBuildCommand = `${cloudflareModernDeployCommand} --skip-build`;
210
+ const cloudflareWranglerDeployCommand = 'wrangler deploy --config .output/wrangler.json';
211
+ const cloudflareWranglerDeployInvalidSkipBuildCommand = `${cloudflareWranglerDeployCommand} --skip-build`;
212
+ function updateGeneratedPackageScripts(packageJson) {
213
+ const scripts = packageJson.scripts;
214
+ if (!scripts || 'object' != typeof scripts || Array.isArray(scripts)) return false;
215
+ let changed = false;
216
+ const cloudflareBuild = scripts['cloudflare:build'];
217
+ if ('string' == typeof cloudflareBuild && cloudflareBuild.includes(cloudflareModernDeployCommand) && !cloudflareBuild.includes(cloudflareModernDeploySkipBuildCommand)) {
218
+ scripts['cloudflare:build'] = cloudflareBuild.replace(cloudflareModernDeployCommand, cloudflareModernDeploySkipBuildCommand);
219
+ changed = true;
220
+ }
221
+ const cloudflareDeploy = scripts['cloudflare:deploy'];
222
+ if ('string' == typeof cloudflareDeploy && cloudflareDeploy.includes(cloudflareWranglerDeployInvalidSkipBuildCommand)) {
223
+ scripts['cloudflare:deploy'] = cloudflareDeploy.replace(cloudflareWranglerDeployInvalidSkipBuildCommand, cloudflareWranglerDeployCommand);
224
+ changed = true;
225
+ }
226
+ return changed;
227
+ }
228
+ function normalizeStrictEffectApiMetadata(value) {
229
+ const api = value.api;
230
+ if (!api || 'object' != typeof api || Array.isArray(api)) return false;
231
+ let changed = false;
232
+ const oldEffect = api.effect;
233
+ if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
234
+ if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
235
+ api.stem = oldEffect.stem;
236
+ changed = true;
237
+ }
238
+ if (void 0 === api.prefix && 'string' == typeof oldEffect.prefix) {
239
+ api.prefix = oldEffect.prefix;
240
+ changed = true;
241
+ }
242
+ if (void 0 === api.consumedBy && Array.isArray(oldEffect.consumedBy)) {
243
+ api.consumedBy = oldEffect.consumedBy;
244
+ changed = true;
245
+ }
246
+ delete api.effect;
247
+ changed = true;
248
+ }
249
+ if (void 0 !== api.runtime && 'effect' !== api.runtime) {
250
+ api.runtime = 'effect';
251
+ changed = true;
252
+ }
253
+ if (api.bff && 'object' == typeof api.bff && !Array.isArray(api.bff)) {
254
+ if (true !== api.bff.strictEffectApproach) {
255
+ api.bff.strictEffectApproach = true;
256
+ changed = true;
257
+ }
258
+ }
259
+ if ('string' == typeof value.path) {
260
+ const directServerEntry = `${value.path}/api/index.ts`;
261
+ if ('string' == typeof api.serverEntry && /\/api\/effect\/index\.[cm]?[jt]sx?$/u.test(api.serverEntry)) {
262
+ api.serverEntry = directServerEntry;
263
+ changed = true;
264
+ }
265
+ if (api.contract && 'object' == typeof api.contract && !Array.isArray(api.contract)) {
266
+ if ('./shared/effect/api' === api.contract.export) {
267
+ api.contract.export = './api';
268
+ changed = true;
269
+ }
270
+ if ('string' == typeof api.contract.path && /\/shared\/effect\/api\.[cm]?[jt]sx?$/u.test(api.contract.path)) {
271
+ api.contract.path = `${value.path}/shared/api.ts`;
272
+ changed = true;
273
+ }
274
+ }
275
+ if (api.client && 'object' == typeof api.client && !Array.isArray(api.client)) {
276
+ if ('./effect/client' === api.client.export) {
277
+ api.client.export = './api/client';
278
+ changed = true;
279
+ }
280
+ if ('string' == typeof api.client.path && /\/src\/effect\/[^/]+-client\.[cm]?ts$/u.test(api.client.path)) {
281
+ const basename = node_path.basename(api.client.path);
282
+ api.client.path = `${value.path}/src/api/${basename}`;
283
+ changed = true;
284
+ }
285
+ }
286
+ if (void 0 === api.serverEntry && 'effect' === api.runtime) {
287
+ api.serverEntry = directServerEntry;
288
+ changed = true;
289
+ }
290
+ }
291
+ return changed;
292
+ }
293
+ function replaceYamlLine(source, pattern, replacement) {
294
+ const updated = source.replace(pattern, replacement);
295
+ return {
296
+ source: updated,
297
+ changed: updated !== source
298
+ };
299
+ }
300
+ function ensureYamlListItem(source, key, item) {
301
+ const itemLine = ` - '${item}'`;
302
+ const headerPattern = new RegExp(`^${key}:\\n(?:(?: - .+\\n)*)`, 'mu');
303
+ const header = source.match(headerPattern);
304
+ if (header) {
305
+ if (header[0].split('\n').includes(itemLine)) return {
306
+ source,
307
+ changed: false
308
+ };
309
+ return {
310
+ source: source.replace(headerPattern, `${header[0]}${itemLine}\n`),
311
+ changed: true
312
+ };
313
+ }
314
+ const block = `${key}:\n${itemLine}\n`;
315
+ const afterTrustPolicyIgnore = source.replace(/^(trustPolicyIgnoreAfter: .+\n)/mu, `$1${block}`);
316
+ if (afterTrustPolicyIgnore !== source) return {
317
+ source: afterTrustPolicyIgnore,
318
+ changed: true
319
+ };
320
+ return {
321
+ source: `${source.trimEnd()}\n${block}`,
322
+ changed: true
323
+ };
324
+ }
325
+ function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
326
+ const workspaceFile = node_path.join(workspaceRoot, 'pnpm-workspace.yaml');
327
+ if (!node_fs.existsSync(workspaceFile)) return false;
328
+ let source = node_fs.readFileSync(workspaceFile, 'utf-8');
329
+ let changed = false;
330
+ const replacements = [
331
+ [
332
+ /^ {4}'@effect\/vitest>effect': .+$/mu,
333
+ ` '@effect/vitest>effect': '${EFFECT_VERSION}'`
334
+ ],
335
+ [
336
+ /^ {2}'@effect\/vitest': .+$/mu,
337
+ ` '@effect/vitest': ${EFFECT_VITEST_VERSION}`
338
+ ],
339
+ [
340
+ /^ {2}effect: .+$/mu,
341
+ ` effect: ${EFFECT_VERSION}`
342
+ ]
343
+ ];
344
+ for (const [pattern, replacement] of replacements){
345
+ const result = replaceYamlLine(source, pattern, replacement);
346
+ source = result.source;
347
+ changed = result.changed || changed;
348
+ }
349
+ for (const item of strictEffectPackageVersionPolicyExclusions){
350
+ const packageName = item.slice(0, item.lastIndexOf('@'));
351
+ const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
352
+ const currentVersion = replaceYamlLine(source, new RegExp(`^ {2}- '${escapedPackageName}@[^']+'$`, 'gmu'), ` - '${item}'`);
353
+ source = currentVersion.source;
354
+ changed = currentVersion.changed || changed;
355
+ for (const policyKey of [
356
+ 'minimumReleaseAgeExclude',
357
+ 'trustPolicyExclude'
358
+ ]){
359
+ const policyExclude = ensureYamlListItem(source, policyKey, item);
360
+ source = policyExclude.source;
361
+ changed = policyExclude.changed || changed;
362
+ }
363
+ }
364
+ if (changed) node_fs.writeFileSync(workspaceFile, source, 'utf-8');
365
+ return changed;
366
+ }
367
+ function updateUltramodernConfig(workspaceRoot, packageSource) {
368
+ const configPath = node_path.join(workspaceRoot, '.modernjs/ultramodern.json');
369
+ const config = readJsonFile(configPath);
370
+ config.packageSource = {
371
+ strategy: packageSource.strategy,
372
+ modernPackageVersion: packageSource.modernPackageVersion,
373
+ ...packageSource.registry ? {
374
+ registry: packageSource.registry
375
+ } : {},
376
+ ...packageSource.aliasScope ? {
377
+ aliasScope: packageSource.aliasScope
378
+ } : {},
379
+ ...packageSource.aliasPackageNamePrefix ? {
380
+ aliasPackageNamePrefix: packageSource.aliasPackageNamePrefix
381
+ } : {}
382
+ };
383
+ for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
384
+ writeJsonFile(configPath, config);
385
+ }
386
+ function updateReferenceTopology(workspaceRoot) {
387
+ const topologyPath = node_path.join(workspaceRoot, 'topology/reference-topology.json');
388
+ if (!node_fs.existsSync(topologyPath)) return false;
389
+ const topology = readJsonFile(topologyPath);
390
+ let changed = false;
391
+ for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
392
+ if (changed) writeJsonFile(topologyPath, topology);
393
+ return changed;
394
+ }
395
+ function createMigrationPackageSource(args, current) {
396
+ const strategy = hasFlag(args, '--workspace') ? 'workspace' : 'install';
397
+ const registry = readOption(args, '--registry') ?? readOption(args, '--ultramodern-package-registry');
398
+ const explicitAliasScope = readOption(args, '--alias-scope') ?? readOption(args, '--ultramodern-package-scope');
399
+ const aliasScope = explicitAliasScope ?? ('install' === strategy && void 0 === registry ? current.packageSource?.aliasScope ?? BLEEDINGDEV_PACKAGE_SCOPE : current.packageSource?.aliasScope);
400
+ const aliasPackageNamePrefix = readOption(args, '--alias-package-name-prefix') ?? readOption(args, '--ultramodern-package-name-prefix') ?? current.packageSource?.aliasPackageNamePrefix ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0);
401
+ if ('workspace' === strategy) return {
402
+ strategy,
403
+ modernPackageVersion: WORKSPACE_PACKAGE_VERSION,
404
+ ...registry ? {
405
+ registry
406
+ } : {},
407
+ ...aliasScope ? {
408
+ aliasScope
409
+ } : {},
410
+ ...aliasPackageNamePrefix ? {
411
+ aliasPackageNamePrefix
412
+ } : {}
413
+ };
414
+ const version = readOption(args, '--version') ?? readOption(args, '--ultramodern-package-version') ?? current.packageSource?.modernPackageVersion;
415
+ if (!version || version === WORKSPACE_PACKAGE_VERSION) throw new Error('migrate-strict-effect needs --version <published-ultramodern-version> for install package source.');
416
+ return {
417
+ strategy,
418
+ modernPackageVersion: version,
419
+ ...registry ? {
420
+ registry
421
+ } : {},
422
+ ...aliasScope ? {
423
+ aliasScope
424
+ } : {},
425
+ ...aliasPackageNamePrefix ? {
426
+ aliasPackageNamePrefix
427
+ } : {}
428
+ };
429
+ }
430
+ function runPnpmLockfileRefresh(context) {
431
+ const result = spawnSync('pnpm', [
432
+ 'install',
433
+ '--lockfile-only',
434
+ "--ignore-scripts"
435
+ ], {
436
+ cwd: context.workspaceRoot,
437
+ stdio: 'inherit'
438
+ });
439
+ if (result.error) throw result.error;
440
+ return result.status ?? 1;
441
+ }
442
+ function runMigrateStrictEffect(args, context) {
443
+ if (args.includes('--help') || args.includes('-h')) {
444
+ process.stdout.write(`Usage:
445
+ modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
446
+
447
+ Updates generated UltraModern package-source metadata, Modern package aliases,
448
+ framework-owned toolchain pins, direct Effect API topology metadata, strict
449
+ Effect pnpm overrides/trust policy, and the pnpm lockfile. Source code still
450
+ has to pass pnpm api:check and pnpm contract:check.
451
+ `);
452
+ return 0;
453
+ }
454
+ const current = readUltramodernConfig(context.workspaceRoot);
455
+ const packageSource = createMigrationPackageSource(args, current);
456
+ updateUltramodernConfig(context.workspaceRoot, packageSource);
457
+ updateReferenceTopology(context.workspaceRoot);
458
+ for (const relativePackageFile of listWorkspacePackageFiles(context.workspaceRoot)){
459
+ const packageFile = node_path.join(context.workspaceRoot, relativePackageFile);
460
+ const packageJson = readJsonFile(packageFile);
461
+ if ('package.json' === relativePackageFile) {
462
+ packageJson.modernjs ??= {};
463
+ packageJson.modernjs.packageSource = {
464
+ strategy: packageSource.strategy,
465
+ config: './.modernjs/ultramodern.json'
466
+ };
467
+ }
468
+ const modernDependenciesChanged = updateModernDependencies(packageJson, packageSource);
469
+ const toolingDependenciesChanged = updateGeneratedToolingDependencies(packageJson);
470
+ const generatedScriptsChanged = updateGeneratedPackageScripts(packageJson);
471
+ const changed = modernDependenciesChanged || toolingDependenciesChanged || generatedScriptsChanged;
472
+ if (changed) writeJsonFile(packageFile, packageJson);
473
+ else if ('package.json' === relativePackageFile) writeJsonFile(packageFile, packageJson);
474
+ }
475
+ updateGeneratedPnpmWorkspacePolicy(context.workspaceRoot);
476
+ if (!hasFlag(args, '--skip-install')) {
477
+ const status = runPnpmLockfileRefresh(context);
478
+ if (0 !== status) return status;
479
+ }
480
+ process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
481
+ return 0;
482
+ }
86
483
  function runSkills(args, context) {
87
484
  const [subcommand, ...rest] = args;
88
485
  if ('install' === subcommand) return spawnNodeScript("template-workspace/scripts/bootstrap-agent-skills.mjs", rest, context);
@@ -113,6 +510,8 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
113
510
  });
114
511
  case 'mf-types':
115
512
  return runMfTypes(rest, context);
513
+ case 'migrate-strict-effect':
514
+ return runMigrateStrictEffect(rest, context);
116
515
  case 'public-surface':
117
516
  return spawnNodeScript("templates/workspace-scripts/generate-public-surface-assets.mjs", rest, context);
118
517
  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',
@@ -179,6 +179,7 @@ function createRootPackageJson(scope, packageSource, remotes = [], bridge) {
179
179
  'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mts --check",
180
180
  'mf:types': "node ./scripts/assert-mf-types.mts",
181
181
  'performance:readiness': "node ./scripts/ultramodern-performance-readiness.mts",
182
+ 'migrate:strict-effect': "node ./scripts/migrate-strict-effect.mts",
182
183
  'contract:check': "node ./scripts/validate-ultramodern-workspace.mts",
183
184
  'api:check': "node ./scripts/check-ultramodern-api-boundaries.mts",
184
185
  'i18n:boundaries': "node ./scripts/check-ultramodern-i18n-boundaries.mts",
@@ -371,7 +372,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [
371
372
  scripts: {
372
373
  dev: 'modern dev',
373
374
  build: app.exposes ? `ULTRAMODERN_ZEPHYR=false modern build && ${publicSurfaceBuildCommand} && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mts` : `ULTRAMODERN_ZEPHYR=false modern build && ${publicSurfaceBuildCommand}`,
374
- 'cloudflare:build': `ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ${publicSurfaceCloudflareBuildCommand} && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy && ${publicSurfaceCloudflareOutputCommand}`,
375
+ 'cloudflare:build': `ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ${publicSurfaceCloudflareBuildCommand} && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy --skip-build && ${publicSurfaceCloudflareOutputCommand}`,
375
376
  'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json',
376
377
  'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
377
378
  'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mts --app ${app.id}`,
@@ -9,12 +9,14 @@ const CLOUDFLARE_COMPATIBILITY_DATE = '2026-06-02';
9
9
  const TAILWIND_VERSION = '4.3.1';
10
10
  const TAILWIND_POSTCSS_VERSION = '4.3.1';
11
11
  const POSTCSS_VERSION = '8.5.15';
12
+ const EFFECT_VERSION = '4.0.0-beta.91';
13
+ const EFFECT_VITEST_VERSION = '4.0.0-beta.91';
12
14
  const EFFECT_TSGO_VERSION = '0.14.6';
13
15
  const TYPESCRIPT_STABLE_VERSION = '6.0.3';
14
- const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260624.1';
16
+ const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260628.1';
15
17
  const TYPESCRIPT_VERSION = TYPESCRIPT_STABLE_VERSION;
16
18
  const OXLINT_VERSION = '1.71.0';
17
- const OXFMT_VERSION = '0.55.0';
19
+ const OXFMT_VERSION = '0.56.0';
18
20
  const ULTRACITE_VERSION = '7.8.3';
19
21
  const LEFTHOOK_VERSION = '^2.1.9';
20
22
  const I18NEXT_VERSION = '26.3.1';
@@ -32,7 +34,9 @@ const ultramodernWorkspaceVersions = {
32
34
  tanstackRouter: TANSTACK_ROUTER_VERSION,
33
35
  tanstackRouterCore: TANSTACK_ROUTER_CORE_VERSION,
34
36
  moduleFederation: MODULE_FEDERATION_VERSION,
37
+ effect: EFFECT_VERSION,
38
+ effectVitest: EFFECT_VITEST_VERSION,
35
39
  tailwind: TAILWIND_VERSION,
36
40
  tailwindPostcss: TAILWIND_POSTCSS_VERSION
37
41
  };
38
- 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 };
42
+ 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 };
@@ -163,6 +163,7 @@ function writeGeneratedWorkspaceScripts(targetDir, _scope, _enableTailwind, _rem
163
163
  writeWorkspaceOwnedMtsScript(targetDir, 'proof-cloudflare-version', createToolWrapperScript('cloudflare-proof'));
164
164
  writeFileReplacing(targetDir, "scripts/ultramodern-performance-readiness.config.mjs", createPerformanceReadinessConfigScript());
165
165
  writeWorkspaceOwnedMtsScript(targetDir, 'ultramodern-performance-readiness', createToolWrapperScript('performance-readiness'));
166
+ writeWorkspaceOwnedMtsScript(targetDir, 'migrate-strict-effect', createToolWrapperScript('migrate-strict-effect'));
166
167
  writeWorkspaceOwnedMtsScript(targetDir, 'ultramodern-typecheck', createToolWrapperScript('typecheck'));
167
168
  writeWorkspaceOwnedMtsScript(targetDir, 'bootstrap-agent-skills', createSkillsToolWrapperScript());
168
169
  migrateCopiedWorkspaceScriptToMts(targetDir, 'setup-agent-reference-repos');
@@ -15,7 +15,7 @@ import { runCodeSmithOverlays } from "./overlays.js";
15
15
  import { createAppMfTypesTsConfig, createAppPackage, createAppTsConfig, createRootPackageJson, createRootTsConfig, createSharedContractsIndex, createSharedPackage, createSharedPackageTsConfig, createTsConfigBase } from "./package-json.js";
16
16
  import { resolvePackageSource } from "./package-source.js";
17
17
  import { createPublicWebAppArtifacts } from "./public-surface.js";
18
- import { NODE_FETCH_VERSION, NODE_VERSION, PNPM_VERSION, TANSTACK_ROUTER_CORE_VERSION, TANSTACK_ROUTER_VERSION, TYPESCRIPT_VERSION } from "./versions.js";
18
+ 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";
19
19
  import { writeGeneratedWorkspaceScripts } from "./workspace-scripts.js";
20
20
  function writeApp(targetDir, scope, app, packageSource, enableTailwind, remotes = [], bridge) {
21
21
  const resolvedApp = 'shell' === app.kind ? createShellHost(remotes) : app;
@@ -131,6 +131,8 @@ function generateUltramodernWorkspace(options) {
131
131
  nodeVersion: NODE_VERSION,
132
132
  pnpmVersion: PNPM_VERSION,
133
133
  nodeFetchVersion: NODE_FETCH_VERSION,
134
+ effectVersion: EFFECT_VERSION,
135
+ effectVitestVersion: EFFECT_VITEST_VERSION,
134
136
  tanstackRouterCoreVersion: TANSTACK_ROUTER_CORE_VERSION,
135
137
  tanstackRouterVersion: TANSTACK_ROUTER_VERSION,
136
138
  typescriptVersion: TYPESCRIPT_VERSION,
@@ -13,12 +13,14 @@ export declare const CLOUDFLARE_COMPATIBILITY_DATE = "2026-06-02";
13
13
  export declare const TAILWIND_VERSION = "4.3.1";
14
14
  export declare const TAILWIND_POSTCSS_VERSION = "4.3.1";
15
15
  export declare const POSTCSS_VERSION = "8.5.15";
16
+ export declare const EFFECT_VERSION = "4.0.0-beta.91";
17
+ export declare const EFFECT_VITEST_VERSION = "4.0.0-beta.91";
16
18
  export declare const EFFECT_TSGO_VERSION = "0.14.6";
17
19
  export declare const TYPESCRIPT_STABLE_VERSION = "6.0.3";
18
- export declare const TYPESCRIPT_NATIVE_PREVIEW_VERSION = "7.0.0-dev.20260624.1";
20
+ export declare const TYPESCRIPT_NATIVE_PREVIEW_VERSION = "7.0.0-dev.20260628.1";
19
21
  export declare const TYPESCRIPT_VERSION = "6.0.3";
20
22
  export declare const OXLINT_VERSION = "1.71.0";
21
- export declare const OXFMT_VERSION = "0.55.0";
23
+ export declare const OXFMT_VERSION = "0.56.0";
22
24
  export declare const ULTRACITE_VERSION = "7.8.3";
23
25
  export declare const LEFTHOOK_VERSION = "^2.1.9";
24
26
  export declare const I18NEXT_VERSION = "26.3.1";
@@ -36,6 +38,8 @@ export declare const ultramodernWorkspaceVersions: {
36
38
  tanstackRouter: string;
37
39
  tanstackRouterCore: string;
38
40
  moduleFederation: string;
41
+ effect: string;
42
+ effectVitest: string;
39
43
  tailwind: string;
40
44
  tailwindPostcss: string;
41
45
  };
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.5.0-ultramodern.0",
24
+ "version": "3.5.0-ultramodern.10",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/esm-node/index.js",
27
27
  "bin": {
@@ -75,14 +75,14 @@
75
75
  ],
76
76
  "dependencies": {
77
77
  "@modern-js/codesmith": "2.6.9",
78
- "oxfmt": "0.55.0",
78
+ "oxfmt": "0.56.0",
79
79
  "ultracite": "7.8.3",
80
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.5.0-ultramodern.0"
80
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.5.0-ultramodern.10"
81
81
  },
82
82
  "devDependencies": {
83
- "@rslib/core": "0.23.0",
83
+ "@rslib/core": "0.23.1",
84
84
  "@types/node": "^26.0.1",
85
- "@typescript/native-preview": "7.0.0-dev.20260624.1",
85
+ "@typescript/native-preview": "7.0.0-dev.20260628.1",
86
86
  "tsx": "^4.22.4",
87
87
  "typescript": "^6.0.3",
88
88
  "@scripts/rstest-config": "2.66.0"
@@ -99,6 +99,6 @@
99
99
  "test": "rm -rf dist && rslib build -c rslibconfig.mts && rstest --passWithNoTests"
100
100
  },
101
101
  "ultramodern": {
102
- "frameworkVersion": "3.5.0-ultramodern.0"
102
+ "frameworkVersion": "3.5.0-ultramodern.10"
103
103
  }
104
104
  }
@@ -69,7 +69,7 @@ typecheck, skills, i18n boundary validation, contract validation, and build as
69
69
  separate matrix jobs so failures are isolated and parallelizable.
70
70
 
71
71
  Type checking is TS7-first. `pnpm typecheck` runs
72
- `scripts/ultramodern-typecheck.mjs` in TS-Go build mode over
72
+ `scripts/ultramodern-typecheck.mts` in TS-Go build mode over
73
73
  `tsconfig.json` project references, with native-preview `--checkers` and
74
74
  `--builders` enabled by default. `@typescript/native-preview` is the TS7 latest
75
75
  dev compiler lane. The classic `typescript` package is pinned to latest TS6
@@ -147,11 +147,54 @@ ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP=https://shell-super-app.example.workers.d
147
147
  pnpm cloudflare:proof -- --require-public-urls
148
148
  ```
149
149
 
150
+ ## Strict Effect API
151
+
152
+ Generated HTTP APIs use the direct strict Effect topology only:
153
+
154
+ - API contracts live at `shared/api.ts`.
155
+ - Runtime entries live at `api/index.ts`.
156
+ - Browser clients live under `src/api/*-client.ts`.
157
+ - `modern.config.ts` uses `bff.runtimeFramework: 'effect'`,
158
+ `bff.effect.entry: './api/index'`, and
159
+ `bff.effect.strictEffectApproach: true`.
160
+
161
+ Do not add `api/effect`, `api/lambda`, `shared/effect`, `src/effect`, Hono
162
+ server imports, raw request handlers, manual `request.json()` parsing, or
163
+ manual `new Response(...)` construction inside generated API modules.
164
+ `pnpm api:check` and Oxlint enforce this. Model requests, responses, and typed
165
+ errors with concrete Effect `Schema` values; generic JSON shortcuts such as
166
+ `Schema.UnknownFromJsonString`, `Schema.Unknown`, and `Schema.Any` are rejected
167
+ in API modules.
168
+
169
+ Generated pnpm overrides pin the framework-compatible Effect cohort. Keep
170
+ `effect` and `@effect/vitest` aligned with `pnpm-workspace.yaml`; do not add
171
+ new direct package-level Effect versions unless the whole UltraModern cohort is
172
+ upgraded. The generated pnpm policy intentionally excludes the matching
173
+ `effect` and `@effect/opentelemetry` cohort versions from the
174
+ minimum-release-age and no-downgrade checks while Effect's beta publishes move
175
+ from trusted-publisher metadata to provenance attestations.
176
+
177
+ For older generated workspaces, run the framework migration command first:
178
+
179
+ ```bash
180
+ pnpm dlx @bleedingdev/modern-js-create@3.5.0-ultramodern.10 ultramodern \
181
+ migrate-strict-effect --version 3.5.0-ultramodern.10
182
+ pnpm api:check
183
+ pnpm contract:check
184
+ ```
185
+
186
+ The command updates generated package-source metadata, Modern package aliases,
187
+ framework-owned toolchain pins, direct API topology metadata, strict Effect pnpm
188
+ overrides/trust policy, and the lockfile. Remaining failures are source
189
+ migration work; fix the owning API files instead of adding compatibility shims.
190
+
150
191
  ## Troubleshooting
151
192
 
152
193
  | Symptom | Current check | Owner |
153
194
  | --- | --- | --- |
154
195
  | Package cohort mismatch | Regenerate with one package source strategy, run `mise install`, then rerun `pnpm install` from the activated shell. | Generated workspace package source metadata |
196
+ | Effect runtime cohort mismatch | Keep `effect` and `@effect/vitest` on the generated pnpm override versions, then rerun `pnpm install`. | Generated workspace dependency policy |
197
+ | Old nested API path | Run `pnpm api:check`, move code to `api/index.ts`, `shared/api.ts`, and `src/api/*`, then delete `api/effect`, `api/lambda`, `shared/effect`, and `src/effect`. | API owner |
155
198
  | Install failure | Check the active Node/pnpm from `mise install`; rerun `pnpm install` after the shell sees the pinned versions. | Toolchain setup |
156
199
  | Build failure | Run the matching primitive gate (`pnpm lint`, `pnpm typecheck`, `pnpm i18n:boundaries`, `pnpm contract:check`) before `pnpm build`; fix the owning failure first. | Owning package or generated contract |
157
200
  | Missing public URL | Set the app public URL env key recorded in `.modernjs/ultramodern.json`, for example `ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP`. | Deployment operator |