@bleedingdev/modern-js-ultramodern-create 3.9.0-ultramodern.2 → 3.9.0-ultramodern.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cjs/ultramodern-tooling/commands/migrate-strict-effect/generated-artifacts-typescript.cjs +1 -0
  2. package/dist/cjs/ultramodern-tooling/commands/migrate-strict-effect/package-cohort.cjs +2 -0
  3. package/dist/cjs/ultramodern-tooling/commands/migrate-strict-effect/shared-api-infrastructure.cjs +99 -0
  4. package/dist/cjs/ultramodern-tooling/commands/migrate-strict-effect/toolchain-pins.cjs +1 -0
  5. package/dist/cjs/ultramodern-tooling/commands/migrate-strict-effect.cjs +2 -0
  6. package/dist/cjs/ultramodern-workspace/api/client.cjs +2 -2
  7. package/dist/cjs/ultramodern-workspace/api/service.cjs +45 -16
  8. package/dist/cjs/ultramodern-workspace/api/shared.cjs +26 -16
  9. package/dist/cjs/ultramodern-workspace/mf-validation/inspect.cjs +6 -2
  10. package/dist/cjs/ultramodern-workspace/module-federation/reexport-module.cjs +0 -5
  11. package/dist/cjs/ultramodern-workspace/package-json.cjs +12 -1
  12. package/dist/cjs/ultramodern-workspace/policy.cjs +1 -0
  13. package/dist/cjs/ultramodern-workspace/tsconfigs.cjs +3 -0
  14. package/dist/cjs/ultramodern-workspace/workspace-script-plan.cjs +1 -1
  15. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +5 -0
  16. package/dist/cjs/ultramodern-workspace/write-app.cjs +9 -3
  17. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +5 -3
  18. package/dist/esm/ultramodern-tooling/commands/migrate-strict-effect/generated-artifacts-typescript.js +1 -0
  19. package/dist/esm/ultramodern-tooling/commands/migrate-strict-effect/package-cohort.js +2 -0
  20. package/dist/esm/ultramodern-tooling/commands/migrate-strict-effect/shared-api-infrastructure.js +50 -0
  21. package/dist/esm/ultramodern-tooling/commands/migrate-strict-effect/toolchain-pins.js +1 -0
  22. package/dist/esm/ultramodern-tooling/commands/migrate-strict-effect.js +2 -0
  23. package/dist/esm/ultramodern-workspace/api/client.js +2 -2
  24. package/dist/esm/ultramodern-workspace/api/service.js +45 -16
  25. package/dist/esm/ultramodern-workspace/api/shared.js +27 -17
  26. package/dist/esm/ultramodern-workspace/mf-validation/inspect.js +6 -2
  27. package/dist/esm/ultramodern-workspace/module-federation/reexport-module.js +0 -5
  28. package/dist/esm/ultramodern-workspace/package-json.js +12 -1
  29. package/dist/esm/ultramodern-workspace/policy.js +1 -0
  30. package/dist/esm/ultramodern-workspace/tsconfigs.js +3 -0
  31. package/dist/esm/ultramodern-workspace/workspace-script-plan.js +1 -1
  32. package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -0
  33. package/dist/esm/ultramodern-workspace/write-app.js +9 -3
  34. package/dist/esm/ultramodern-workspace/write-workspace.js +6 -4
  35. package/dist/esm-node/ultramodern-tooling/commands/migrate-strict-effect/generated-artifacts-typescript.js +1 -0
  36. package/dist/esm-node/ultramodern-tooling/commands/migrate-strict-effect/package-cohort.js +2 -0
  37. package/dist/esm-node/ultramodern-tooling/commands/migrate-strict-effect/shared-api-infrastructure.js +51 -0
  38. package/dist/esm-node/ultramodern-tooling/commands/migrate-strict-effect/toolchain-pins.js +1 -0
  39. package/dist/esm-node/ultramodern-tooling/commands/migrate-strict-effect.js +2 -0
  40. package/dist/esm-node/ultramodern-workspace/api/client.js +2 -2
  41. package/dist/esm-node/ultramodern-workspace/api/service.js +45 -16
  42. package/dist/esm-node/ultramodern-workspace/api/shared.js +27 -17
  43. package/dist/esm-node/ultramodern-workspace/mf-validation/inspect.js +6 -2
  44. package/dist/esm-node/ultramodern-workspace/module-federation/reexport-module.js +0 -5
  45. package/dist/esm-node/ultramodern-workspace/package-json.js +12 -1
  46. package/dist/esm-node/ultramodern-workspace/policy.js +1 -0
  47. package/dist/esm-node/ultramodern-workspace/tsconfigs.js +3 -0
  48. package/dist/esm-node/ultramodern-workspace/workspace-script-plan.js +1 -1
  49. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -0
  50. package/dist/esm-node/ultramodern-workspace/write-app.js +9 -3
  51. package/dist/esm-node/ultramodern-workspace/write-workspace.js +6 -4
  52. package/dist/types/ultramodern-tooling/commands/migrate-strict-effect/shared-api-infrastructure.d.ts +4 -0
  53. package/dist/types/ultramodern-workspace/api/client.d.ts +3 -1
  54. package/dist/types/ultramodern-workspace/api/service.d.ts +3 -1
  55. package/dist/types/ultramodern-workspace/api/shared.d.ts +2 -0
  56. package/dist/types/ultramodern-workspace/package-json.d.ts +1 -1
  57. package/dist/types/ultramodern-workspace/policy.d.ts +2 -0
  58. package/package.json +4 -4
  59. package/template-workspace/.modernjs/release-cohort.json +40 -40
  60. package/templates/packages/effect-bff-runtime.ts +50 -0
  61. package/templates/packages/microvertical-api-baseline.ts +102 -0
  62. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +49 -28
  63. package/templates/workspace-scripts/microvertical-api-baseline-boundary.mts +944 -0
  64. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +25 -7
  65. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +1 -1
