@ws-test-realm/admin-kit 0.1.11 → 0.1.14

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/hooks/ngcc.js ADDED
@@ -0,0 +1,41 @@
1
+ // Built-in federation postInstall hook: run ngcc against the federation
2
+ // node_modules tree so every @angular package gets `__processed_by_ivy_ngcc__`
3
+ // markers and class definitions rewritten in place (ɵcmp/ɵdir/etc).
4
+ //
5
+ // Producers reference this hook from their lib's package.json:
6
+ //
7
+ // "wsmodules": {
8
+ // "federationHooks": { "postInstall": "@ws-test-realm/admin-kit/hooks/ngcc" }
9
+ // }
10
+ //
11
+ // The hook resolves `@angular/compiler-cli/ngcc` from the PRODUCER workspace
12
+ // (where compiler-cli is installed as a dev dep, alongside ng-cli, etc.).
13
+ // The federation tree itself only holds runtime peer deps and won't have
14
+ // compiler-cli unless a lib explicitly declares it.
15
+
16
+ const Module = require("module");
17
+ const path = require("path");
18
+
19
+ module.exports = function ngccHook({ federationDir, producerWorkspaceDir, libName }) {
20
+ const requireFromProducer = Module.createRequire(
21
+ path.join(producerWorkspaceDir, "package.json")
22
+ );
23
+ let ngcc;
24
+ try {
25
+ ngcc = requireFromProducer("@angular/compiler-cli/ngcc");
26
+ } catch (e) {
27
+ console.warn(
28
+ `\x1b[33mWarning:\x1b[0m ngcc hook (${libName}): couldn't load @angular/compiler-cli/ngcc from ${producerWorkspaceDir}: ${e.message}`
29
+ );
30
+ return;
31
+ }
32
+
33
+ const basePath = path.join(federationDir, "node_modules");
34
+ console.log(` ngcc: rewriting ${basePath}`);
35
+ ngcc.process({
36
+ basePath,
37
+ propertiesToConsider: ["module_ivy_ngcc", "browser", "module", "main"],
38
+ compileAllFormats: false,
39
+ async: false,
40
+ });
41
+ };
@@ -7,27 +7,17 @@ const { loadDeployContext, resolveTarget } = require("./target-resolution");
7
7
  const { promotePeerDeps, syncFederationWorkspace } = require("./federation-peers");
8
8
 
9
9
  function copyTypes(distLib, hostFederationDir) {
10
- // Preserve any existing node_modules dir under the staged location —
11
- // npm's workspace install populated it and the next syncFederationWorkspace
12
- // run will reconcile. Replacing it on every type-copy would force a
13
- // fresh per-lib install instead of letting npm hoist across siblings.
14
- const preservedNodeModules = path.join(hostFederationDir, "node_modules");
15
- let cached = null;
16
- if (fs.existsSync(preservedNodeModules)) {
17
- cached = preservedNodeModules + `.preserve-${process.pid}`;
18
- fs.renameSync(preservedNodeModules, cached);
19
- }
20
10
  if (fs.existsSync(hostFederationDir)) {
21
11
  fs.rmSync(hostFederationDir, { recursive: true, force: true });
22
12
  }
23
13
  fs.mkdirSync(hostFederationDir, { recursive: true });
24
14
  fs.cpSync(distLib, hostFederationDir, { recursive: true });
25
- if (cached) {
26
- fs.renameSync(cached, preservedNodeModules);
27
- }
28
15
  // Promote peerDependencies → dependencies in the staged package.json so
29
16
  // npm's workspace install actually installs them. The producer's source
30
17
  // package.json is untouched; this only affects the host-staged copy.
18
+ // syncFederationWorkspace runs once at the end of the deploy loop and
19
+ // re-establishes <federationDir>/node_modules from scratch, so we don't
20
+ // need to preserve any prior node_modules under <id>/.
31
21
  promotePeerDeps(hostFederationDir);
32
22
  }
33
23
 
