@touk/federated-types 1.2.1 → 1.3.0-beta.1

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 (3) hide show
  1. package/README.md +42 -8
  2. package/cli.js +92 -30
  3. package/package.json +46 -45
package/README.md CHANGED
@@ -14,7 +14,6 @@ npm install @touk/federated-types
14
14
 
15
15
  You'll also need to place a `federation.config.json` in each package being federated. It will contain the remote name and exported members. These properties are used in Webpack's `ModuleFederationPlugin` configuration object. An example:
16
16
 
17
-
18
17
  ```json
19
18
  //federation.config.json
20
19
 
@@ -28,11 +27,9 @@ You'll also need to place a `federation.config.json` in each package being feder
28
27
 
29
28
  It's recommended that you spread these properties into your ModuleFederationPlugin configuration, like so:
30
29
 
31
-
32
30
  ```javascript
33
31
  //webpack.config.js
34
32
 
35
- const deps = require('../package.json').dependencies;
36
33
  const federationConfig = require('./federation.config.json');
37
34
 
38
35
  module.exports = {
@@ -42,9 +39,7 @@ module.exports = {
42
39
  new ModuleFederationPlugin({
43
40
  ...federationConfig,
44
41
  filename: "remoteEntry.js",
45
- shared: {
46
- ...deps,
47
- },
42
+ shared: { ... },
48
43
  }),
49
44
  ],
50
45
 
@@ -55,7 +50,6 @@ module.exports = {
55
50
 
56
51
  Then you can call `make-federated-types` from your `scripts` block in your package's `package.json` file:
57
52
 
58
-
59
53
  ```javascript
60
54
  //package.json
61
55
 
@@ -68,7 +62,6 @@ This will write new package to the `node_modules/@types/__federated_types` in yo
68
62
 
69
63
  If you would rather specify a directory in which to write the typing files, you can pass an `--outputDir` parameter to the command like so:
70
64
 
71
-
72
65
  ```javascript
73
66
  //package.json
74
67
 
@@ -76,6 +69,7 @@ scripts: {
76
69
  "make-types": "make-federated-types --outputDir ../../my_types/"
77
70
  }
78
71
  ```
72
+
79
73
  You can add an `--saveToNodeModules` parameter to save in both places.
80
74
 
81
75
  If you would like to specify custom path to the config, you can pass `--config` parameter like so:
@@ -87,3 +81,43 @@ scripts: {
87
81
  "make-types": "make-federated-types --config ./path/to/my/config.json --outputDir ../../my_types/"
88
82
  }
89
83
  ```
84
+
85
+ ## Inline configuration (without config file)
86
+
87
+ If you prefer not to maintain a separate `federation.config.json` file, you can provide the configuration directly via CLI arguments using `--name` and `--exposes`:
88
+
89
+ ```bash
90
+ make-federated-types \
91
+ --name myApp \
92
+ --exposes App ./src/App.tsx \
93
+ --exposes Button ./src/components/Button.tsx \
94
+ --outputDir ./types
95
+ ```
96
+
97
+ ### Inline config features:
98
+
99
+ - **Multiple exposures**: Use `--exposes` multiple times, each followed by a key and file path
100
+ - **Auto-prefix**: Keys without `./` prefix are automatically normalized (e.g., `App` becomes `./App`)
101
+ - **Shell-friendly**: Works with tab completion for file paths
102
+ - **Priority**: Inline config takes precedence over `--config` and auto-discovery
103
+
104
+ ### Examples:
105
+
106
+ ```bash
107
+ # Simple inline config
108
+ make-federated-types --name app2 --exposes Button ./src/Button.tsx
109
+
110
+ # Multiple exposures
111
+ make-federated-types \
112
+ --name app2 \
113
+ --exposes ./Button ./src/Button.tsx \
114
+ --exposes ./Header ./src/Header.tsx
115
+
116
+ # Nested keys (auto-prefixed with ./)
117
+ make-federated-types \
118
+ --name app2 \
119
+ --exposes components/Button ./src/components/Button.tsx \
120
+ --exposes utils/helpers ./src/utils/helpers.ts
121
+ ```
122
+
123
+ **Note**: When using inline config, both `--name` and at least one `--exposes` are required.
package/cli.js CHANGED
@@ -7,11 +7,16 @@ const ts = require('typescript');
7
7
 
8
8
  const formatHost = {
9
9
  getCurrentDirectory: ts.sys.getCurrentDirectory,
10
- getNewLine: () => ts.sys.newLine
10
+ getNewLine: () => ts.sys.newLine,
11
11
  };
12
12
 
13
13
  function reportDiagnostic(diagnostic) {
14
- console.log("TS Error", diagnostic.code, ":", ts.flattenDiagnosticMessageText( diagnostic.messageText, formatHost.getNewLine()));
14
+ console.log(
15
+ 'TS Error',
16
+ diagnostic.code,
17
+ ':',
18
+ ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine())
19
+ );
15
20
  }
16
21
 
17
22
  const [nodeModules] = findNodeModules({ cwd: process.argv[1], relative: false });
@@ -24,23 +29,43 @@ const getArg = (argName) => {
24
29
  const argIndex = process.argv.indexOf(argName);
25
30
  return argIndex !== -1 ? process.argv[argIndex + 1] : null;
26
31
  };
32
+ const getAllArgs = (argName) => {
33
+ const results = [];
34
+ let i = 2; // process.argv starts with node and script path
35
+
36
+ while (i < process.argv.length) {
37
+ if (process.argv[i] === argName) {
38
+ // Next two arguments are key and value
39
+ if (i + 2 < process.argv.length) {
40
+ results.push([process.argv[i + 1], process.argv[i + 2]]);
41
+ i += 3; // skip argName, key, value
42
+ } else {
43
+ console.error(`ERROR: --exposes requires two arguments (key path)`);
44
+ process.exit(1);
45
+ }
46
+ } else {
47
+ i++;
48
+ }
49
+ }
50
+ return results;
51
+ };
27
52
 
28
53
  const nodeModulesOutputDir = path.resolve(nodeModules, '@types/__federated_types/');
29
54
  const saveToNodeMoulesArg = hasArg('--saveToNodeModules');
30
55
  const outDirArg = getArg('--outputDir');
31
56
 
32
- const outputDir = outDirArg
33
- ? path.resolve('./', outDirArg)
34
- : nodeModulesOutputDir;
57
+ const outputDir = outDirArg ? path.resolve('./', outDirArg) : nodeModulesOutputDir;
35
58
 
36
- const outputDirs = outputDir !== nodeModulesOutputDir && saveToNodeMoulesArg
37
- ? [nodeModulesOutputDir, outputDir]
38
- : [outputDir];
59
+ const outputDirs =
60
+ outputDir !== nodeModulesOutputDir && saveToNodeMoulesArg
61
+ ? [nodeModulesOutputDir, outputDir]
62
+ : [outputDir];
39
63
 
40
64
  const configPathArg = getArg('--config');
41
- const configPath = configPathArg
42
- ? path.resolve(configPathArg)
43
- : null;
65
+ const configPath = configPathArg ? path.resolve(configPathArg) : null;
66
+
67
+ const nameArg = getArg('--name');
68
+ const exposesArgs = getAllArgs('--exposes');
44
69
 
45
70
  const findFederationConfig = (base) => {
46
71
  let files = fs.readdirSync(base);
@@ -56,27 +81,57 @@ const findFederationConfig = (base) => {
56
81
  }
57
82
  }
58
83
 
59
- for( let i = 0; i < queue.length; i++ ) {
84
+ for (let i = 0; i < queue.length; i++) {
60
85
  return findFederationConfig(queue[i]);
61
86
  }
62
87
  };
63
88
 
64
- if (configPath && !fs.existsSync(configPath)) {
65
- console.error(`ERROR: Unable to find a provided config: ${configPath}`);
66
- process.exit(1);
67
- }
89
+ let federationConfig;
68
90
 
69
- const federationConfigPath = configPath || findFederationConfig('./');
91
+ // Priority: inline config > --config > auto-find
92
+ if (nameArg || exposesArgs.length > 0) {
93
+ // Inline config mode
94
+ if (!nameArg || exposesArgs.length === 0) {
95
+ console.error('ERROR: Both --name and --exposes are required for inline config');
96
+ process.exit(1);
97
+ }
70
98
 
99
+ if (configPath) {
100
+ console.warn('WARNING: Both inline config and --config provided. Using inline config.');
101
+ }
71
102
 
72
- if (federationConfigPath === undefined) {
73
- console.error(`ERROR: Unable to find a federation.config.json file in this package`);
74
- process.exit(1);
75
- }
103
+ const exposes = {};
104
+
105
+ exposesArgs.forEach(([key, value]) => {
106
+ // Normalize key - add ./ if missing
107
+ const normalizedKey = key.startsWith('./') ? key : `./${key}`;
108
+ exposes[normalizedKey] = value;
109
+ });
110
+
111
+ federationConfig = {
112
+ name: nameArg,
113
+ exposes: exposes,
114
+ };
115
+
116
+ console.log('Using inline config:', JSON.stringify(federationConfig, null, 2));
117
+ } else {
118
+ // File-based config (current behavior)
119
+ if (configPath && !fs.existsSync(configPath)) {
120
+ console.error(`ERROR: Unable to find a provided config: ${configPath}`);
121
+ process.exit(1);
122
+ }
123
+
124
+ const federationConfigPath = configPath || findFederationConfig('./');
76
125
 
77
- console.log(`Using config file: ${federationConfigPath}`);
126
+ if (federationConfigPath === undefined) {
127
+ console.error(`ERROR: Unable to find a federation.config.json file in this package`);
128
+ process.exit(1);
129
+ }
130
+
131
+ console.log(`Using config file: ${federationConfigPath}`);
132
+ federationConfig = require(federationConfigPath);
133
+ }
78
134
 
79
- const federationConfig = require(federationConfigPath);
80
135
  const compileFiles = Object.values(federationConfig.exposes);
81
136
  const compileKeys = Object.keys(federationConfig.exposes);
82
137
 
@@ -97,16 +152,16 @@ try {
97
152
  declaration: true,
98
153
  emitDeclarationOnly: true,
99
154
  skipLibCheck: true,
100
- jsx: 'react',
155
+ jsx: ts.JsxEmit.React,
101
156
  esModuleInterop: true,
102
157
  });
