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