@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.
- package/README.md +28 -0
- package/dist/cjs/ultramodern-tooling/commands.cjs +399 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +1 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +6 -0
- package/dist/cjs/ultramodern-workspace/package-json.cjs +2 -1
- package/dist/cjs/ultramodern-workspace/versions.cjs +12 -2
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +1 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +2 -0
- package/dist/esm/ultramodern-tooling/commands.js +399 -0
- package/dist/esm/ultramodern-workspace/contracts.js +1 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +6 -0
- package/dist/esm/ultramodern-workspace/package-json.js +2 -1
- package/dist/esm/ultramodern-workspace/versions.js +7 -3
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +1 -0
- package/dist/esm/ultramodern-workspace/write-workspace.js +3 -1
- package/dist/esm-node/ultramodern-tooling/commands.js +399 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +1 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +6 -0
- package/dist/esm-node/ultramodern-workspace/package-json.js +2 -1
- package/dist/esm-node/ultramodern-workspace/versions.js +7 -3
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +1 -0
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +3 -1
- package/dist/types/ultramodern-workspace/versions.d.ts +6 -2
- package/package.json +6 -6
- package/template-workspace/README.md.handlebars +44 -1
- package/template-workspace/pnpm-workspace.yaml.handlebars +8 -0
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +30 -8
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
83
|
+
"@rslib/core": "0.23.1",
|
|
84
84
|
"@types/node": "^26.0.1",
|
|
85
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
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.
|
|
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.
|
|
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 |
|