@@ -179,7 +169,7 @@ function deployRemotes({ workspaceDir, restrictTo = [], withDeps = false }) {
179
169
  // conflicts. Consumers' TypeScript walks up from .federation/<id>/ and
180
170
  // resolves transitive types without consumer-side dep management.
181
171
  try {
182
- syncFederationWorkspace(ctx.adminDir);
172
+ syncFederationWorkspace(ctx.adminDir, workspaceDir);
183
173
  } catch (e) {
184
174
  console.warn(
185
175
  `\x1b[33mWarning:\x1b[0m federation peer install failed: ${e.message}`
@@ -1,5 +1,6 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
+ const Module = require("module");
3
4
  const { execSync } = require("child_process");
4
5
 
5
6
  // Mirror a federation lib's peerDependencies into its dependencies in the
@@ -22,13 +23,19 @@ function promotePeerDeps(libDir) {
22
23
  }
23
24
 
24
25
  // Reconcile the entire .federation/ dir as an npm workspace root. Lists
25
- // every lib subdir as a workspace, then runs `npm install` once npm
26
- // hoists shared peer versions to .federation/node_modules/ and nests only
27
- // where individual libs disagree, automatically scoping conflicts the way
28
- // npm normally would.
26
+ // every lib subdir as a workspace, runs `npm install` (npm hoists shared
27
+ // peer versions, nests only on per-lib disagreement), then runs each
28
+ // lib's declared postInstall hooks against the freshly installed tree.
29
+ //
30
+ // Hooks let a federation lib ship arbitrary prep work that consumers run
31
+ // automatically — e.g. ws-framework declares
32
+ // `wsmodules.federationHooks.postInstall = "@ws-test-realm/admin-kit/hooks/ngcc"`
33
+ // so the consumer's install pipeline Ivy-rewrites the Angular tree in
34
+ // place. admin-kit knows nothing about Angular here; the producer's
35
+ // declaration drives what runs.
29
36
  //
30
37
  // Idempotent. Re-runs at the end of every deploy.
31
- function syncFederationWorkspace(adminDir) {
38
+ function syncFederationWorkspace(adminDir, producerWorkspaceDir) {
32
39
  const federationDir = path.join(adminDir, ".federation");
33
40
  if (!fs.existsSync(federationDir)) return { installed: false };
34
41
 
@@ -45,9 +52,98 @@ function syncFederationWorkspace(adminDir) {
45
52
  cwd: federationDir,
46
53
  stdio: "inherit",
47
54
  });
55
+
56
+ runFederationHooks({ federationDir, libs, producerWorkspaceDir });
57
+
48
58
  return { installed: true, libs };
49
59
  }
50
60
 
61
+ // Walk each federation lib's package.json for declared postInstall hooks
62
+ // (`wsmodules.federationHooks.postInstall`, string or array). Dedupe by
63
+ // hook reference — multiple libs declaring the same hook (e.g. all
64
+ // Angular libs referencing the built-in ngcc hook) run it once. Each hook
65
+ // is a CommonJS module exporting a default function with signature
66
+ // `({ federationDir, libDir, libName, producerWorkspaceDir }) => void`.
67
+ //
68
+ // Hook reference resolution:
69
+ // - "./..." or "../..." → relative to the lib's federation dir
70
+ // - any other specifier → node `require` resolution, first trying
71
+ // admin-kit's own neighborhood (built-in hooks
72
+ // like @ws-test-realm/admin-kit/hooks/ngcc),
73
+ // then the producer workspace (third-party
74
+ // hook packages).
75
+ function runFederationHooks({ federationDir, libs, producerWorkspaceDir }) {
76
+ const queue = [];
77
+ const seen = new Set();
78
+ for (const libName of libs) {
79
+ const libDir = path.join(federationDir, libName);
80
+ const pkg = safeReadJson(path.join(libDir, "package.json"));
81
+ const refs = collectHookRefs(pkg);
82
+ for (const ref of refs) {
83
+ if (seen.has(ref)) continue;
84
+ seen.add(ref);
85
+ queue.push({ ref, libDir, libName });
86
+ }
87
+ }
88
+ if (!queue.length) return;
89
+
90
+ console.log(
91
+ `\n=== federation peers: postInstall hooks (${queue.length}) ===`
92
+ );
93
+ for (const { ref, libDir, libName } of queue) {
94
+ console.log(` ${libName} → ${ref}`);
95
+ let hookFn;
96
+ try {
97
+ hookFn = resolveHook({ ref, libDir, producerWorkspaceDir });
98
+ } catch (e) {
99
+ console.warn(
100
+ ` \x1b[33mWarning:\x1b[0m couldn't load hook (${e.message})`
101
+ );
102
+ continue;
103
+ }
104
+ if (typeof hookFn !== "function") {
105
+ console.warn(
106
+ ` \x1b[33mWarning:\x1b[0m hook ${ref} did not export a function`
107
+ );
108
+ continue;
109
+ }
110
+ try {
111
+ hookFn({ federationDir, libDir, libName, producerWorkspaceDir });
112
+ } catch (e) {
113
+ console.warn(
114
+ ` \x1b[33mWarning:\x1b[0m hook ${ref} threw: ${e.message}`
115
+ );
116
+ }
117
+ }
118
+ }
119
+
120
+ function collectHookRefs(pkg) {
121
+ if (!pkg || !pkg.wsmodules || !pkg.wsmodules.federationHooks) return [];
122
+ const v = pkg.wsmodules.federationHooks.postInstall;
123
+ if (!v) return [];
124
+ return Array.isArray(v) ? v : [v];
125
+ }
126
+
127
+ function resolveHook({ ref, libDir, producerWorkspaceDir }) {
128
+ let resolvedPath;
129
+ if (ref.startsWith("./") || ref.startsWith("../")) {
130
+ resolvedPath = path.resolve(libDir, ref);
131
+ } else {
132
+ const adminKitRequire = Module.createRequire(
133
+ path.join(__dirname, "..", "package.json")
134
+ );
135
+ try {
136
+ resolvedPath = adminKitRequire.resolve(ref);
137
+ } catch {
138
+ const producerRequire = Module.createRequire(
139
+ path.join(producerWorkspaceDir, "package.json")
140
+ );
141
+ resolvedPath = producerRequire.resolve(ref);
142
+ }
143
+ }
144
+ return require(resolvedPath);
145
+ }
146
+
51
147
  function listFederationLibs(federationDir) {
52
148
  return fs
53
149
  .readdirSync(federationDir, { withFileTypes: true })
@@ -73,6 +169,14 @@ function writeWorkspaceManifest(federationDir, libs) {
73
169
  );
74
170
  }
75
171
 
172
+ function safeReadJson(file) {
173
+ try {
174
+ return JSON.parse(fs.readFileSync(file, "utf8"));
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+
76
180
  module.exports = {
77
181
  promotePeerDeps,
78
182
  syncFederationWorkspace,
@@ -127,7 +127,7 @@ function purgeRemotes({
127
127
  // claiming workspace slots).
128
128
  if (actions.some((a) => a.typesRemoved)) {
129
129
  try {
130
- syncFederationWorkspace(ctx.adminDir);
130
+ syncFederationWorkspace(ctx.adminDir, workspaceDir);
131
131
  } catch (e) {
132
132
  console.warn(
133
133
  `\x1b[33mWarning:\x1b[0m federation peer resync failed: ${e.message}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ws-test-realm/admin-kit",
3
- "version": "0.1.11",
3
+ "version": "0.1.14",
4
4
  "description": "Workflow CLI + scaffolding for Wiresphere admin-modules workspaces. Ships `ws-init-workspace` (init/merge into a project, stamps @wiresphere/shared's `getOverrides()` into the workspace's npm overrides block), `ws-modules` (build/pack/deploy driver), `ws-generate-module`/`ws-drop-module`, `ws-wire-host`, `ws-wire-pom`, `ws-sync-paths`, and `ws-pack-remote`. Depends on @wiresphere/devkit (toolchain BOM, peerDeps); pairs with @wiresphere/shared (runtime BOM + MF share map + overrides manifest). Requires npm 9+ for overrides + peerDep resolution to coexist cleanly.",
5
5
  "license": "Artistic-2.0",
6
6
  "publishConfig": {
@@ -21,6 +21,7 @@
21
21
  "files": [
22
22
  "bin",
23
23
  "lib",
24
+ "hooks",
24
25
  "template",
25
26
  "template-module",
26
27
  "README.md"