103
158
 
104
159
  const { emitSkipped, diagnostics } = program.emit();
105
160
 
106
- diagnostics.forEach(reportDiagnostic)
161
+ diagnostics.forEach(reportDiagnostic);
107
162
 
108
163
  if (emitSkipped) {
109
- process.exit(0)
164
+ process.exit(0);
110
165
  }
111
166
 
112
167
  let typing = fs.readFileSync(outFile, { encoding: 'utf8', flag: 'r' });
@@ -114,19 +169,23 @@ try {
114
169
  const moduleRegex = RegExp(/declare module "(.*)"/, 'g');
115
170
  const moduleNames = [];
116
171
 
172
+ let execResults;
117
173
  while ((execResults = moduleRegex.exec(typing)) !== null) {
118
174
  moduleNames.push(execResults[1]);
119
175
  }
120
176
 
121
177
  moduleNames.forEach((name) => {
122
178
  // exposeName - relative name of exposed component (if not found - just take moduleName)
123
- const [exposeName = name, ...aliases] = compileKeys.filter(key => federationConfig.exposes[key].endsWith(name));
179
+ const [exposeName = name, ...aliases] = compileKeys.filter((key) => {
180
+ const normalizedPath = federationConfig.exposes[key].replace(/\.(tsx?|jsx?)$/, '');
181
+ return normalizedPath.endsWith(name);
182
+ });
124
183
  const regex = RegExp(`"${name}"`, 'g');
125
184
 
126
185
  const moduleDeclareName = getModuleDeclareName(exposeName);
127
186
 
128
187
  // language=TypeScript
129
- const createAliasModule = name => `
188
+ const createAliasModule = (name) => `
130
189
  declare module "${getModuleDeclareName(name)}" {
131
190
  export * from "${moduleDeclareName}"
132
191
  }
@@ -138,7 +197,7 @@ try {
138
197
  ].join('\n');
139
198
  });
140
199
 
141
- outputDirs.forEach(_outputDir => {
200
+ outputDirs.forEach((_outputDir) => {
142
201
  const _outFile = path.resolve(_outputDir, `${federationConfig.name}.d.ts`);
143
202
 
144
203
  console.log('writing typing file:', _outFile);
@@ -152,7 +211,10 @@ try {
152
211
 
153
212
  if (!fs.existsSync(packageJsonPath)) {
154
213
  console.debug('writing package.json:', packageJsonPath);
155
- fs.copyFileSync(path.resolve(__dirname, 'typings.package.tmpl.json'), packageJsonPath);
214
+ fs.copyFileSync(
215
+ path.resolve(__dirname, 'typings.package.tmpl.json'),
216
+ packageJsonPath
217
+ );
156
218
  } else {
157
219
  console.debug(packageJsonPath, 'already exists');
158
220
  }
package/package.json CHANGED
@@ -1,47 +1,48 @@
1
1
  {
2
- "name": "@touk/federated-types",
3
- "description": "share typings for your federated typescript packages around your mono-repo",
4
- "version": "1.2.1",
5
- "author": "Julian Wielga <j@touk.pl>",
6
- "bin": {
7
- "make-federated-types": "./cli.js"
8
- },
9
- "contributors": [
10
- "Jonathan Greene <jgreene@pixability.com>"
11
- ],
12
- "dependencies": {
13
- "find-node-modules": "2.1.3"
14
- },
15
- "devDependencies": {
16
- "@commitlint/cli": "17.1.2",
17
- "@commitlint/config-conventional": "13.2.0",
18
- "@semantic-release/changelog": "6.0.3",
19
- "@semantic-release/git": "10.0.1",
20
- "husky": "8.0.3",
21
- "prettier": "3.6.2",
22
- "semantic-release": "19.0.5"
23
- },
24
- "files": [
25
- "cli.js",
26
- "typings.package.tmpl.json"
27
- ],
28
- "keywords": [
29
- "module-federation",
30
- "mono-repo",
31
- "typescript",
32
- "typings"
33
- ],
34
- "license": "MIT",
35
- "main": "cli.js",
36
- "peerDependencies": {
37
- "typescript": ">4.0.0"
38
- },
39
- "repository": "touk/federated-types.git",
40
- "scripts": {
41
- "semantic-release": "semantic-release",
42
- "test": "npm run make-types -workspaces --if-present"
43
- },
44
- "workspaces": [
45
- "./packages/*"
46
- ]
2
+ "name": "@touk/federated-types",
3
+ "description": "share typings for your federated typescript packages around your mono-repo",
4
+ "version": "1.3.0-beta.1",
5
+ "author": "Julian Wielga <j@touk.pl>",
6
+ "bin": {
7
+ "make-federated-types": "./cli.js"
8
+ },
9
+ "contributors": [
10
+ "Jonathan Greene <jgreene@pixability.com>"
11
+ ],
12
+ "dependencies": {
13
+ "find-node-modules": "2.1.3"
14
+ },
15
+ "devDependencies": {
16
+ "@commitlint/cli": "^20.4.2",
17
+ "@commitlint/config-conventional": "^20.4.2",
18
+ "@semantic-release/changelog": "^6.0.3",
19
+ "@semantic-release/git": "10.0.1",
20
+ "husky": "^9.1.7",
21
+ "prettier": "^3.8.1",
22
+ "semantic-release": "^25.0.3",
23
+ "typescript": "^5.9.3"
24
+ },
25
+ "files": [
26
+ "cli.js",
27
+ "typings.package.tmpl.json"
28
+ ],
29
+ "keywords": [
30
+ "module-federation",
31
+ "mono-repo",
32
+ "typescript",
33
+ "typings"
34
+ ],
35
+ "license": "MIT",
36
+ "main": "cli.js",
37
+ "peerDependencies": {
38
+ "typescript": ">4.0.0"
39
+ },
40
+ "repository": "touk/federated-types.git",
41
+ "scripts": {
42
+ "semantic-release": "semantic-release",
43
+ "test": "npm run make-types -workspaces --if-present"
44
+ },
45
+ "workspaces": [
46
+ "./packages/*"
47
+ ]
47
48
  }