@@ -0,0 +1,50 @@
1
+ import node_fs from "node:fs";
2
+ import node_path from "node:path";
3
+ import { readFileTemplate } from "../../../ultramodern-workspace/fs-io.js";
4
+ import { createSharedPackage, createSharedPackageTsConfig } from "../../../ultramodern-workspace/package-json.js";
5
+ import { readJsonFile, writeJsonFile } from "./io.js";
6
+ const directory = 'packages/shared-contracts';
7
+ function record(value, label) {
8
+ if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error(`${label} must be an object; resolve it before migration.`);
9
+ return value;
10
+ }
11
+ function ensureSharedApiInfrastructure(io, scope, packageSource) {
12
+ for (const relativePath of [
13
+ 'packages',
14
+ directory,
15
+ `${directory}/package.json`,
16
+ `${directory}/src`,
17
+ `${directory}/src/index.ts`
18
+ ])if (node_fs.lstatSync(node_path.join(io.workspaceRoot, relativePath), {
19
+ throwIfNoEntry: false
20
+ })?.isSymbolicLink()) throw new Error(`Shared API migration cannot write through a symbolic link: ${relativePath}`);
21
+ const manifestPath = node_path.join(io.workspaceRoot, directory, 'package.json');
22
+ const defaults = createSharedPackage(scope, 'shared-contracts', 'Shared workspace contracts', packageSource);
23
+ const manifest = node_fs.existsSync(manifestPath) ? record(readJsonFile(manifestPath), `${directory}/package.json`) : defaults;
24
+ const exports = void 0 === manifest.exports ? {} : 'string' == typeof manifest.exports ? {
25
+ '.': manifest.exports
26
+ } : record(manifest.exports, `${directory}/package.json exports`);
27
+ if (Object.keys(exports).some((key)=>!key.startsWith('.')) || void 0 !== exports['.'] && './src/index.ts' !== exports['.']) throw new Error(`${directory}/package.json exports must expose ./src/index.ts at "." before adding shared API infrastructure; consumer exports were not overwritten.`);
28
+ for (const [subpath, target] of Object.entries(record(defaults.exports, 'Shared contract exports')))if (void 0 !== exports[subpath] && exports[subpath] !== target) throw new Error(`${directory}/package.json ${subpath} must expose ${String(target)}; consumer exports were not overwritten.`);
29
+ const dependencies = record(manifest.dependencies ?? {}, `${directory}/package.json dependencies`);
30
+ manifest.exports = {
31
+ ...record(defaults.exports, 'Shared contract exports'),
32
+ ...exports
33
+ };
34
+ manifest.dependencies = {
35
+ ...record(defaults.dependencies, 'Shared contract dependencies'),
36
+ ...dependencies
37
+ };
38
+ const writeMissing = (relativePath, content)=>{
39
+ const filePath = node_path.join(io.workspaceRoot, directory, relativePath);
40
+ if (void 0 === node_fs.lstatSync(filePath, {
41
+ throwIfNoEntry: false
42
+ })) io.writeGenerated(filePath, content);
43
+ };
44
+ writeMissing('src/effect-bff-runtime.ts', readFileTemplate('packages/effect-bff-runtime.ts'));
45
+ writeMissing('src/microvertical-api-baseline.ts', readFileTemplate('packages/microvertical-api-baseline.ts'));
46
+ writeMissing('tsconfig.json', `${JSON.stringify(createSharedPackageTsConfig(directory), null, 2)}\n`);
47
+ writeMissing('src/index.ts', readFileTemplate('packages/shared-contracts-index.ts'));
48
+ writeJsonFile(io, manifestPath, manifest);
49
+ }
50
+ export { ensureSharedApiInfrastructure };
@@ -35,6 +35,7 @@ function updateRootPackageToolchain(packageJson) {
35
35
  const devDependencies = ensureRecord(packageJson, 'devDependencies', 'package.json devDependencies');
36
36
  for (const packageName of retiredRootToolingDependencies)delete devDependencies[packageName];
37
37
  devDependencies["@typescript/native"] = ULTRAMODERN_PACKAGE_PINS.rootDevDependencies["@typescript/native"];
38
+ devDependencies['@types/node'] = ULTRAMODERN_PACKAGE_PINS.rootDevDependencies["@types/node"];
38
39
  devDependencies.miniflare = ULTRAMODERN_PACKAGE_PINS.rootDevDependencies.miniflare;
39
40
  }
