@bleedingdev/modern-js-create 3.5.0-ultramodern.27 → 3.5.0-ultramodern.29
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/dist/cjs/ultramodern-tooling/commands.cjs +497 -155
- package/dist/cjs/ultramodern-tooling/config.cjs +149 -8
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +25 -0
- package/dist/cjs/ultramodern-workspace/api.cjs +44 -0
- package/dist/cjs/ultramodern-workspace/backend-federation.cjs +282 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +24 -8
- package/dist/cjs/ultramodern-workspace/delivery-unit-sync.cjs +157 -0
- package/dist/cjs/ultramodern-workspace/delivery-unit.cjs +88 -0
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +24 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +122 -44
- package/dist/cjs/ultramodern-workspace/package-json.cjs +11 -34
- package/dist/cjs/ultramodern-workspace/pnpm-workspace-policy-plan.cjs +166 -0
- package/dist/cjs/ultramodern-workspace/tooling-command-catalog.cjs +153 -0
- package/dist/cjs/ultramodern-workspace/workspace-script-plan.cjs +175 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +43 -62
- package/dist/cjs/ultramodern-workspace/workspace-validation-contract.cjs +97 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +9 -3
- package/dist/cjs/ultramodern-workspace/zerops.cjs +100 -0
- package/dist/esm/ultramodern-tooling/commands.js +500 -158
- package/dist/esm/ultramodern-tooling/config.js +144 -9
- package/dist/esm/ultramodern-workspace/add-vertical.js +26 -1
- package/dist/esm/ultramodern-workspace/api.js +43 -2
- package/dist/esm/ultramodern-workspace/backend-federation.js +217 -0
- package/dist/esm/ultramodern-workspace/contracts.js +24 -8
- package/dist/esm/ultramodern-workspace/delivery-unit-sync.js +108 -0
- package/dist/esm/ultramodern-workspace/delivery-unit.js +31 -0
- package/dist/esm/ultramodern-workspace/descriptors.js +13 -1
- package/dist/esm/ultramodern-workspace/module-federation.js +121 -36
- package/dist/esm/ultramodern-workspace/package-json.js +11 -31
- package/dist/esm/ultramodern-workspace/pnpm-workspace-policy-plan.js +113 -0
- package/dist/esm/ultramodern-workspace/tooling-command-catalog.js +100 -0
- package/dist/esm/ultramodern-workspace/workspace-script-plan.js +110 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +35 -63
- package/dist/esm/ultramodern-workspace/workspace-validation-contract.js +59 -0
- package/dist/esm/ultramodern-workspace/write-workspace.js +11 -5
- package/dist/esm/ultramodern-workspace/zerops.js +62 -0
- package/dist/esm-node/ultramodern-tooling/commands.js +500 -158
- package/dist/esm-node/ultramodern-tooling/config.js +144 -9
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +26 -1
- package/dist/esm-node/ultramodern-workspace/api.js +43 -2
- package/dist/esm-node/ultramodern-workspace/backend-federation.js +218 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +24 -8
- package/dist/esm-node/ultramodern-workspace/delivery-unit-sync.js +109 -0
- package/dist/esm-node/ultramodern-workspace/delivery-unit.js +32 -0
- package/dist/esm-node/ultramodern-workspace/descriptors.js +13 -1
- package/dist/esm-node/ultramodern-workspace/module-federation.js +121 -36
- package/dist/esm-node/ultramodern-workspace/package-json.js +11 -31
- package/dist/esm-node/ultramodern-workspace/pnpm-workspace-policy-plan.js +114 -0
- package/dist/esm-node/ultramodern-workspace/tooling-command-catalog.js +101 -0
- package/dist/esm-node/ultramodern-workspace/workspace-script-plan.js +111 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +35 -63
- package/dist/esm-node/ultramodern-workspace/workspace-validation-contract.js +60 -0
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +11 -5
- package/dist/esm-node/ultramodern-workspace/zerops.js +63 -0
- package/dist/types/ultramodern-tooling/config.d.ts +6 -0
- package/dist/types/ultramodern-workspace/api.d.ts +1 -0
- package/dist/types/ultramodern-workspace/backend-federation.d.ts +11 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +1 -1
- package/dist/types/ultramodern-workspace/delivery-unit-sync.d.ts +6 -0
- package/dist/types/ultramodern-workspace/delivery-unit.d.ts +27 -0
- package/dist/types/ultramodern-workspace/descriptors.d.ts +4 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +4 -9
- package/dist/types/ultramodern-workspace/package-json.d.ts +0 -1
- package/dist/types/ultramodern-workspace/pnpm-workspace-policy-plan.d.ts +39 -0
- package/dist/types/ultramodern-workspace/tooling-command-catalog.d.ts +18 -0
- package/dist/types/ultramodern-workspace/workspace-script-plan.d.ts +72 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +3 -0
- package/dist/types/ultramodern-workspace/workspace-validation-contract.d.ts +42 -0
- package/dist/types/ultramodern-workspace/zerops.d.ts +2 -0
- package/package.json +3 -3
- package/template-workspace/.gitignore.handlebars +1 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +11 -3
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +40 -2
- package/templates/workspace-scripts/generate-node-backend-federation.mjs +270 -0
- package/templates/workspace-scripts/materialize-zerops-runtime.mjs +448 -0
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +234 -8
- package/templates/workspace-scripts/proof-node-backend-federation.mjs +729 -0
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +188 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +322 -8
|
@@ -7,28 +7,30 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { resolveCreatePackageRoot } from "../create-package-root.js";
|
|
8
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";
|
|
9
9
|
import { createAppEnvDts } from "../ultramodern-workspace/app-files.js";
|
|
10
|
+
import { createBackendFederationContractFile } from "../ultramodern-workspace/backend-federation.js";
|
|
11
|
+
import { runSyncDeliveryUnit } from "../ultramodern-workspace/delivery-unit-sync.js";
|
|
10
12
|
import { validateModuleFederationTypes } from "../ultramodern-workspace/mf-validation.js";
|
|
11
|
-
import { createAppModernConfig } from "../ultramodern-workspace/module-federation.js";
|
|
13
|
+
import { createAppModernConfig, createUltramodernBuildModule, createUltramodernBuildReexportModule } from "../ultramodern-workspace/module-federation.js";
|
|
12
14
|
import { createAppMfTypesTsConfig, createAppTsConfig, createSharedPackageTsConfig, createTsConfigBase } from "../ultramodern-workspace/package-json.js";
|
|
15
|
+
import { GENERATED_TOOLING_COMMANDS, generatedToolingCommandList, generatedToolingCommands } from "../ultramodern-workspace/tooling-command-catalog.js";
|
|
13
16
|
import { DRIZZLE_ORM_VERSION, EFFECT_TSGO_VERSION, EFFECT_VERSION, EFFECT_VITEST_VERSION, MODULE_FEDERATION_VERSION, OXFMT_VERSION, OXLINT_VERSION, TYPESCRIPT_NATIVE_PREVIEW_VERSION, WRANGLER_VERSION, ZEPHYR_AGENT_VERSION, ZEPHYR_RSPACK_PLUGIN_VERSION } from "../ultramodern-workspace/versions.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
17
|
+
import { createWorkspaceAppPackageScripts, createWorkspaceRootPackageScripts } from "../ultramodern-workspace/workspace-script-plan.js";
|
|
18
|
+
import { createWorkspaceValidationScript, createZeropsRuntimeMaterializationScript, writeGeneratedToolWrapperScripts } from "../ultramodern-workspace/workspace-scripts.js";
|
|
19
|
+
import { createZeropsYaml } from "../ultramodern-workspace/zerops.js";
|
|
20
|
+
import { normalizeCompactUltramodernConfig, readUltramodernConfig, synthesizeCompactUltramodernConfig, workspaceAppsFromToolingConfig } from "./config.js";
|
|
16
21
|
const commands_dirname = node_path.dirname(fileURLToPath(import.meta.url));
|
|
17
22
|
const createPackageRoot = resolveCreatePackageRoot(commands_dirname);
|
|
18
23
|
function printHelp() {
|
|
24
|
+
const commands = [
|
|
25
|
+
...generatedToolingCommandList().map((command)=>` ${command}`),
|
|
26
|
+
' skills install',
|
|
27
|
+
' skills check'
|
|
28
|
+
].join('\n');
|
|
19
29
|
process.stdout.write(`Usage:
|
|
20
30
|
modern-js-create ultramodern <command> [args]
|
|
21
31
|
|
|
22
32
|
Commands:
|
|
23
|
-
|
|
24
|
-
typecheck
|
|
25
|
-
mf-types
|
|
26
|
-
migrate-strict-effect
|
|
27
|
-
public-surface
|
|
28
|
-
cloudflare-proof
|
|
29
|
-
performance-readiness
|
|
30
|
-
skills install
|
|
31
|
-
skills check
|
|
33
|
+
${commands}
|
|
32
34
|
`);
|
|
33
35
|
}
|
|
34
36
|
function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
|
|
@@ -47,6 +49,13 @@ function spawnNodeScript(relativeScriptPath, args, context, options = {}) {
|
|
|
47
49
|
if (result.error) throw result.error;
|
|
48
50
|
return result.status ?? 1;
|
|
49
51
|
}
|
|
52
|
+
function runTemplateBackedToolingCommand(command, args, context) {
|
|
53
|
+
const toolingCommand = generatedToolingCommands.find((candidate)=>candidate.command === command);
|
|
54
|
+
if (!toolingCommand?.templatePath) return;
|
|
55
|
+
return spawnNodeScript(toolingCommand.templatePath, args, context, {
|
|
56
|
+
cwd: 'invocation' === toolingCommand.cwd ? context.invocationCwd : context.workspaceRoot
|
|
57
|
+
});
|
|
58
|
+
}
|
|
50
59
|
function runRenderedModule(source, context) {
|
|
51
60
|
const tempDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'ultramodern-tool-'));
|
|
52
61
|
const tempFile = node_path.join(tempDir, 'command.mjs');
|
|
@@ -93,11 +102,45 @@ const modernPackageNames = new Set([
|
|
|
93
102
|
...ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES,
|
|
94
103
|
...ULTRAMODERN_WORKSPACE_MODERN_PACKAGES
|
|
95
104
|
]);
|
|
105
|
+
function createMigrationIo(workspaceRoot, dryRun) {
|
|
106
|
+
const plan = [];
|
|
107
|
+
const rel = (p)=>(node_path.relative(workspaceRoot, p) || node_path.basename(p)).split(node_path.sep).join('/');
|
|
108
|
+
return {
|
|
109
|
+
workspaceRoot,
|
|
110
|
+
dryRun,
|
|
111
|
+
plan,
|
|
112
|
+
write (filePath, content) {
|
|
113
|
+
if (node_fs.existsSync(filePath) && node_fs.readFileSync(filePath, 'utf-8') === content) return false;
|
|
114
|
+
if (dryRun) {
|
|
115
|
+
plan.push(`[dry-run] would write ${rel(filePath)}`);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
node_fs.mkdirSync(node_path.dirname(filePath), {
|
|
119
|
+
recursive: true
|
|
120
|
+
});
|
|
121
|
+
node_fs.writeFileSync(filePath, content, 'utf-8');
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
remove (filePath) {
|
|
125
|
+
if (!node_fs.existsSync(filePath)) return false;
|
|
126
|
+
if (dryRun) {
|
|
127
|
+
plan.push(`[dry-run] would delete ${rel(filePath)}`);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
node_fs.rmSync(filePath);
|
|
131
|
+
return true;
|
|
132
|
+
},
|
|
133
|
+
log (message) {
|
|
134
|
+
if (dryRun) plan.push(`[dry-run] ${message}`);
|
|
135
|
+
else process.stdout.write(`[ultramodern] ${message}\n`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
96
139
|
function readJsonFile(filePath) {
|
|
97
140
|
return JSON.parse(node_fs.readFileSync(filePath, 'utf-8'));
|
|
98
141
|
}
|
|
99
|
-
function writeJsonFile(filePath, value) {
|
|
100
|
-
|
|
142
|
+
function writeJsonFile(io, filePath, value) {
|
|
143
|
+
return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
101
144
|
}
|
|
102
145
|
function readOption(args, name) {
|
|
103
146
|
const prefix = `${name}=`;
|
|
@@ -130,7 +173,7 @@ function listWorkspacePackageFiles(workspaceRoot) {
|
|
|
130
173
|
withFileTypes: true
|
|
131
174
|
})){
|
|
132
175
|
if (!entry.isDirectory()) continue;
|
|
133
|
-
const packageFile =
|
|
176
|
+
const packageFile = `${directory}/${entry.name}/package.json`;
|
|
134
177
|
if (node_fs.existsSync(node_path.join(workspaceRoot, packageFile))) packageFiles.push(packageFile);
|
|
135
178
|
}
|
|
136
179
|
}
|
|
@@ -220,15 +263,36 @@ const cloudflareModernDeployCommand = 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=
|
|
|
220
263
|
const cloudflareModernDeploySkipBuildCommand = `${cloudflareModernDeployCommand} --skip-build`;
|
|
221
264
|
const cloudflareWranglerDeployCommand = 'wrangler deploy --config .output/wrangler.json';
|
|
222
265
|
const cloudflareWranglerDeployInvalidSkipBuildCommand = `${cloudflareWranglerDeployCommand} --skip-build`;
|
|
223
|
-
function
|
|
266
|
+
function removeStaleBackendFederationCommandSegments(command) {
|
|
267
|
+
return command.replace(/\s+&&\s+node\s+\S*scripts\/generate-node-backend-federation\.m[ct]s(?:\s+--app\s+\S+)?(?:\s+--target\s+\S+)?(?=\s+&&|$)/gu, '');
|
|
268
|
+
}
|
|
269
|
+
function updateGeneratedPackageScripts(packageJson, options = {}) {
|
|
224
270
|
const scripts = packageJson.scripts;
|
|
225
271
|
if (!scripts || 'object' != typeof scripts || Array.isArray(scripts)) return false;
|
|
226
272
|
let changed = false;
|
|
273
|
+
const apps = options.apps ?? [];
|
|
274
|
+
const app = apps.find((candidate)=>`${candidate.directory}/package.json` === options.relativePackageFile);
|
|
275
|
+
const expectedScripts = 'package.json' === options.relativePackageFile ? createWorkspaceRootPackageScripts(apps.filter((candidate)=>'shell' !== candidate.kind)) : app ? createWorkspaceAppPackageScripts(app) : void 0;
|
|
276
|
+
if (expectedScripts) {
|
|
277
|
+
for (const [name, value] of Object.entries(expectedScripts))if (scripts[name] !== value) {
|
|
278
|
+
scripts[name] = value;
|
|
279
|
+
changed = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const build = scripts.build;
|
|
283
|
+
if ('string' == typeof build) {
|
|
284
|
+
const nextBuild = removeStaleBackendFederationCommandSegments(build);
|
|
285
|
+
if (nextBuild !== build) {
|
|
286
|
+
scripts.build = nextBuild;
|
|
287
|
+
changed = true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
227
290
|
const cloudflareBuild = scripts['cloudflare:build'];
|
|
228
291
|
if ('string' == typeof cloudflareBuild) {
|
|
229
|
-
let nextCloudflareBuild = cloudflareBuild;
|
|
292
|
+
let nextCloudflareBuild = removeStaleBackendFederationCommandSegments(cloudflareBuild);
|
|
230
293
|
if (nextCloudflareBuild.includes(cloudflareModernDeployCommand) && !nextCloudflareBuild.includes(cloudflareModernDeploySkipBuildCommand)) nextCloudflareBuild = nextCloudflareBuild.replace(cloudflareModernDeployCommand, cloudflareModernDeploySkipBuildCommand);
|
|
231
294
|
nextCloudflareBuild = nextCloudflareBuild.replace(/ && node \S*scripts\/generate-public-surface-assets\.m[ct]s --app [^&]+ --target dist(?= && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy --skip-build)/u, '');
|
|
295
|
+
nextCloudflareBuild = nextCloudflareBuild.replace(/ && node \S*scripts\/verify-cloudflare-output\.m[ct]s(?: --app [^&]+)?/u, '');
|
|
232
296
|
if (nextCloudflareBuild !== cloudflareBuild) {
|
|
233
297
|
scripts['cloudflare:build'] = nextCloudflareBuild;
|
|
234
298
|
changed = true;
|
|
@@ -242,9 +306,18 @@ function updateGeneratedPackageScripts(packageJson) {
|
|
|
242
306
|
return changed;
|
|
243
307
|
}
|
|
244
308
|
function normalizeStrictEffectApiMetadata(value) {
|
|
245
|
-
const api = value.api;
|
|
246
|
-
if (!api || 'object' != typeof api || Array.isArray(api)) return false;
|
|
247
309
|
let changed = false;
|
|
310
|
+
const backendFederation = value.backendFederation;
|
|
311
|
+
if (backendFederation && 'object' == typeof backendFederation && !Array.isArray(backendFederation) && Object.hasOwn(backendFederation, 'entry')) {
|
|
312
|
+
delete backendFederation.entry;
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
315
|
+
const api = value.api;
|
|
316
|
+
if (!api || 'object' != typeof api || Array.isArray(api)) return changed;
|
|
317
|
+
if (void 0 !== api.backendFederation) {
|
|
318
|
+
delete api.backendFederation;
|
|
319
|
+
changed = true;
|
|
320
|
+
}
|
|
248
321
|
const oldEffect = api.effect;
|
|
249
322
|
if (oldEffect && 'object' == typeof oldEffect && !Array.isArray(oldEffect)) {
|
|
250
323
|
if (void 0 === api.stem && 'string' == typeof oldEffect.stem) {
|
|
@@ -306,6 +379,38 @@ function normalizeStrictEffectApiMetadata(value) {
|
|
|
306
379
|
}
|
|
307
380
|
return changed;
|
|
308
381
|
}
|
|
382
|
+
function removeGeneratedFileIfExists(io, relativePath) {
|
|
383
|
+
return io.remove(node_path.join(io.workspaceRoot, relativePath));
|
|
384
|
+
}
|
|
385
|
+
function removeStaleBackendFederationArtifacts(io, config) {
|
|
386
|
+
let changed = false;
|
|
387
|
+
for (const relativePath of [
|
|
388
|
+
"scripts/generate-node-backend-federation.mts",
|
|
389
|
+
"scripts/proof-node-backend-federation.mts",
|
|
390
|
+
"scripts/verify-cloudflare-output.mts"
|
|
391
|
+
])changed = removeGeneratedFileIfExists(io, relativePath) || changed;
|
|
392
|
+
for (const app of workspaceAppsFromToolingConfig(config))changed = removeGeneratedFileIfExists(io, node_path.join(app.directory, 'api/backend-federation.ts')) || changed;
|
|
393
|
+
return changed;
|
|
394
|
+
}
|
|
395
|
+
function updateGeneratedZeropsArtifacts(io, config) {
|
|
396
|
+
const apps = workspaceAppsFromToolingConfig(config);
|
|
397
|
+
let changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, 'zerops.yaml'), `${createZeropsYaml(config.workspace.packageScope, apps)}\n`);
|
|
398
|
+
changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, "scripts/materialize-zerops-runtime.mjs"), createZeropsRuntimeMaterializationScript()) || changed;
|
|
399
|
+
return changed;
|
|
400
|
+
}
|
|
401
|
+
function updateGeneratedBuildIdentityModules(io, config) {
|
|
402
|
+
let changed = false;
|
|
403
|
+
for (const app of workspaceAppsFromToolingConfig(config)){
|
|
404
|
+
changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'src/ultramodern-build.ts'), createUltramodernBuildReexportModule()) || changed;
|
|
405
|
+
changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'shared/ultramodern-build.ts'), createUltramodernBuildModule(config.workspace.packageScope, app)) || changed;
|
|
406
|
+
}
|
|
407
|
+
return changed;
|
|
408
|
+
}
|
|
409
|
+
function updateGeneratedBackendFederationContractFiles(io, config) {
|
|
410
|
+
let changed = false;
|
|
411
|
+
for (const app of workspaceAppsFromToolingConfig(config))if (app.api) changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'api/backend-federation.ts'), createBackendFederationContractFile(app)) || changed;
|
|
412
|
+
return changed;
|
|
413
|
+
}
|
|
309
414
|
function replaceYamlLine(source, pattern, replacement) {
|
|
310
415
|
const updated = source.replace(pattern, replacement);
|
|
311
416
|
return {
|
|
@@ -338,22 +443,35 @@ function ensureYamlListItem(source, key, item) {
|
|
|
338
443
|
changed: true
|
|
339
444
|
};
|
|
340
445
|
}
|
|
341
|
-
function
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const currentEntry = source.match(currentEntryPattern);
|
|
347
|
-
if (currentEntry) {
|
|
348
|
-
if (currentEntry[0] === entryLine) return {
|
|
349
|
-
source,
|
|
350
|
-
changed: false
|
|
351
|
-
};
|
|
352
|
-
return {
|
|
353
|
-
source: source.replace(currentEntryPattern, entryLine),
|
|
354
|
-
changed: true
|
|
355
|
-
};
|
|
446
|
+
function yamlEntryPattern(entryKey, scalar = false) {
|
|
447
|
+
const bareKey = entryKey.replace(/^['"]|['"]$/gu, '');
|
|
448
|
+
if (scalar) {
|
|
449
|
+
const esc = bareKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
450
|
+
return new RegExp(`^ {2}(?:'${esc}'|"${esc}"|${esc}): .+$`, 'gmu');
|
|
356
451
|
}
|
|
452
|
+
const packageName = bareKey.includes('@') ? bareKey.slice(0, bareKey.lastIndexOf('@')) : bareKey;
|
|
453
|
+
const esc = packageName.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
454
|
+
return new RegExp(`^ {2}(?:'${esc}@[^']+'|"${esc}@[^"]+"|${esc}@[^:'"\\s]+): .+$`, 'gmu');
|
|
455
|
+
}
|
|
456
|
+
function upsertYamlEntry(source, key, entryLine, pattern) {
|
|
457
|
+
const linePattern = new RegExp(pattern.source, 'u');
|
|
458
|
+
const lines = source.split('\n');
|
|
459
|
+
let seen = false;
|
|
460
|
+
let changed = false;
|
|
461
|
+
const out = [];
|
|
462
|
+
for (const line of lines)if (linePattern.test(line)) {
|
|
463
|
+
if (seen) {
|
|
464
|
+
changed = true;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
seen = true;
|
|
468
|
+
if (line !== entryLine) changed = true;
|
|
469
|
+
out.push(entryLine);
|
|
470
|
+
} else out.push(line);
|
|
471
|
+
if (seen) return {
|
|
472
|
+
source: out.join('\n'),
|
|
473
|
+
changed
|
|
474
|
+
};
|
|
357
475
|
const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
|
|
358
476
|
const header = source.match(headerPattern);
|
|
359
477
|
if (header) {
|
|
@@ -371,62 +489,43 @@ function ensureYamlMapEntry(source, key, entryKey, value) {
|
|
|
371
489
|
changed: true
|
|
372
490
|
};
|
|
373
491
|
}
|
|
492
|
+
function ensureYamlMapEntry(source, key, entryKey, value) {
|
|
493
|
+
return upsertYamlEntry(source, key, ` '${entryKey}': ${value}`, yamlEntryPattern(entryKey));
|
|
494
|
+
}
|
|
374
495
|
function ensureYamlScalarMapEntry(source, key, entryKey, value) {
|
|
375
|
-
|
|
376
|
-
const escapedEntryKey = entryKey.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
377
|
-
const currentEntryPattern = new RegExp(`^ {2}${escapedEntryKey}: .+$`, 'mu');
|
|
378
|
-
const currentEntry = source.match(currentEntryPattern);
|
|
379
|
-
if (currentEntry) {
|
|
380
|
-
if (currentEntry[0] === entryLine) return {
|
|
381
|
-
source,
|
|
382
|
-
changed: false
|
|
383
|
-
};
|
|
384
|
-
return {
|
|
385
|
-
source: source.replace(currentEntryPattern, entryLine),
|
|
386
|
-
changed: true
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
const headerPattern = new RegExp(`^${key}:\\n(?:(?: .+\\n)*)`, 'mu');
|
|
390
|
-
const header = source.match(headerPattern);
|
|
391
|
-
if (header) return {
|
|
392
|
-
source: source.replace(headerPattern, `${header[0]}${entryLine}\n`),
|
|
393
|
-
changed: true
|
|
394
|
-
};
|
|
395
|
-
return {
|
|
396
|
-
source: `${source.trimEnd()}\n${key}:\n${entryLine}\n`,
|
|
397
|
-
changed: true
|
|
398
|
-
};
|
|
496
|
+
return upsertYamlEntry(source, key, ` ${entryKey}: ${value}`, yamlEntryPattern(entryKey, true));
|
|
399
497
|
}
|
|
400
498
|
function removeYamlMapEntry(source, entryKey) {
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
499
|
+
const linePattern = new RegExp(yamlEntryPattern(entryKey).source, 'u');
|
|
500
|
+
const lines = source.split('\n');
|
|
501
|
+
let changed = false;
|
|
502
|
+
const out = [];
|
|
503
|
+
for (const line of lines){
|
|
504
|
+
if (linePattern.test(line)) {
|
|
505
|
+
changed = true;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
out.push(line);
|
|
509
|
+
}
|
|
510
|
+
return changed ? {
|
|
511
|
+
source: out.join('\n'),
|
|
512
|
+
changed
|
|
513
|
+
} : {
|
|
405
514
|
source,
|
|
406
|
-
changed
|
|
407
|
-
};
|
|
408
|
-
return {
|
|
409
|
-
source: source.replace(currentEntryPattern, ''),
|
|
410
|
-
changed: true
|
|
515
|
+
changed
|
|
411
516
|
};
|
|
412
517
|
}
|
|
413
|
-
function ensureGeneratedPatchFile(
|
|
414
|
-
const targetPath = node_path.join(workspaceRoot, relativePatchPath);
|
|
518
|
+
function ensureGeneratedPatchFile(io, relativePatchPath, sourcePatchPath) {
|
|
519
|
+
const targetPath = node_path.join(io.workspaceRoot, relativePatchPath);
|
|
415
520
|
const patch = node_fs.readFileSync(sourcePatchPath, 'utf-8');
|
|
416
|
-
|
|
417
|
-
node_fs.mkdirSync(node_path.dirname(targetPath), {
|
|
418
|
-
recursive: true
|
|
419
|
-
});
|
|
420
|
-
node_fs.writeFileSync(targetPath, patch, 'utf-8');
|
|
421
|
-
return true;
|
|
521
|
+
return io.write(targetPath, patch);
|
|
422
522
|
}
|
|
423
|
-
function removeGeneratedPatchFileIfUnchanged(
|
|
424
|
-
const targetPath = node_path.join(workspaceRoot, relativePatchPath);
|
|
523
|
+
function removeGeneratedPatchFileIfUnchanged(io, relativePatchPath, sourcePatchPath) {
|
|
524
|
+
const targetPath = node_path.join(io.workspaceRoot, relativePatchPath);
|
|
425
525
|
if (!node_fs.existsSync(targetPath)) return false;
|
|
426
526
|
const patch = node_fs.readFileSync(sourcePatchPath, 'utf-8');
|
|
427
527
|
if (node_fs.readFileSync(targetPath, 'utf-8') !== patch) return false;
|
|
428
|
-
|
|
429
|
-
return true;
|
|
528
|
+
return io.remove(targetPath);
|
|
430
529
|
}
|
|
431
530
|
function workspaceUsesDependency(workspaceRoot, packageName) {
|
|
432
531
|
const packageJsonPaths = [
|
|
@@ -463,20 +562,20 @@ function workspaceUsesDependency(workspaceRoot, packageName) {
|
|
|
463
562
|
}
|
|
464
563
|
return false;
|
|
465
564
|
}
|
|
466
|
-
function ensureGeneratedDeclarationPatches(
|
|
565
|
+
function ensureGeneratedDeclarationPatches(io, options) {
|
|
467
566
|
let changed = false;
|
|
468
|
-
changed = ensureGeneratedPatchFile(
|
|
469
|
-
changed = ensureGeneratedPatchFile(
|
|
470
|
-
changed = ensureGeneratedPatchFile(
|
|
471
|
-
changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(
|
|
567
|
+
changed = ensureGeneratedPatchFile(io, moduleFederationModernJsPatchPath, moduleFederationModernJsPatchSourcePath) || changed;
|
|
568
|
+
changed = ensureGeneratedPatchFile(io, moduleFederationBridgeReactPatchPath, moduleFederationBridgeReactPatchSourcePath) || changed;
|
|
569
|
+
changed = ensureGeneratedPatchFile(io, effectDeclarationPatchPath, effectDeclarationPatchSourcePath) || changed;
|
|
570
|
+
changed = options.includeDrizzleOrmPatch ? ensureGeneratedPatchFile(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed : removeGeneratedPatchFileIfUnchanged(io, drizzleOrmDeclarationPatchPath, drizzleOrmDeclarationPatchSourcePath) || changed;
|
|
472
571
|
return changed;
|
|
473
572
|
}
|
|
474
|
-
function updateGeneratedPnpmWorkspacePolicy(
|
|
475
|
-
const workspaceFile = node_path.join(workspaceRoot, 'pnpm-workspace.yaml');
|
|
573
|
+
function updateGeneratedPnpmWorkspacePolicy(io) {
|
|
574
|
+
const workspaceFile = node_path.join(io.workspaceRoot, 'pnpm-workspace.yaml');
|
|
476
575
|
if (!node_fs.existsSync(workspaceFile)) return false;
|
|
477
576
|
let source = node_fs.readFileSync(workspaceFile, 'utf-8');
|
|
478
577
|
let changed = false;
|
|
479
|
-
const usesDrizzleOrm = workspaceUsesDependency(workspaceRoot, 'drizzle-orm');
|
|
578
|
+
const usesDrizzleOrm = workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm');
|
|
480
579
|
const replacements = [
|
|
481
580
|
[
|
|
482
581
|
/^ {4}'@effect\/vitest>effect': .+$/mu,
|
|
@@ -536,12 +635,11 @@ function updateGeneratedPnpmWorkspacePolicy(workspaceRoot) {
|
|
|
536
635
|
const drizzleOrmPatch = usesDrizzleOrm ? ensureYamlMapEntry(source, 'patchedDependencies', `drizzle-orm@${DRIZZLE_ORM_VERSION}`, drizzleOrmDeclarationPatchPath) : removeYamlMapEntry(source, `drizzle-orm@${DRIZZLE_ORM_VERSION}`);
|
|
537
636
|
source = drizzleOrmPatch.source;
|
|
538
637
|
changed = drizzleOrmPatch.changed || changed;
|
|
539
|
-
if (changed)
|
|
638
|
+
if (changed) io.write(workspaceFile, source);
|
|
540
639
|
return changed;
|
|
541
640
|
}
|
|
542
|
-
function updateUltramodernConfig(
|
|
543
|
-
const configPath = node_path.join(workspaceRoot, '.modernjs/ultramodern.json');
|
|
544
|
-
const config = readJsonFile(configPath);
|
|
641
|
+
function updateUltramodernConfig(io, config, packageSource) {
|
|
642
|
+
const configPath = node_path.join(io.workspaceRoot, '.modernjs/ultramodern.json');
|
|
545
643
|
config.packageSource = {
|
|
546
644
|
strategy: packageSource.strategy,
|
|
547
645
|
modernPackageVersion: packageSource.modernPackageVersion,
|
|
@@ -556,73 +654,145 @@ function updateUltramodernConfig(workspaceRoot, packageSource) {
|
|
|
556
654
|
} : {}
|
|
557
655
|
};
|
|
558
656
|
for (const app of config.topology?.apps ?? [])if (app && 'object' == typeof app && !Array.isArray(app)) normalizeStrictEffectApiMetadata(app);
|
|
559
|
-
writeJsonFile(configPath, config);
|
|
657
|
+
writeJsonFile(io, configPath, config);
|
|
560
658
|
}
|
|
561
|
-
function writeJsonIfChanged(filePath, value) {
|
|
562
|
-
|
|
563
|
-
if (node_fs.existsSync(filePath) && node_fs.readFileSync(filePath, 'utf-8') === next) return false;
|
|
564
|
-
node_fs.mkdirSync(node_path.dirname(filePath), {
|
|
565
|
-
recursive: true
|
|
566
|
-
});
|
|
567
|
-
node_fs.writeFileSync(filePath, next, 'utf-8');
|
|
568
|
-
return true;
|
|
659
|
+
function writeJsonIfChanged(io, filePath, value) {
|
|
660
|
+
return io.write(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
569
661
|
}
|
|
570
|
-
function writeTextIfChanged(filePath, value) {
|
|
571
|
-
|
|
572
|
-
node_fs.mkdirSync(node_path.dirname(filePath), {
|
|
573
|
-
recursive: true
|
|
574
|
-
});
|
|
575
|
-
node_fs.writeFileSync(filePath, value, 'utf-8');
|
|
576
|
-
return true;
|
|
662
|
+
function writeTextIfChanged(io, filePath, value) {
|
|
663
|
+
return io.write(filePath, value);
|
|
577
664
|
}
|
|
578
|
-
function ensureGeneratedIgnoreRules(
|
|
579
|
-
const gitignorePath = node_path.join(workspaceRoot, '.gitignore');
|
|
665
|
+
function ensureGeneratedIgnoreRules(io) {
|
|
666
|
+
const gitignorePath = node_path.join(io.workspaceRoot, '.gitignore');
|
|
580
667
|
const existing = node_fs.existsSync(gitignorePath) ? node_fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
581
668
|
const lines = 0 === existing.trimEnd().length ? [] : existing.trimEnd().split(/\r?\n/u);
|
|
582
669
|
let changed = false;
|
|
583
670
|
for (const rule of [
|
|
584
671
|
'.mf/',
|
|
585
672
|
'**/.mf/',
|
|
586
|
-
'dist-cloudflare/'
|
|
673
|
+
'dist-cloudflare/',
|
|
674
|
+
'.output/',
|
|
675
|
+
'**/.output/',
|
|
676
|
+
'.modern-js/',
|
|
677
|
+
'**/.modern-js/'
|
|
587
678
|
])if (!lines.includes(rule)) {
|
|
588
679
|
lines.push(rule);
|
|
589
680
|
changed = true;
|
|
590
681
|
}
|
|
591
682
|
if (!changed) return false;
|
|
592
|
-
|
|
593
|
-
node_fs.writeFileSync(gitignorePath, next, 'utf-8');
|
|
594
|
-
return true;
|
|
683
|
+
return io.write(gitignorePath, `${lines.join('\n')}\n`);
|
|
595
684
|
}
|
|
596
|
-
function updateGeneratedTypeScriptSurfaces(
|
|
685
|
+
function updateGeneratedTypeScriptSurfaces(io, config) {
|
|
597
686
|
let changed = false;
|
|
598
687
|
const apps = workspaceAppsFromToolingConfig(config);
|
|
599
688
|
const remotes = apps.filter((app)=>'shell' !== app.kind);
|
|
600
|
-
changed = writeJsonIfChanged(node_path.join(workspaceRoot, 'tsconfig.base.json'), createTsConfigBase()) || changed;
|
|
601
|
-
changed = ensureGeneratedIgnoreRules(
|
|
689
|
+
changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, 'tsconfig.base.json'), createTsConfigBase()) || changed;
|
|
690
|
+
changed = ensureGeneratedIgnoreRules(io) || changed;
|
|
602
691
|
for (const sharedPackage of [
|
|
603
692
|
'packages/shared-contracts',
|
|
604
693
|
'packages/shared-design-tokens'
|
|
605
|
-
])changed = writeJsonIfChanged(node_path.join(workspaceRoot, sharedPackage, 'tsconfig.json'), createSharedPackageTsConfig(sharedPackage)) || changed;
|
|
694
|
+
])changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, sharedPackage, 'tsconfig.json'), createSharedPackageTsConfig(sharedPackage)) || changed;
|
|
606
695
|
for (const app of apps){
|
|
607
|
-
changed = writeJsonIfChanged(node_path.join(workspaceRoot, app.directory, 'tsconfig.json'), createAppTsConfig(app, remotes)) || changed;
|
|
608
|
-
changed = writeJsonIfChanged(node_path.join(workspaceRoot, app.directory, 'tsconfig.mf-types.json'), createAppMfTypesTsConfig(app)) || changed;
|
|
609
|
-
changed = writeTextIfChanged(node_path.join(workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), createAppEnvDts(app, remotes)) || changed;
|
|
696
|
+
changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'tsconfig.json'), createAppTsConfig(app, remotes)) || changed;
|
|
697
|
+
changed = writeJsonIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'tsconfig.mf-types.json'), createAppMfTypesTsConfig(app)) || changed;
|
|
698
|
+
changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'src/modern-app-env.d.ts'), createAppEnvDts(app, remotes)) || changed;
|
|
610
699
|
}
|
|
611
700
|
return changed;
|
|
612
701
|
}
|
|
613
|
-
function updateGeneratedModernConfigs(
|
|
702
|
+
function updateGeneratedModernConfigs(io, config) {
|
|
614
703
|
let changed = false;
|
|
615
704
|
const apps = workspaceAppsFromToolingConfig(config);
|
|
616
|
-
|
|
705
|
+
const remotes = apps.filter((app)=>'shell' !== app.kind);
|
|
706
|
+
for (const app of apps)changed = writeTextIfChanged(io, node_path.join(io.workspaceRoot, app.directory, 'modern.config.ts'), createAppModernConfig(config.workspace.packageScope, app, remotes)) || changed;
|
|
617
707
|
return changed;
|
|
618
708
|
}
|
|
619
|
-
function
|
|
620
|
-
const
|
|
709
|
+
function ensureGeneratedOxfmtIgnorePatterns(io) {
|
|
710
|
+
const configPath = node_path.join(io.workspaceRoot, 'oxfmt.config.ts');
|
|
711
|
+
if (!node_fs.existsSync(configPath)) return false;
|
|
712
|
+
const source = node_fs.readFileSync(configPath, 'utf-8');
|
|
713
|
+
const requiredPatterns = [
|
|
714
|
+
'.modernjs',
|
|
715
|
+
'.output',
|
|
716
|
+
'**/modern-tanstack/**',
|
|
717
|
+
'**/routeTree.gen.*'
|
|
718
|
+
];
|
|
719
|
+
const warnUnparseable = ()=>{
|
|
720
|
+
const message = `Could not update oxfmt.config.ts ignorePatterns automatically; add these entries manually: ${requiredPatterns.join(', ')}.`;
|
|
721
|
+
if (io.dryRun) io.log(message);
|
|
722
|
+
else process.stderr.write(`[ultramodern] ${message}\n`);
|
|
723
|
+
};
|
|
724
|
+
const anchor = source.indexOf('ignorePatterns:');
|
|
725
|
+
if (-1 === anchor) {
|
|
726
|
+
warnUnparseable();
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
const openBracket = source.indexOf('[', anchor);
|
|
730
|
+
if (-1 === openBracket) {
|
|
731
|
+
warnUnparseable();
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
let depth = 0;
|
|
735
|
+
let closeBracket = -1;
|
|
736
|
+
let stringQuote;
|
|
737
|
+
for(let index = openBracket; index < source.length; index += 1){
|
|
738
|
+
const char = source[index];
|
|
739
|
+
if (stringQuote) {
|
|
740
|
+
if ('\\' === char) index += 1;
|
|
741
|
+
else if (char === stringQuote) stringQuote = void 0;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if ("'" === char || '"' === char || '`' === char) stringQuote = char;
|
|
745
|
+
else if ('[' === char) depth += 1;
|
|
746
|
+
else if (']' === char) {
|
|
747
|
+
depth -= 1;
|
|
748
|
+
if (0 === depth) {
|
|
749
|
+
closeBracket = index;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (-1 === closeBracket) {
|
|
755
|
+
warnUnparseable();
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
const body = source.slice(openBracket + 1, closeBracket);
|
|
759
|
+
if (body.includes('...')) {
|
|
760
|
+
warnUnparseable();
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
const literalPattern = /(['"`])((?:\\.|(?!\1).)*)\1/g;
|
|
764
|
+
const existing = new Set();
|
|
765
|
+
for (const match of body.matchAll(literalPattern))existing.add(match[2]);
|
|
766
|
+
const missing = requiredPatterns.filter((pattern)=>!existing.has(pattern));
|
|
767
|
+
if (0 === missing.length) return false;
|
|
768
|
+
const bodyLines = body.split('\n');
|
|
769
|
+
let indent = ' ';
|
|
770
|
+
let quote = "'";
|
|
771
|
+
for(let index = bodyLines.length - 1; index >= 0; index -= 1){
|
|
772
|
+
const literal = bodyLines[index].match(/^(\s*)(['"`])/u);
|
|
773
|
+
if (literal) {
|
|
774
|
+
indent = literal[1];
|
|
775
|
+
quote = literal[2];
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const head = source.slice(0, closeBracket);
|
|
780
|
+
const rest = source.slice(closeBracket);
|
|
781
|
+
const tailMatch = head.match(/(\r?\n[ \t]*)$/u);
|
|
782
|
+
const tail = tailMatch ? tailMatch[1] : '\n';
|
|
783
|
+
let bodyContent = tailMatch ? head.slice(0, head.length - tail.length) : head;
|
|
784
|
+
if (!/[[,]\s*$/u.test(bodyContent)) bodyContent = `${bodyContent},`;
|
|
785
|
+
const insertionLines = missing.map((pattern)=>`${indent}${quote}${pattern}${quote},`).join('\n');
|
|
786
|
+
const nextSource = `${bodyContent}\n${insertionLines}${tail}${rest}`;
|
|
787
|
+
return io.write(configPath, nextSource);
|
|
788
|
+
}
|
|
789
|
+
function updateReferenceTopology(io) {
|
|
790
|
+
const topologyPath = node_path.join(io.workspaceRoot, 'topology/reference-topology.json');
|
|
621
791
|
if (!node_fs.existsSync(topologyPath)) return false;
|
|
622
792
|
const topology = readJsonFile(topologyPath);
|
|
623
793
|
let changed = false;
|
|
624
794
|
for (const vertical of topology.verticals ?? [])if (vertical && 'object' == typeof vertical && !Array.isArray(vertical)) changed = normalizeStrictEffectApiMetadata(vertical) || changed;
|
|
625
|
-
if (changed) writeJsonFile(topologyPath, topology);
|
|
795
|
+
if (changed) writeJsonFile(io, topologyPath, topology);
|
|
626
796
|
return changed;
|
|
627
797
|
}
|
|
628
798
|
function createMigrationPackageSource(args, current) {
|
|
@@ -675,25 +845,56 @@ function runPnpmLockfileRefresh(context) {
|
|
|
675
845
|
function runMigrateStrictEffect(args, context) {
|
|
676
846
|
if (args.includes('--help') || args.includes('-h')) {
|
|
677
847
|
process.stdout.write(`Usage:
|
|
678
|
-
modern-js-create ultramodern migrate-strict-effect --version <version> [--skip-install]
|
|
848
|
+
modern-js-create ultramodern migrate-strict-effect --version <version> [--dry-run] [--skip-install]
|
|
679
849
|
|
|
680
850
|
Updates generated UltraModern package-source metadata, Modern package aliases,
|
|
681
851
|
framework-owned toolchain pins, direct Effect API topology metadata, strict
|
|
682
852
|
Effect pnpm overrides/trust policy, framework-owned TypeScript config
|
|
683
853
|
surfaces, and the pnpm lockfile. Source code still has to pass pnpm api:check
|
|
684
854
|
and pnpm contract:check.
|
|
855
|
+
|
|
856
|
+
When the compact config is absent but legacy UltraModern 3.2 metadata is
|
|
857
|
+
present, the compact config is synthesized from it first. Shell-only
|
|
858
|
+
workspaces skip the backend-federation and Zerops runtime stages. Pass
|
|
859
|
+
--dry-run to print the planned filesystem changes without writing anything
|
|
860
|
+
(implies --skip-install).
|
|
685
861
|
`);
|
|
686
862
|
return 0;
|
|
687
863
|
}
|
|
688
|
-
const
|
|
864
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
865
|
+
const skipInstall = dryRun || hasFlag(args, '--skip-install');
|
|
866
|
+
const io = createMigrationIo(context.workspaceRoot, dryRun);
|
|
867
|
+
const compactPath = node_path.join(io.workspaceRoot, '.modernjs/ultramodern.json');
|
|
868
|
+
let raw;
|
|
869
|
+
if (node_fs.existsSync(compactPath)) raw = readJsonFile(compactPath);
|
|
870
|
+
else {
|
|
871
|
+
const synthesized = synthesizeCompactUltramodernConfig(io.workspaceRoot);
|
|
872
|
+
if (!synthesized) throw new Error("Missing .modernjs/ultramodern.json and no legacy UltraModern metadata (.modernjs/ultramodern-generated-contract.json) was found to synthesize it from.");
|
|
873
|
+
raw = synthesized.compact;
|
|
874
|
+
io.write(compactPath, `${JSON.stringify(raw, null, 2)}\n`);
|
|
875
|
+
io.log(`Synthesized .modernjs/ultramodern.json from legacy metadata: ${synthesized.sources.join(', ')}.`);
|
|
876
|
+
if (synthesized.missing.length > 0) io.log(`Legacy metadata not found (using defaults): ${synthesized.missing.join(', ')}.`);
|
|
877
|
+
}
|
|
878
|
+
const current = normalizeCompactUltramodernConfig(io.workspaceRoot, raw);
|
|
689
879
|
const packageSource = createMigrationPackageSource(args, current);
|
|
690
|
-
updateUltramodernConfig(
|
|
691
|
-
updateReferenceTopology(
|
|
692
|
-
const migrated =
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
880
|
+
updateUltramodernConfig(io, raw, packageSource);
|
|
881
|
+
updateReferenceTopology(io);
|
|
882
|
+
const migrated = normalizeCompactUltramodernConfig(io.workspaceRoot, raw);
|
|
883
|
+
const migratedApps = workspaceAppsFromToolingConfig(migrated);
|
|
884
|
+
const shellOnly = !migrated.topology.apps.some((app)=>app.api);
|
|
885
|
+
if (shellOnly) io.log('Shell-only workspace: skipping backend-federation and Zerops runtime stages.');
|
|
886
|
+
else {
|
|
887
|
+
removeStaleBackendFederationArtifacts(io, migrated);
|
|
888
|
+
updateGeneratedZeropsArtifacts(io, migrated);
|
|
889
|
+
updateGeneratedBackendFederationContractFiles(io, migrated);
|
|
890
|
+
}
|
|
891
|
+
if (dryRun) io.log("would refresh generated tool wrapper scripts under scripts/");
|
|
892
|
+
else writeGeneratedToolWrapperScripts(io.workspaceRoot);
|
|
893
|
+
updateGeneratedBuildIdentityModules(io, migrated);
|
|
894
|
+
updateGeneratedTypeScriptSurfaces(io, migrated);
|
|
895
|
+
updateGeneratedModernConfigs(io, migrated);
|
|
896
|
+
for (const relativePackageFile of listWorkspacePackageFiles(io.workspaceRoot)){
|
|
897
|
+
const packageFile = node_path.join(io.workspaceRoot, relativePackageFile);
|
|
697
898
|
const packageJson = readJsonFile(packageFile);
|
|
698
899
|
if ('package.json' === relativePackageFile) {
|
|
699
900
|
packageJson.modernjs ??= {};
|
|
@@ -702,21 +903,28 @@ and pnpm contract:check.
|
|
|
702
903
|
config: './.modernjs/ultramodern.json'
|
|
703
904
|
};
|
|
704
905
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
906
|
+
updateModernDependencies(packageJson, packageSource);
|
|
907
|
+
updateGeneratedToolingDependencies(packageJson);
|
|
908
|
+
updateGeneratedPackageScripts(packageJson, {
|
|
909
|
+
relativePackageFile,
|
|
910
|
+
apps: migratedApps
|
|
911
|
+
});
|
|
912
|
+
writeJsonFile(io, packageFile, packageJson);
|
|
913
|
+
}
|
|
914
|
+
updateGeneratedPnpmWorkspacePolicy(io);
|
|
915
|
+
ensureGeneratedDeclarationPatches(io, {
|
|
916
|
+
includeDrizzleOrmPatch: workspaceUsesDependency(io.workspaceRoot, 'drizzle-orm')
|
|
715
917
|
});
|
|
716
|
-
|
|
918
|
+
ensureGeneratedOxfmtIgnorePatterns(io);
|
|
919
|
+
if (!skipInstall) {
|
|
717
920
|
const status = runPnpmLockfileRefresh(context);
|
|
718
921
|
if (0 !== status) return status;
|
|
719
922
|
}
|
|
923
|
+
if (dryRun) {
|
|
924
|
+
for (const line of io.plan)process.stdout.write(`${line}\n`);
|
|
925
|
+
process.stdout.write(`[dry-run] migrate-strict-effect would migrate UltraModern strict Effect metadata to ${packageSource.modernPackageVersion}.\n`);
|
|
926
|
+
return 0;
|
|
927
|
+
}
|
|
720
928
|
process.stdout.write(`UltraModern strict Effect metadata migrated to ${packageSource.modernPackageVersion}. Run pnpm api:check && pnpm contract:check next.\n`);
|
|
721
929
|
return 0;
|
|
722
930
|
}
|
|
@@ -729,6 +937,140 @@ function runSkills(args, context) {
|
|
|
729
937
|
], context);
|
|
730
938
|
throw new Error('Usage: modern-js-create ultramodern skills <install|check>');
|
|
731
939
|
}
|
|
940
|
+
const resolveCloudflareOutputVerifyTargets = (args, context)=>{
|
|
941
|
+
const outputDirectory = readOption(args, '--output');
|
|
942
|
+
const appId = readOption(args, '--app');
|
|
943
|
+
if (outputDirectory && appId) throw new Error('Use either --app or --output, not both.');
|
|
944
|
+
const targets = outputDirectory ? [
|
|
945
|
+
{
|
|
946
|
+
label: outputDirectory,
|
|
947
|
+
outputDirectory: node_path.resolve(context.invocationCwd, outputDirectory)
|
|
948
|
+
}
|
|
949
|
+
] : workspaceAppsFromToolingConfig(readUltramodernConfig(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
|
|
950
|
+
label: app.id,
|
|
951
|
+
outputDirectory: node_path.join(context.workspaceRoot, app.directory, '.output')
|
|
952
|
+
}));
|
|
953
|
+
if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId}.`);
|
|
954
|
+
return targets;
|
|
955
|
+
};
|
|
956
|
+
const renderCloudflareOutputVerifyModule = ({ workspaceRoot, targets, scanRoots, importWorker })=>`
|
|
957
|
+
import { createRequire } from 'node:module';
|
|
958
|
+
import path from 'node:path';
|
|
959
|
+
|
|
960
|
+
const workspaceRoot = ${JSON.stringify(workspaceRoot)};
|
|
961
|
+
const targets = ${JSON.stringify(targets, null, 2)};
|
|
962
|
+
const scanRoots = ${JSON.stringify(scanRoots)};
|
|
963
|
+
const verifierRequire = createRequire(path.join(workspaceRoot, 'package.json'));
|
|
964
|
+
const {
|
|
965
|
+
verifyCloudflareOutput,
|
|
966
|
+
verifyCloudflareOutputMutationPolicy,
|
|
967
|
+
} = verifierRequire('@modern-js/app-tools/cloudflare-output-verifier');
|
|
968
|
+
|
|
969
|
+
let failed = false;
|
|
970
|
+
for (const target of targets) {
|
|
971
|
+
const result = await verifyCloudflareOutput({
|
|
972
|
+
outputDirectory: target.outputDirectory,
|
|
973
|
+
importWorker: ${JSON.stringify(importWorker)},
|
|
974
|
+
});
|
|
975
|
+
if (result.ok) {
|
|
976
|
+
console.log(\`[ultramodern] Cloudflare output verified: \${target.label}\`);
|
|
977
|
+
} else {
|
|
978
|
+
failed = true;
|
|
979
|
+
console.error(\`[ultramodern] Cloudflare output failed: \${target.label}\`);
|
|
980
|
+
for (const issue of result.issues) {
|
|
981
|
+
console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (scanRoots.length > 0) {
|
|
987
|
+
const policyResult = await verifyCloudflareOutputMutationPolicy({ scanRoots });
|
|
988
|
+
if (!policyResult.ok) {
|
|
989
|
+
failed = true;
|
|
990
|
+
console.error('[ultramodern] generated-output mutation policy failed');
|
|
991
|
+
for (const issue of policyResult.issues) {
|
|
992
|
+
console.error(\`- \${issue.code}: \${issue.message}\${issue.path ? \` (\${issue.path})\` : ''}\`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
process.exit(failed ? 1 : 0);
|
|
998
|
+
`;
|
|
999
|
+
function runCloudflareOutputVerify(args, context) {
|
|
1000
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
1001
|
+
process.stdout.write(`Usage:
|
|
1002
|
+
modern-js-create ultramodern cloudflare-output-verify [--app <id> | --output <dir>] [--no-import-worker] [--no-source-scan]
|
|
1003
|
+
|
|
1004
|
+
Verifies generated Cloudflare output against the UltraModern worker contract.
|
|
1005
|
+
Without --app or --output, every generated workspace app is verified.
|
|
1006
|
+
`);
|
|
1007
|
+
return 0;
|
|
1008
|
+
}
|
|
1009
|
+
const source = renderCloudflareOutputVerifyModule({
|
|
1010
|
+
workspaceRoot: context.workspaceRoot,
|
|
1011
|
+
targets: resolveCloudflareOutputVerifyTargets(args, context),
|
|
1012
|
+
scanRoots: hasFlag(args, '--no-source-scan') ? [] : [
|
|
1013
|
+
context.workspaceRoot
|
|
1014
|
+
],
|
|
1015
|
+
importWorker: !hasFlag(args, '--no-import-worker')
|
|
1016
|
+
});
|
|
1017
|
+
return runRenderedModule(source, context);
|
|
1018
|
+
}
|
|
1019
|
+
const resolveRoutesGenerateTargets = (args, context)=>{
|
|
1020
|
+
const appId = readOption(args, '--app');
|
|
1021
|
+
const targets = workspaceAppsFromToolingConfig(readUltramodernConfig(context.workspaceRoot)).filter((app)=>!appId || app.id === appId).map((app)=>({
|
|
1022
|
+
label: app.id,
|
|
1023
|
+
appDirectory: node_path.join(context.workspaceRoot, app.directory)
|
|
1024
|
+
}));
|
|
1025
|
+
if (0 === targets.length) throw new Error(`No generated UltraModern app matched ${appId ?? '<any>'}.`);
|
|
1026
|
+
return targets;
|
|
1027
|
+
};
|
|
1028
|
+
const renderRoutesGenerateModule = ({ workspaceRoot, targets })=>`
|
|
1029
|
+
import { createRequire } from 'node:module';
|
|
1030
|
+
import path from 'node:path';
|
|
1031
|
+
import { pathToFileURL } from 'node:url';
|
|
1032
|
+
|
|
1033
|
+
const workspaceRoot = ${JSON.stringify(workspaceRoot)};
|
|
1034
|
+
const targets = ${JSON.stringify(targets, null, 2)};
|
|
1035
|
+
const appRequire = createRequire(path.join(workspaceRoot, 'package.json'));
|
|
1036
|
+
const pluginUrl = pathToFileURL(
|
|
1037
|
+
appRequire.resolve('@modern-js/plugin-tanstack'),
|
|
1038
|
+
).href;
|
|
1039
|
+
const { generateTanstackRouteArtifacts } = await import(pluginUrl);
|
|
1040
|
+
|
|
1041
|
+
let failed = false;
|
|
1042
|
+
// Sequential — the app-tools cli singleton is not re-entrant.
|
|
1043
|
+
for (const target of targets) {
|
|
1044
|
+
try {
|
|
1045
|
+
await generateTanstackRouteArtifacts({ appDirectory: target.appDirectory });
|
|
1046
|
+
console.log(\`[ultramodern] TanStack route artifacts generated: \${target.label}\`);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
failed = true;
|
|
1049
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1050
|
+
console.error(\`[ultramodern] TanStack route generation failed: \${target.label}\`);
|
|
1051
|
+
console.error(\`- \${message}\`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
process.exit(failed ? 1 : 0);
|
|
1056
|
+
`;
|
|
1057
|
+
function runRoutesGenerate(args, context) {
|
|
1058
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
1059
|
+
process.stdout.write(`Usage:
|
|
1060
|
+
modern-js-create ultramodern routes-generate [--app <id>]
|
|
1061
|
+
|
|
1062
|
+
Regenerates TanStack route artifacts (router.gen.ts, register.gen.d.ts) for
|
|
1063
|
+
generated UltraModern apps without running dev or build. Without --app, every
|
|
1064
|
+
generated workspace app is regenerated.
|
|
1065
|
+
`);
|
|
1066
|
+
return 0;
|
|
1067
|
+
}
|
|
1068
|
+
const source = renderRoutesGenerateModule({
|
|
1069
|
+
workspaceRoot: context.workspaceRoot,
|
|
1070
|
+
targets: resolveRoutesGenerateTargets(args, context)
|
|
1071
|
+
});
|
|
1072
|
+
return runRenderedModule(source, context);
|
|
1073
|
+
}
|
|
732
1074
|
async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd()) {
|
|
733
1075
|
try {
|
|
734
1076
|
const [command, ...rest] = args;
|
|
@@ -742,25 +1084,25 @@ async function runUltramodernToolingCli(args, workspaceRoot = process.env.ULTRAM
|
|
|
742
1084
|
case '-h':
|
|
743
1085
|
printHelp();
|
|
744
1086
|
return 0;
|
|
745
|
-
case
|
|
1087
|
+
case GENERATED_TOOLING_COMMANDS.validate.command:
|
|
746
1088
|
return runValidate(context);
|
|
747
|
-
case
|
|
748
|
-
return spawnNodeScript("templates/workspace-scripts/ultramodern-typecheck.mjs", rest, context, {
|
|
749
|
-
cwd: context.invocationCwd
|
|
750
|
-
});
|
|
751
|
-
case 'mf-types':
|
|
1089
|
+
case GENERATED_TOOLING_COMMANDS.mfTypes.command:
|
|
752
1090
|
return runMfTypes(rest, context);
|
|
753
|
-
case
|
|
1091
|
+
case GENERATED_TOOLING_COMMANDS.migrateStrictEffect.command:
|
|
754
1092
|
return runMigrateStrictEffect(rest, context);
|
|
755
|
-
case
|
|
756
|
-
return
|
|
757
|
-
case
|
|
758
|
-
return
|
|
759
|
-
case '
|
|
760
|
-
return
|
|
1093
|
+
case GENERATED_TOOLING_COMMANDS.cloudflareOutputVerify.command:
|
|
1094
|
+
return runCloudflareOutputVerify(rest, context);
|
|
1095
|
+
case GENERATED_TOOLING_COMMANDS.routesGenerate.command:
|
|
1096
|
+
return runRoutesGenerate(rest, context);
|
|
1097
|
+
case 'sync-delivery-unit':
|
|
1098
|
+
return runSyncDeliveryUnit(rest, context);
|
|
761
1099
|
case 'skills':
|
|
762
1100
|
return runSkills(rest, context);
|
|
763
1101
|
default:
|
|
1102
|
+
{
|
|
1103
|
+
const templateBackedStatus = runTemplateBackedToolingCommand(command ?? '', rest, context);
|
|
1104
|
+
if (void 0 !== templateBackedStatus) return templateBackedStatus;
|
|
1105
|
+
}
|
|
764
1106
|
throw new Error(`Unknown UltraModern command: ${command}`);
|
|
765
1107
|
}
|
|
766
1108
|
} catch (error) {
|