@ws-test-realm/admin-kit 0.2.4-ng16 → 0.2.6-ng16

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.
@@ -1,16 +1,26 @@
1
1
  #!/usr/bin/env node
2
- // ws-sync-paths — regenerate tsconfig.federation.json from current workspace
3
- // state. Scans angular.json for sibling-dist aliases and reads wsconfig.json
4
- // for the @ws-remote/* mapping target. Safe to run any time; written
5
- // automatically by ws-init-workspace, ws-wire-host, ws-generate-module, and
6
- // ws-drop-module.
2
+ // ws-sync-paths — refresh the workspace's federation wiring from current
3
+ // state. Does two things:
4
+ //
5
+ // 1. Regenerates `tsconfig.federation.json` from angular.json sibling
6
+ // projects (workspace-local dist paths).
7
+ // 2. Refreshes `node_modules/<name>` symlinks pointing at the fiddle's
8
+ // `.federation/<name>/` for each cross-workspace federation sibling
9
+ // published into the fiddle. Skips any name that's a workspace-local
10
+ // project (npm-workspaces already owns its symlink). With these symlinks
11
+ // in place, cross-workspace consumers import federation siblings via
12
+ // ordinary bare specifiers — no per-workspace package.json file: refs.
13
+ //
14
+ // Safe to run any time. Invoked automatically by ws-init-workspace,
15
+ // ws-wire-host, ws-generate-module, ws-drop-module, and the workspace's
16
+ // postinstall hook.
7
17
  //
8
18
  // Usage:
9
19
  // ws-sync-paths
10
20
 
11
21
  const fs = require("fs");
12
22
  const path = require("path");
13
- const { rebuildFederationTsconfig } = require("../lib/wire-host");
23
+ const { rebuildFederationTsconfig, syncFederationSymlinks } = require("../lib/wire-host");
14
24
 