40
41
  function updateMiseTools(content) {
@@ -18,6 +18,7 @@ import { ensureBffEffectDependencies, updateGeneratedPackageScripts, updateGener
18
18
  import { createMigrationPackageSource } from "./migrate-strict-effect/package-source.js";
19
19
  import { updateGeneratedPnpmWorkspacePolicy, validateGeneratedPnpmLockReleaseAgePolicy } from "./migrate-strict-effect/pnpm-policy.js";
20
20
  import { ensureGeneratedModuleFederationBridgeRouterOptOut, preflightModuleFederationBridgeRouter, removeRetiredReactRouterDependency } from "./migrate-strict-effect/react-router-retirement.js";
21
+ import { ensureSharedApiInfrastructure } from "./migrate-strict-effect/shared-api-infrastructure.js";
21
22
  import { updateGeneratedToolchainFiles, updateRootPackageToolchain } from "./migrate-strict-effect/toolchain-pins.js";
22
23
  import { hasFlag } from "./options.js";
23
24
  const retiredMetadataPaths = [
@@ -282,6 +283,7 @@ function migrateStrictEffect(args, context, io, dryRun, skipInstall) {
282
283
  const verticalApps = migrated.topology.apps.filter((app)=>'shell' !== app.kind);
283
284
  const hasBackendSurface = verticalApps.some((app)=>app.api);
284
285
  const shellOnly = 0 === verticalApps.length;
286
+ if (hasBackendSurface) ensureSharedApiInfrastructure(io, migrated.workspace.packageScope, packageSource);
285
287
  if (shellOnly) {
286
288
  io.log('Shell-only workspace: skipping backend-federation and Zerops runtime stages.');
287
289
  for (const relativePath of [
@@ -2,7 +2,7 @@ import { resolveApiProtocol, resolveApiStem, verticalApiApps } from "../descript
2
2
  import { packageName, toPascalCase } from "../naming.js";
3
3
  import { createCheckoutCartClientExports, serviceHasCheckoutCartState } from "./checkout-cart.js";
4
4
  import { verticalApiErrorStem, verticalApiExport, verticalApiGroupName, verticalApiNotFoundErrorExport } from "./names.js";
5
- function createApiClient(service, contractImportPath) {
5
+ function createApiClient(service, contractImportPath, options) {
6
6
  const apiExport = verticalApiExport(service);
7
7
  const contractExport = verticalApiGroupName(service);
8
8
  const stem = resolveApiStem(service);
@@ -118,7 +118,7 @@ export const ${readinessName} = (
118
118
  operationContext:
119
119
  options.operationContext ?? ${groupName}OperationContexts.readiness,
120
120
  }).pipe(
121
- Effect.flatMap(client => client.${groupName}.readiness({})),
121
+ Effect.flatMap(client => client.${options ? 'foundation' : groupName}.readiness({})),
122
122
  );
123
123
 
124
124
  export const ${getName} = (
@@ -4,23 +4,22 @@ import { packageName } from "../naming.js";
4
4
  import { createCheckoutCartServerHandlers, createCheckoutCartServerState } from "./checkout-cart.js";
5
5
  import { verticalApiExport, verticalApiGroupName, verticalApiNotFoundErrorExport } from "./names.js";
6
6
  import { createRpcApiServiceEntry, rpcPath, verticalRpcContractExport, verticalRpcGroupExport } from "./rpc.js";
7
- function createApiServiceEntry(service, contractImportPath) {
7
+ function createApiServiceEntry(service, contractImportPath, options) {
8
8
  if ((service.api?.protocol ?? 'rest') === 'rpc') return createRpcApiServiceEntry(service);
9
9
  const apiExport = verticalApiExport(service);
10
10
  const groupName = verticalApiGroupName(service);
11
11
  const notFoundErrorExport = verticalApiNotFoundErrorExport(service);
12
12
  const stem = resolveApiStem(service);
13
- return `import {
14
- defineEffectBff,
13
+ const assemblyImport = options ? `import { assembleEffectBffRuntime } from '${packageName(options.scope, 'shared-contracts')}/server/effect-bff-runtime';\n` : '';
14
+ return `${assemblyImport}import {
15
+ ${options ? '' : 'defineEffectBff,'}
15
16
  Effect,
16
17
  HttpApiBuilder,
17
18
  Layer,
18
19
  } from '@modern-js/plugin-bff/effect-edge';
19
- import type {
20
- EffectBffDefinition,
21
- EffectBffRuntime,
20
+ ${options ? '' : `import type {
22
21
  EffectRuntimeLayer,
23
- } from '@modern-js/plugin-bff/effect-edge';
22
+ } from '@modern-js/plugin-bff/effect-edge';`}
24
23
  import { ultramodernApiMarker } from '../shared/ultramodern-build.ts';
25
24
  import {
26
25
  ${apiExport},
@@ -50,6 +49,31 @@ const operationAttributes = (operationContext: OperationContext) => ({
50
49
  : {}),
51
50
  });
52
51
 
52
+ ${options ? `const ${groupName}ReadinessLayer = HttpApiBuilder.group(
53
+ ${apiExport},
54
+ 'foundation',
55
+ handlers => handlers
56
+ .handle('readiness', () =>
57
+ Effect.succeed({
58
+ checks: {
59
+ api: 'ready' as const,
60
+ moduleFederation: 'ready' as const,
61
+ ssr: 'ready' as const,
62
+ translations: 'ready' as const,
63
+ },
64
+ marker: ultramodernApiMarker,
65
+ status: 'ready' as const,
66
+ versionSkew: 'none' as const,
67
+ }).pipe(
68
+ Effect.withSpan('ultramodern.api.${groupName}.readiness', {
69
+ attributes: operationAttributes(${groupName}OperationContexts.readiness),
70
+ kind: 'server',
71
+ }),
72
+ ),
73
+ )
74
+ ,
75
+ );` : ''}
76
+
53
77
  const ${groupName}Layer = HttpApiBuilder.group(
54
78
  ${apiExport},
55
79
  '${groupName}',
@@ -68,7 +92,7 @@ const ${groupName}Layer = HttpApiBuilder.group(
68
92
  }),
69
93
  ),
70
94
  )
71
- .handle('readiness', () =>
95
+ ${options ? '' : ` .handle('readiness', () =>
72
96
  Effect.succeed({
73
97
  checks: {
74
98
  api: 'ready' as const,
@@ -86,6 +110,7 @@ const ${groupName}Layer = HttpApiBuilder.group(
86
110
  }),
87
111
  ),
88
112
  )
113
+ `}
89
114
  .handle('get', ({ params }) => {
90
115
  const matchedItem = ${groupName}Items.find(
91
116
  candidate => candidate.id === params.id,
@@ -125,14 +150,18 @@ const ${groupName}Layer = HttpApiBuilder.group(
125
150
  )${createCheckoutCartServerHandlers(service)},
126
151
  );
127
152
 
128
- const layer = HttpApiBuilder.layer(${apiExport}).pipe(
129
- Layer.provide(${groupName}Layer),
130
- ) satisfies EffectRuntimeLayer;
131
- const apiRuntime: EffectBffDefinition<typeof ${apiExport}, EffectRuntimeLayer> &
132
- EffectBffRuntime<typeof ${apiExport}, EffectRuntimeLayer> = defineEffectBff({
133
- api: ${apiExport},
134
- layer,
135
- });
153
+ ${options ? `const apiHandlersLive = Layer.mergeAll(${groupName}Layer, ${groupName}ReadinessLayer);
154
+ export const make${apiExport[0].toUpperCase()}${apiExport.slice(1)}Runtime = () =>
155
+ assembleEffectBffRuntime({
156
+ api: ${apiExport},
157
+ handlers: apiHandlersLive,
158
+ });` : `export const make${apiExport[0].toUpperCase()}${apiExport.slice(1)}Runtime = () => {
159
+ const layer = HttpApiBuilder.layer(${apiExport}).pipe(
160
+ Layer.provide(${groupName}Layer),
161
+ ) satisfies EffectRuntimeLayer;
162
+ return defineEffectBff({ api: ${apiExport}, layer });
163
+ };`}
164
+ const apiRuntime = make${apiExport[0].toUpperCase()}${apiExport.slice(1)}Runtime();
136
165
 
137
166
  export default apiRuntime;
138
167
  `;
@@ -1,6 +1,6 @@
1
1
  import { resolveApiPrefix, resolveApiStem } from "../descriptors.js";
2
2
  import { renderTemplate } from "../fs-io.js";
3
- import { toPascalCase } from "../naming.js";
3
+ import { packageName, toPascalCase } from "../naming.js";
4
4
  import { createCheckoutCartApiContractFields, createCheckoutCartEndpointDefinitions, createCheckoutCartOperationContexts, createCheckoutCartSharedSchemas } from "./checkout-cart.js";
5
5
  import { verticalApiCreatePayloadSchemaExport, verticalApiExport, verticalApiGroupName, verticalApiMarkerSchemaExport, verticalApiName, verticalApiNotFoundErrorExport, verticalApiNotFoundSchemaExport, verticalApiReadinessSchemaExport, verticalApiSchemaExport } from "./names.js";
6
6
  function createSharedApiImports() {
@@ -13,7 +13,7 @@ function createSharedApiImports() {
13
13
  } from '@modern-js/plugin-bff/effect-client';
14
14
  `;
15
15
  }
16
- function createSharedApiContract(service) {
16
+ function createSharedApiContract(service, shared = false) {
17
17
  const schemaExport = verticalApiSchemaExport(service);
18
18
  const markerSchemaExport = verticalApiMarkerSchemaExport(service);
19
19
  const readinessSchemaExport = verticalApiReadinessSchemaExport(service);
@@ -70,7 +70,7 @@ function createSharedApiContract(service) {
70
70
  listOperationContext,
71
71
  readinessOperationContext
72
72
  }).trim();
73
- return `export interface ${markerType} {
73
+ return `${shared ? `export type ${markerType} = MicroVerticalBuildMarker;` : `export interface ${markerType} {
74
74
  readonly appId: string;
75
75
  readonly build: string;
76
76
  readonly buildMarker: string;
@@ -80,7 +80,7 @@ function createSharedApiContract(service) {
80
80
  readonly surface: string;
81
81
  readonly unitId: string;
82
82
  readonly version: string;
83
- }
83
+ }`}
84
84
 
85
85
  export interface ${itemType} {
86
86
  readonly id: string;
@@ -88,7 +88,7 @@ export interface ${itemType} {
88
88
  readonly title: string;
89
89
  }
90
90
 
91
- export interface ${readinessType} {
91
+ ${shared ? `export type ${readinessType} = MicroVerticalReadiness;` : `export interface ${readinessType} {
92
92
  readonly checks: {
93
93
  readonly api: 'ready';
94
94
  readonly moduleFederation: 'ready';
@@ -98,7 +98,7 @@ export interface ${readinessType} {
98
98
  readonly marker: ${markerType};
99
99
  readonly status: 'ready';
100
100
  readonly versionSkew: 'none';
101
- }
101
+ }`}
102
102
 
103
103
  export interface ${createPayloadType} {
104
104
  readonly title: string;
@@ -117,7 +117,7 @@ export interface ${notFoundErrorExport} {
117
117
  readonly id: string;
118
118
  }
119
119
 
120
- export const ${markerSchemaExport}: Schema.Codec<${markerType}> = Schema.Struct({
120
+ ${shared ? `export const ${markerSchemaExport}: Schema.Codec<${markerType}> = MicroVerticalBuildMarkerSchema;` : `export const ${markerSchemaExport}: Schema.Codec<${markerType}> = Schema.Struct({
121
121
  appId: Schema.String,
122
122
  build: Schema.String,
123
123
  buildMarker: Schema.String,
@@ -127,7 +127,7 @@ export const ${markerSchemaExport}: Schema.Codec<${markerType}> = Schema.Struct(
127
127
  surface: Schema.String,
128
128
  unitId: Schema.String,
129
129
  version: Schema.String,
130
- });
130
+ });`}
131
131
 
132
132
  export const ${schemaExport}: Schema.Codec<${itemType}> = Schema.Struct({
133
133
  id: Schema.String,
@@ -135,7 +135,7 @@ export const ${schemaExport}: Schema.Codec<${itemType}> = Schema.Struct({
135
135
  title: Schema.String,
136
136
  });
137
137
 
138
- export const ${readinessSchemaExport}: Schema.Codec<${readinessType}> = Schema.Struct({
138
+ ${shared ? `export const ${readinessSchemaExport}: Schema.Codec<${readinessType}> = MicroVerticalReadinessSchema;` : `export const ${readinessSchemaExport}: Schema.Codec<${readinessType}> = Schema.Struct({
139
139
  checks: Schema.Struct({
140
140
  api: Schema.Literal('ready'),
141
141
  moduleFederation: Schema.Literal('ready'),
@@ -145,7 +145,7 @@ export const ${readinessSchemaExport}: Schema.Codec<${readinessType}> = Schema.S
145
145
  marker: ${markerSchemaExport},
146
146
  status: Schema.Literal('ready'),
147
147
  versionSkew: Schema.Literal('none'),
148
- });
148
+ });`}
149
149
 
150
150
  export const ${createPayloadSchemaExport}: Schema.Codec<${createPayloadType}> = Schema.Struct({
151
151
  title: Schema.String,
@@ -157,7 +157,7 @@ ${checkoutCartSharedSchemaSection}export const ${notFoundSchemaExport}: Schema.C
157
157
  HttpApiSchema.status(404),
158
158
  );
159
159
 
160
- export interface OperationContext {
160
+ ${shared ? 'export type OperationContext = MicroVerticalOperationContext;' : `export interface OperationContext {
161
161
  method: string;
162
162
  operationId: string;
163
163
  routePath: string;
@@ -169,9 +169,14 @@ export interface OperationContext {
169
169
  | 'data-platform'
170
170
  | 'unknown';
171
171
  traceId?: string;
172
- }
172
+ }`}
173
173
 
174
- export const ${apiExport} = HttpApi.make('${apiName}').add(
174
+ ${shared ? `export const ${groupName}FoundationApi = HttpApi.make('${apiName}Foundation').add(
175
+ HttpApiGroup.make('foundation').add(
176
+ HttpApiEndpoint.get('readiness', '/${stem}/readiness', { success: ${readinessSchemaExport} }),
177
+ ),
178
+ );` : ''}
179
+ export const ${apiExport} = HttpApi.make('${apiName}')${shared ? `.addHttpApi(${groupName}FoundationApi)` : ''}.add(
175
180
  HttpApiGroup.make('${groupName}')
176
181
  .add(
177
182
  HttpApiEndpoint.get('list', '/${stem}', {
@@ -183,11 +188,12 @@ export const ${apiExport} = HttpApi.make('${apiName}').add(
183
188
  }),
184
189
  }),
185
190
  )
186
- .add(
191
+ ${shared ? '' : ` .add(
187
192
  HttpApiEndpoint.get('readiness', '/${stem}/readiness', {
188
193
  success: ${readinessSchemaExport},
189
194
  }),
190
195
  )
196
+ `}
191
197
  .add(
192
198
  HttpApiEndpoint.get('get', '/${stem}/:id', {
193
199
  error: ${notFoundSchemaExport},
@@ -208,7 +214,7 @@ export const ${apiExport} = HttpApi.make('${apiName}').add(
208
214
  );
209
215
 
210
216
  export const ${groupName}OperationContexts = {
211
- ${operationContextEntries}
217
+ ${shared ? operationContextEntries.replaceAll(/(\w+: )\{([\s\S]*?)\},/gu, (_, key, fields)=>`${key}createMicroVerticalOperationContext({${fields.replace(/\n\s*source: 'generated-client',/u, '')}}),`) : operationContextEntries}
212
218
  } satisfies Record<string, OperationContext>;
213
219
 
214
220
  export const ${groupName}ApiContract = {
@@ -219,8 +225,12 @@ ${createCheckoutCartApiContractFields(service)} ownerId: '${service.id}',
219
225
  } as const;
220
226
  `;
221
227
  }
222
- function createSharedApi(service) {
228
+ function createSharedApi(service, options) {
229
+ const imports = options ? `import { MicroVerticalBuildMarkerSchema, MicroVerticalReadinessSchema, createMicroVerticalOperationContext } from '${packageName(options.scope, 'shared-contracts')}/microvertical-api-baseline';
230
+ import type { MicroVerticalBuildMarker, MicroVerticalReadiness, MicroVerticalOperationContext } from '${packageName(options.scope, 'shared-contracts')}/microvertical-api-baseline';
231
+ ` : '';
223
232
  return `${createSharedApiImports()}
224
- ${createSharedApiContract(service)}`;
233
+ ${imports}
234
+ ${createSharedApiContract(service, void 0 !== options)}`;
225
235
  }
226
236
  export { createSharedApi };
@@ -11,7 +11,11 @@ function extractExposes(configPath, value) {
11
11
  if (array) return array.sort();
12
12
  throw new Error(`Cannot statically extract Module Federation exposes from ${configPath}; use a literal exposes object or string array.`);
13
13
  }
14
- function extractDtsSettings(configPath, value) {
14
+ function extractDtsSettings(configPath, value, exposes) {
15
+ if ('false' === value) {
16
+ if (exposes.length > 0) throw new Error(`Module Federation DTS cannot be disabled for exposed app ${configPath}.`);
17
+ return {};
18
+ }
15
19
  if (void 0 === value) return {};
16
20
  const dts = parseObjectLiteral(value);
17
21
  if (!dts || dts.hasSpread) throw new Error(`Cannot statically extract Module Federation DTS settings from ${configPath}; use a literal dts object.`);
@@ -38,7 +42,7 @@ function inspectModuleFederationConfigSource(source, appDir, configPath) {
38
42
  return {
39
43
  appDir,
40
44
  configPath,
41
- dts: extractDtsSettings(configPath, properties.properties.get('dts')),
45
+ dts: extractDtsSettings(configPath, properties.properties.get('dts'), exposes),
42
46
  exposes,
43
47
  hostOnlyNoExposes
44
48
  };
@@ -45,22 +45,17 @@ const ultramodernBuildArtifact = {
45
45
  },
46
46
  } as const;
47
47
 
48
- export { ultramodernBuildArtifact };
49
-
50
48
  export const ultramodernDeliveryUnit =
51
49
  ultramodernBuildArtifact.deliveryUnit;
52
- export const ultramodernVerticalIdentity = ultramodernDeliveryUnit;
53
50
  export const ultramodernUiMarker = ultramodernBuildArtifact.surfaces.ui;
54
51
  export const ultramodernApiMarker = ultramodernBuildArtifact.surfaces.api;
55
52
  `;
56
53
  }
57
54
  function createUltramodernBuildReexportModule() {
58
55
  return `export {
59
- ultramodernBuildArtifact,
60
56
  ultramodernApiMarker,
61
57
  ultramodernDeliveryUnit,
62
58
  ultramodernUiMarker,
63
- ultramodernVerticalIdentity,
64
59
  } from '../shared/ultramodern-build';
65
60
  `;
66
61
  }
@@ -171,7 +171,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [
171
171
  if (Object.keys(packageExports).length > 0) packageJson.exports = packageExports;
172
172
  return packageJson;
173
173
  }
174
- function createSharedPackage(scope, id, description) {
174
+ function createSharedPackage(scope, id, description, packageSource) {
175
175
  const packageJson = {
176
176
  private: true,
177
177
  name: packageName(scope, id),
@@ -188,6 +188,17 @@ function createSharedPackage(scope, id, description) {
188
188
  '@effect/tsgo': ULTRAMODERN_PACKAGE_PINS.appDevDependencies["@effect/tsgo"]
189
189
  }
190
190
  };
191
+ if ('shared-contracts' === id) {
192
+ packageJson.exports = {
193
+ '.': './src/index.ts',
194
+ './microvertical-api-baseline': './src/microvertical-api-baseline.ts',
195
+ './server/effect-bff-runtime': './src/effect-bff-runtime.ts'
196
+ };
197
+ packageJson.dependencies = {
198
+ ...ULTRAMODERN_PACKAGE_PINS.bffEffectDependencies,
199
+ '@modern-js/plugin-bff': packageSource ? modernPackageSpecifier('@modern-js/plugin-bff', packageSource) : WORKSPACE_PACKAGE_VERSION
200
+ };
201
+ }
191
202
  if ('shared-design-tokens' === id) packageJson.exports = {
192
203
  ...packageJson.exports,
193
204
  './tokens.css': './src/tokens.css'
@@ -249,6 +249,7 @@ const ULTRAMODERN_PACKAGE_PINS = {
249
249
  rootDevDependencies: {
250
250
  '@effect/tsgo': EFFECT_TSGO_VERSION,
251
251
  "@typescript/native": `npm:typescript@${TYPESCRIPT_VERSION}`,
252
+ '@types/node': TYPES_NODE_VERSION,
252
253
  'cross-env': CROSS_ENV_VERSION,
253
254
  lefthook: LEFTHOOK_VERSION,
254
255
  miniflare: MINIFLARE_VERSION,
@@ -6,6 +6,9 @@ function createTsConfigBase() {
6
6
  return {
7
7
  compilerOptions: {
8
8
  target: 'ESNext',
9
+ types: [
10
+ 'node'
11
+ ],
9
12
  lib: [
10
13
  'ESNext',
11
14
  'DOM',
@@ -139,7 +139,7 @@ function createWorkspaceRootScriptPlan(remotes = [], options = {}) {
139
139
  migrateStrictEffect: rootToolingWrapperCommand('migrateStrictEffect'),
140
140
  zeropsMaterialize: "node ./scripts/materialize-zerops-runtime.mjs",
141
141
  contractCheck: rootToolingWrapperCommand('validate'),
142
- typecheck: options.typecheck ?? `${rootToolingWrapperCommand('typecheck')} --project tsconfig.json`,
142
+ typecheck: options.typecheck ?? `${rootToolingWrapperCommand('typecheck')} --build tsconfig.json`,
143
143
  check: `pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm i18n:boundaries && pnpm api:check && pnpm contract:check && pnpm performance:readiness${bridgeCheck}`
144
144
  };
145
145
  }
@@ -129,6 +129,7 @@ function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remote
129
129
  const hasBackendSurface = remotes.some(appHasApi);
130
130
  writeWorkspaceOwnedMtsScript(targetDir, 'check-ultramodern-i18n-boundaries', createWorkspaceI18nBoundaryValidationScript());
131
131
  writeWorkspaceOwnedMtsScript(targetDir, 'check-ultramodern-api-boundaries', createWorkspaceApiBoundaryValidationScript());
132
+ writeWorkspaceOwnedMtsScript(targetDir, 'microvertical-api-baseline-boundary', external_fs_io_js_readFileTemplate("workspace-scripts/microvertical-api-baseline-boundary.mts"));
132
133
  if (!shellOnly) {
133
134
  writeFileReplacing(targetDir, "scripts/materialize-zerops-runtime.mjs", createZeropsRuntimeMaterializationScript());
134
135
  writeWorkspaceOwnedMtsScript(targetDir, 'proof-workerd-ssr', createWorkerdSsrProofScript());
@@ -156,6 +157,10 @@ function migratedWorkspaceScriptArtifacts(options) {
156
157
  content: createWorkspaceI18nBoundaryValidationScript(),
157
158
  legacyPath: "scripts/check-ultramodern-i18n-boundaries.mjs"
158
159
  },
160
+ {
161
+ relativePath: "scripts/microvertical-api-baseline-boundary.mts",
162
+ content: external_fs_io_js_readFileTemplate("workspace-scripts/microvertical-api-baseline-boundary.mts")
163
+ },
159
164
  {
160
165
  relativePath: "scripts/check-ultramodern-api-boundaries.mts",
161
166
  content: createWorkspaceApiBoundaryValidationScript(),
@@ -88,11 +88,17 @@ function writeAppRouteAndShellFiles({ targetDir, scope, resolvedApp, emitsUi, re
88
88
  function writeAppApiAndRemoteExposeFiles({ targetDir, scope, resolvedApp, emitsUi, writeAppFile }) {
89
89
  if (appHasApi(resolvedApp)) {
90
90
  const rpcProtocol = 'rpc' === resolveApiProtocol(resolvedApp);
91
- rpcProtocol ? writeFile(targetDir, `${resolvedApp.directory}/shared/rpc.ts`, createRpcContractFile(resolvedApp)) : writeFile(targetDir, `${resolvedApp.directory}/shared/api.ts`, createSharedApi(resolvedApp));
92
- writeFile(targetDir, `${resolvedApp.directory}/api/index.ts`, createApiServiceEntry(resolvedApp, rpcProtocol ? '../shared/rpc.ts' : '../shared/api.ts'));
91
+ rpcProtocol ? writeFile(targetDir, `${resolvedApp.directory}/shared/rpc.ts`, createRpcContractFile(resolvedApp)) : writeFile(targetDir, `${resolvedApp.directory}/shared/api.ts`, createSharedApi(resolvedApp, {
92
+ scope
93
+ }));
94
+ writeFile(targetDir, `${resolvedApp.directory}/api/index.ts`, createApiServiceEntry(resolvedApp, rpcProtocol ? '../shared/rpc.ts' : '../shared/api.ts', {
95
+ scope
96
+ }));
93
97
  writeFile(targetDir, `${resolvedApp.directory}/api/backend-federation.ts`, createBackendFederationContractFile(resolvedApp));
94
98
  writeFile(targetDir, `${resolvedApp.directory}/api/effect-api.ts`, createBackendEffectApiExpose(scope, resolvedApp));
95
- rpcProtocol ? writeFile(targetDir, `${resolvedApp.directory}/src/api/${resolvedApp.api.stem}-rpc-client.ts`, createRpcClientFile(resolvedApp)) : writeFile(targetDir, `${resolvedApp.directory}/src/api/${resolvedApp.api.stem}-client.ts`, createApiClient(resolvedApp, '../../shared/api'));
99
+ rpcProtocol ? writeFile(targetDir, `${resolvedApp.directory}/src/api/${resolvedApp.api.stem}-rpc-client.ts`, createRpcClientFile(resolvedApp)) : writeFile(targetDir, `${resolvedApp.directory}/src/api/${resolvedApp.api.stem}-client.ts`, createApiClient(resolvedApp, '../../shared/api', {
100
+ scope
101
+ }));
96
102
  }
97
103
  if ('vertical' === resolvedApp.kind && emitsUi) {
98
104
  writeAppFile('src/federation-entry.tsx', createRemoteEntry(resolvedApp));
@@ -5,7 +5,7 @@ import { createSharedDesignTokensCss } from "./app-files.js";
5
5
  import { normalizeUltramodernBridgeConfig } from "./bridge-config.js";
6
6
  import { createDevelopmentOverlay, createOwnership, createTopology, createUltramodernConfig } from "./contracts.js";
7
7
  import { ULTRAMODERN_CONFIG_PATH, createShellHost, sharedPackages, shellApp } from "./descriptors.js";
8
- import { copyRootTemplate, formatGeneratedWorkspaceFiles, writeFile, writeFileReplacing, writeJson } from "./fs-io.js";
8
+ import { copyRootTemplate, formatGeneratedWorkspaceFiles, readFileTemplate, writeFile, writeFileReplacing, writeJson } from "./fs-io.js";
9
9
  import { createFileSnapshot, createGenerationResult, diffFileSnapshots } from "./generation-result.js";
10
10
  import { assertUniqueTailwindPrefixes, toPackageScope } from "./naming.js";
11
11
  import { runCodeSmithOverlays } from "./overlays.js";
@@ -19,12 +19,14 @@ import { createZeropsYaml } from "./zerops.js";
19
19
  function hasExplicitInstallRequest(options) {
20
20
  return void 0 !== options.packageSource && 'workspace' !== options.packageSource.strategy;
21
21
  }
22
- function writeSharedPackages(targetDir, scope) {
22
+ function writeSharedPackages(targetDir, scope, packageSource) {
23
23
  for (const sharedPackage of sharedPackages){
24
- writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description));
24
+ writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
25
25
  writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, createSharedPackageTsConfig(sharedPackage.directory));
26
26
  }
27
27
  writeFile(targetDir, 'packages/shared-contracts/src/index.ts', createSharedContractsIndex());
28
+ writeFile(targetDir, 'packages/shared-contracts/src/microvertical-api-baseline.ts', readFileTemplate('packages/microvertical-api-baseline.ts'));
29
+ writeFile(targetDir, 'packages/shared-contracts/src/effect-bff-runtime.ts', readFileTemplate('packages/effect-bff-runtime.ts'));
28
30
  writeFile(targetDir, 'packages/shared-design-tokens/src/index.ts', `export const sharedDesignTokens = {
29
31
  color: {
30
32
  accent: '#2f8f68',
@@ -144,7 +146,7 @@ function generateUltramodernWorkspaceInPlace(options, logicalWorkspaceRoot = opt
144
146
  writeJson(options.targetDir, ULTRAMODERN_CONFIG_PATH, createCompactUltramodernConfig(scope, options.modernVersion, packageSource, createdApps, enableTailwind, bridge));
145
147
  writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind, initialVerticals, bridge);
146
148
  for (const remote of initialVerticals)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind, initialVerticals, bridge);
147
- writeSharedPackages(options.targetDir, scope);
149
+ writeSharedPackages(options.targetDir, scope, packageSource);
148
150
  writeGeneratedWorkspaceScripts(options.targetDir, scope, enableTailwind, initialVerticals, releaseCohort);
149
151
  const preliminaryAfterFiles = createFileSnapshot(options.targetDir);
150
152
  const preliminaryDiff = diffFileSnapshots(beforeFiles, preliminaryAfterFiles);
@@ -71,6 +71,7 @@ function mergeTypeScriptConfig(generated, existing) {
71
71
  ...existingCompilerOptions,
72
72
  ...generatedCompilerOptions
73
73
  };
74
+ if (Array.isArray(generatedCompilerOptions.types)) compilerOptions.types = mergeUniqueJsonValues(generatedCompilerOptions.types, existingCompilerOptions.types);
74
75
  if (Array.isArray(generatedCompilerOptions.plugins) || Array.isArray(existingCompilerOptions.plugins)) compilerOptions.plugins = mergeTypeScriptPlugins(generatedCompilerOptions.plugins, existingCompilerOptions.plugins);
75
76
  const merged = {
76
77
  ...existingConfig,
@@ -136,6 +136,8 @@ function mergeGeneratedScript(consumer, framework, canRetireLegacyOxfmtCliExclus
136
136
  const frameworkSegmentSet = new Set(frameworkSegments);
137
137
  const canonicalFrameworkSegment = (segment)=>{
138
138
  if (frameworkSegmentSet.has(segment)) return segment;
139
+ const referenceBuild = segment.replace('--project tsconfig.json', '--build tsconfig.json');
140
+ if (frameworkSegmentSet.has(referenceBuild)) return referenceBuild;
139
141
  const portableEnvironmentSegment = `cross-env ${segment}`;
140
142
  if (frameworkSegmentSet.has(portableEnvironmentSegment)) return portableEnvironmentSegment;
141
143
  const portableOxfmtSegment = segment.replace(/ '!repos\/\*\*'$/u, '');
@@ -0,0 +1,51 @@
1
+ import "node:module";
2
+ import node_fs from "node:fs";
3
+ import node_path from "node:path";
4
+ import { readFileTemplate } from "../../../ultramodern-workspace/fs-io.js";
5
+ import { createSharedPackage, createSharedPackageTsConfig } from "../../../ultramodern-workspace/package-json.js";
6
+ import { readJsonFile, writeJsonFile } from "./io.js";
7
+ const directory = 'packages/shared-contracts';
8
+ function record(value, label) {
9
+ if (null === value || 'object' != typeof value || Array.isArray(value)) throw new Error(`${label} must be an object; resolve it before migration.`);
10
+ return value;
11
+ }
12
+ function ensureSharedApiInfrastructure(io, scope, packageSource) {
13
+ for (const relativePath of [
14
+ 'packages',
15
+ directory,
16
+ `${directory}/package.json`,
17
+ `${directory}/src`,
18
+ `${directory}/src/index.ts`
19
+ ])if (node_fs.lstatSync(node_path.join(io.workspaceRoot, relativePath), {
20
+ throwIfNoEntry: false
21
+ })?.isSymbolicLink()) throw new Error(`Shared API migration cannot write through a symbolic link: ${relativePath}`);
22
+ const manifestPath = node_path.join(io.workspaceRoot, directory, 'package.json');
23
+ const defaults = createSharedPackage(scope, 'shared-contracts', 'Shared workspace contracts', packageSource);
24
+ const manifest = node_fs.existsSync(manifestPath) ? record(readJsonFile(manifestPath), `${directory}/package.json`) : defaults;
25
+ const exports = void 0 === manifest.exports ? {} : 'string' == typeof manifest.exports ? {
26
+ '.': manifest.exports
27
+ } : record(manifest.exports, `${directory}/package.json exports`);
28
+ if (Object.keys(exports).some((key)=>!key.startsWith('.')) || void 0 !== exports['.'] && './src/index.ts' !== exports['.']) throw new Error(`${directory}/package.json exports must expose ./src/index.ts at "." before adding shared API infrastructure; consumer exports were not overwritten.`);
29
+ for (const [subpath, target] of Object.entries(record(defaults.exports, 'Shared contract exports')))if (void 0 !== exports[subpath] && exports[subpath] !== target) throw new Error(`${directory}/package.json ${subpath} must expose ${String(target)}; consumer exports were not overwritten.`);
30
+ const dependencies = record(manifest.dependencies ?? {}, `${directory}/package.json dependencies`);
31
+ manifest.exports = {
32
+ ...record(defaults.exports, 'Shared contract exports'),
33
+ ...exports
34
+ };
35
+ manifest.dependencies = {
36
+ ...record(defaults.dependencies, 'Shared contract dependencies'),
37
+ ...dependencies
38
+ };
39
+ const writeMissing = (relativePath, content)=>{
40
+ const filePath = node_path.join(io.workspaceRoot, directory, relativePath);
41
+ if (void 0 === node_fs.lstatSync(filePath, {
42
+ throwIfNoEntry: false
43
+ })) io.writeGenerated(filePath, content);
44
+ };
45
+ writeMissing('src/effect-bff-runtime.ts', readFileTemplate('packages/effect-bff-runtime.ts'));
46
+ writeMissing('src/microvertical-api-baseline.ts', readFileTemplate('packages/microvertical-api-baseline.ts'));
47
+ writeMissing('tsconfig.json', `${JSON.stringify(createSharedPackageTsConfig(directory), null, 2)}\n`);
48
+ writeMissing('src/index.ts', readFileTemplate('packages/shared-contracts-index.ts'));
49
+ writeJsonFile(io, manifestPath, manifest);
50
+ }
51
+ export { ensureSharedApiInfrastructure };
@@ -36,6 +36,7 @@ function updateRootPackageToolchain(packageJson) {
36
36
  const devDependencies = ensureRecord(packageJson, 'devDependencies', 'package.json devDependencies');
37
37
  for (const packageName of retiredRootToolingDependencies)delete devDependencies[packageName];
38
38
  devDependencies["@typescript/native"] = ULTRAMODERN_PACKAGE_PINS.rootDevDependencies["@typescript/native"];
39
+ devDependencies['@types/node'] = ULTRAMODERN_PACKAGE_PINS.rootDevDependencies["@types/node"];
39
40
  devDependencies.miniflare = ULTRAMODERN_PACKAGE_PINS.rootDevDependencies.miniflare;
40
41
  }
41
42
  function updateMiseTools(content) {
@@ -19,6 +19,7 @@ import { ensureBffEffectDependencies, updateGeneratedPackageScripts, updateGener
19
19
  import { createMigrationPackageSource } from "./migrate-strict-effect/package-source.js";
20
20
  import { updateGeneratedPnpmWorkspacePolicy, validateGeneratedPnpmLockReleaseAgePolicy } from "./migrate-strict-effect/pnpm-policy.js";
21
21
  import { ensureGeneratedModuleFederationBridgeRouterOptOut, preflightModuleFederationBridgeRouter, removeRetiredReactRouterDependency } from "./migrate-strict-effect/react-router-retirement.js";
22
+ import { ensureSharedApiInfrastructure } from "./migrate-strict-effect/shared-api-infrastructure.js";
22
23
  import { updateGeneratedToolchainFiles, updateRootPackageToolchain } from "./migrate-strict-effect/toolchain-pins.js";
23
24
  import { hasFlag } from "./options.js";
24
25
  const retiredMetadataPaths = [
@@ -283,6 +284,7 @@ function migrateStrictEffect(args, context, io, dryRun, skipInstall) {
283
284
  const verticalApps = migrated.topology.apps.filter((app)=>'shell' !== app.kind);
284
285
  const hasBackendSurface = verticalApps.some((app)=>app.api);
285
286
  const shellOnly = 0 === verticalApps.length;
287
+ if (hasBackendSurface) ensureSharedApiInfrastructure(io, migrated.workspace.packageScope, packageSource);
286
288
  if (shellOnly) {
287
289
  io.log('Shell-only workspace: skipping backend-federation and Zerops runtime stages.');
288
290
  for (const relativePath of [