15
25
  function main() {
16
26
  const cwd = process.cwd();
@@ -20,12 +30,20 @@ function main() {
20
30
  }
21
31
  try {
22
32
  const r = rebuildFederationTsconfig(cwd);
23
- const siblings = Object.keys(r.paths).filter((k) => k !== "@ws-remote/*");
33
+ const siblings = Object.keys(r.paths).filter((k) => k !== "*");
24
34
  console.log(`Wrote ${r.file}`);
25
35
  console.log(` sibling aliases: ${siblings.length ? siblings.join(", ") : "(none)"}`);
26
- console.log(
27
- ` @ws-remote/* -> ${r.federationTarget || "(not wired; run ws-wire-host <wpm-root>)"}`
28
- );
36
+
37
+ const sym = syncFederationSymlinks(cwd);
38
+ if (sym.skipped) {
39
+ console.log(`Federation symlinks: ${sym.skipped}`);
40
+ } else {
41
+ const parts = [];
42
+ if (sym.created.length) parts.push(`created [${sym.created.join(", ")}]`);
43
+ if (sym.updated.length) parts.push(`updated [${sym.updated.join(", ")}]`);
44
+ if (sym.kept.length) parts.push(`kept [${sym.kept.join(", ")}]`);
45
+ console.log(`Federation symlinks: ${parts.length ? parts.join(", ") : "(none)"}`);
46
+ }
29
47
  } catch (e) {
30
48
  console.error(e.message);
31
49
  process.exit(1);
@@ -110,6 +110,13 @@ function registerInAngularJson(workspaceDir, name) {
110
110
  main: `projects/${name}/src/main.ts`,
111
111
  polyfills: [],
112
112
  tsConfig: `projects/${name}/tsconfig.app.json`,
113
+ // Cross-workspace federation siblings reach this workspace via
114
+ // admin-kit-managed symlinks at node_modules/<name>; esbuild needs
115
+ // preserveSymlinks=true to resolve peer imports out of those .d.ts/
116
+ // .mjs files against THIS workspace's node_modules (where the
117
+ // peers are installed) rather than walking up from the symlink
118
+ // target in the fiddle.
119
+ preserveSymlinks: true,
113
120
  styles: [],
114
121
  scripts: [],
115
122
  },
package/lib/wire-host.js CHANGED
@@ -51,17 +51,12 @@ function rebuildFederationTsconfig(workspaceDir) {
51
51
  if (wsconfig.wpmRoot) {
52
52
  const hostDir = resolveHostDir(wsconfig.wpmRoot);
53
53
  federationTarget = path.join(hostDir, ".federation", "*");
54
- paths["@ws-remote/*"] = [federationTarget];
55
54
  }
56
- // Catch-all fallback: any unresolved import resolves against this
57
- // workspace's own node_modules. Required for cross-workspace federation
58
- // siblings (e.g. @ws-remote/ws-framework) whose .d.ts files reference
59
- // transitive deps (@angular/material/form-field, etc.) — TS walks up
60
- // from the .d.ts file's location to find node_modules, but federation
61
- // remotes live under <wpmRoot>/admin/.federation/ outside any consumer
62
- // workspace's tree. This catch-all lets TS fall back to the consumer's
63
- // node_modules regardless of where the .d.ts lives. Must come AFTER
64
- // specific paths so they win on prefix match.
55
+ // Catch-all fallback: unresolved imports resolve against this workspace's
56
+ // own node_modules. Cross-workspace federation siblings reach the consumer
57
+ // via admin-kit-managed symlinks at `node_modules/<name>` pointing at
58
+ // `<wpmRoot>/admin/.federation/<name>/` (see syncFederationSymlinks); from
59
+ // the consumer's POV they look like ordinary packages.
65
60
  paths["*"] = ["./node_modules/*"];
66
61
  // tsconfig.federation.json is the leaf of the workspace-local extends
67
62
  // chain. Its sole responsibility is to layer workspace-specific `paths`
@@ -73,7 +68,13 @@ function rebuildFederationTsconfig(workspaceDir) {
73
68
  // by bumping admin-kit, no local file edits required.
74
69
  const config = {
75
70
  extends: "@ws-test-realm/admin-kit/tsconfig.workspace.json",
76
- compilerOptions: { paths },
71
+ compilerOptions: {
72
+ // `paths` are interpreted relative to `baseUrl`; both belong on the
73
+ // workspace-local layer (this file). TS requires baseUrl when paths
74
+ // are set.
75
+ baseUrl: "./",
76
+ paths,
77
+ },
77
78
  };
78
79
  const file = path.join(workspaceDir, "tsconfig.federation.json");
79
80
  fs.writeFileSync(file, JSON.stringify(config, null, "\t") + "\n");
@@ -102,9 +103,81 @@ function applyWpmRoot(workspaceDir, wpmRoot, hostId) {
102
103
  };
103
104
  }
104
105
 
106
+ // For each federation lib published into the fiddle's `.federation/<name>/`,
107
+ // ensure a symlink at `<workspace>/node_modules/<name>` points at it. This
108
+ // makes cross-workspace federation siblings indistinguishable from in-workspace
109
+ // ones for everything downstream: TS resolves bare imports through
110
+ // node_modules; native-federation's `getPackageInfo` finds `package.json`;
111
+ // esbuild externalizes via the share map normally; the federation runtime
112
+ // dedupes to a singleton chunk at runtime via `singleton: true`.
113
+ //
114
+ // Skips any name that is itself a workspace-local project (entries in
115
+ // angular.json), because for those the npm-workspaces machinery already
116
+ // owns `node_modules/<name>` and points it at `projects/<name>`.
117
+ //
118
+ // Idempotent: leaves correct existing symlinks untouched, replaces stale
119
+ // ones, creates missing ones.
120
+ function syncFederationSymlinks(workspaceDir) {
121
+ const wsconfig = readWsconfig(workspaceDir);
122
+ if (!wsconfig.wpmRoot) {
123
+ return { skipped: "no wpmRoot wired", created: [], updated: [], kept: [] };
124
+ }
125
+ const hostDir = resolveHostDir(wsconfig.wpmRoot);
126
+ const federationDir = path.join(hostDir, ".federation");
127
+ if (!fs.existsSync(federationDir)) {
128
+ return { skipped: `no ${federationDir}`, created: [], updated: [], kept: [] };
129
+ }
130
+ const angularJsonPath = path.join(workspaceDir, "angular.json");
131
+ const localProjects = new Set();
132
+ if (fs.existsSync(angularJsonPath)) {
133
+ const angular = JSON.parse(fs.readFileSync(angularJsonPath, "utf8"));
134
+ for (const k of Object.keys(angular.projects || {})) localProjects.add(k);
135
+ }
136
+ const nodeModulesDir = path.join(workspaceDir, "node_modules");
137
+ fs.mkdirSync(nodeModulesDir, { recursive: true });
138
+
139
+ const created = [];
140
+ const updated = [];
141
+ const kept = [];
142
+
143
+ for (const entry of fs.readdirSync(federationDir, { withFileTypes: true })) {
144
+ if (!entry.isDirectory()) continue;
145
+ const name = entry.name;
146
+ if (localProjects.has(name)) continue;
147
+ if (!fs.existsSync(path.join(federationDir, name, "package.json"))) continue;
148
+
149
+ const target = path.join(federationDir, name);
150
+ const linkPath = path.join(nodeModulesDir, name);
151
+
152
+ if (fs.existsSync(linkPath)) {
153
+ const stat = fs.lstatSync(linkPath);
154
+ if (stat.isSymbolicLink()) {
155
+ const current = fs.readlinkSync(linkPath);
156
+ const currentAbs = path.isAbsolute(current)
157
+ ? current
158
+ : path.resolve(path.dirname(linkPath), current);
159
+ if (currentAbs === target) {
160
+ kept.push(name);
161
+ continue;
162
+ }
163
+ fs.unlinkSync(linkPath);
164
+ fs.symlinkSync(target, linkPath);
165
+ updated.push(name);
166
+ continue;
167
+ }
168
+ // Real directory or file — don't touch (could be npm-managed).
169
+ continue;
170
+ }
171
+ fs.symlinkSync(target, linkPath);
172
+ created.push(name);
173
+ }
174
+ return { created, updated, kept };
175
+ }
176
+
105
177
  module.exports = {
106
178
  readWsconfig,
107
179
  writeWsconfig,
108
180
  applyWpmRoot,
109
181
  rebuildFederationTsconfig,
182
+ syncFederationSymlinks,
110
183
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ws-test-realm/admin-kit",
3
- "version": "0.2.4-ng16",
3
+ "version": "0.2.6-ng16",
4
4
  "description": "Workflow CLI + scaffolding for Wiresphere admin-modules workspaces (Angular 16 + native-federation line). Ships `ws-init-workspace`, `ws-modules` (build+deploy driver), `ws-generate-module`/`ws-drop-module`, `ws-wire-host`, `ws-wire-pom`, `ws-sync-paths`, and `ws-purge`. Depends on @ws-test-realm/devkit (toolchain BOM) + @ws-test-realm/shared (runtime BOM + native-federation share map).",
5
5
  "license": "Artistic-2.0",
6
6
  "publishConfig": {
@@ -8,12 +8,30 @@ module.exports = withNativeFederation({
8
8
  // shared chunk for this package (declared in `shared` below). Without
9
9
  // the shim, esbuild would inline the whole library here and decorator
10
10
  // side effects would fire a second time on remote load.
11
- './Module': './src/exposed-module.ts',
11
+ './Module': './projects/__name__/src/exposed-module.ts',
12
12
  },
13
13
  // share() expands includeSecondaries against each package's exports field.
14
14
  // withNativeFederation's own share() invocation is commented out upstream.
15
- // Self-share this package so siblings consuming it via bare specifiers
16
- // resolve to the same singleton chunk at runtime.
17
- shared: { ...share(sharedDescriptors()), __name__: WORKSPACE_LIBS },
15
+ //
16
+ // Three flavors of entries below the share() spread:
17
+ //
18
+ // 1. Self-share `'__name__': WORKSPACE_LIBS` — emits a chunk + import-map
19
+ // entry for THIS module so siblings consuming it via bare specifiers
20
+ // get the same instance at runtime.
21
+ //
22
+ // 2. Cross-workspace siblings (e.g. `'ws-framework': WORKSPACE_LIBS`) —
23
+ // add when this module imports from another admin module that lives
24
+ // in a different workspace. admin-kit symlinks `node_modules/<name>`
25
+ // to the fiddle's `.federation/<name>/` so esbuild can resolve.
26
+ //
27
+ // 3. Module-carried federation contributions — runtime libs THIS module
28
+ // introduces for the federation, that aren't in `sharedDescriptors()`'s
29
+ // default slice. Example: db-login carries `@auth0/angular-jwt`. The
30
+ // module's project package.json should list these as
31
+ // `peerDependencies` (not `dependencies`); ng-packagr leaves them as
32
+ // bare imports, native-federation emits the chunk + share entry, and
33
+ // the runtime singleton mechanism dedupes if other consumers ever
34
+ // import them.
35
+ shared: { ...share(sharedDescriptors()), '__name__': WORKSPACE_LIBS },
18
36
  skip: ['rxjs/ajax', 'rxjs/fetch', 'rxjs/testing', 'rxjs/webSocket'],
19
37
  });
@@ -2,8 +2,21 @@
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "../../out-tsc/app",
5
- "types": []
5
+ "strict": false,
6
+ "strictNullChecks": false,
7
+ "noImplicitAny": false,
8
+ "noImplicitReturns": false,
9
+ "useDefineForClassFields": false,
10
+ "skipLibCheck": true,
11
+ "skipDefaultLibCheck": true,
12
+ "types": ["node"]
6
13
  },
7
- "files": ["src/main.ts", "src/polyfills.ts"],
8
- "include": ["src/**/*.d.ts"]
14
+ "angularCompilerOptions": {
15
+ "strictTemplates": false,
16
+ "strictInjectionParameters": false,
17
+ "strictInputAccessModifiers": false
18
+ },
19
+ "files": ["src/main.ts"],
20
+ "include": ["src/**/*.ts"],
21
+ "exclude": ["src/**/*.spec.ts", "src/test.ts"]
9
22
  }