pepr 1.0.8 → 1.1.0

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/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "!src/fixtures/**",
17
17
  "!dist/**/*.test.d.ts*"
18
18
  ],
19
- "version": "1.0.8",
19
+ "version": "1.1.0",
20
20
  "main": "dist/lib.js",
21
21
  "types": "dist/lib.d.ts",
22
22
  "scripts": {
@@ -34,7 +34,7 @@
34
34
  "format:prettier": "prettier --config config/.prettierrc src integration/cli/**/*.ts integration/helpers/**/*.ts",
35
35
  "format:src": "eslint --config config/eslint.root.config.mjs 'src/**/*.ts' --ignore-pattern '**/*.test.ts' --ignore-pattern 'src/templates/**'",
36
36
  "format:tests": "eslint --config config/eslint.test.config.mjs 'src/**/*.test.ts'",
37
- "gen-data-json": "node hack/build-template-data.js",
37
+ "gen-data-json": "node hack/build-template-data.mjs",
38
38
  "prebuild": "rm -fr dist/* && npm run gen-data-json",
39
39
  "prepare": "if [ \"$NODE_ENV\" != 'production' ]; then husky; fi",
40
40
  "set-version": "bash -lc 'ver=${PEPR_VERSION:-}; if [ -z \"$ver\" ]; then git fetch --tags --force; ver=$(git describe --tags --abbrev=0); fi; ver=${ver#v}; echo Using version $ver; node ./scripts/set-version.js \"$ver\"'",
@@ -51,14 +51,14 @@
51
51
  "dependencies": {
52
52
  "@types/ramda": "0.31.1",
53
53
  "command-line-args": "^6.0.1",
54
- "commander": "14.0.2",
54
+ "commander": "14.0.3",
55
55
  "express": "5.2.1",
56
56
  "fast-json-patch": "3.1.1",
57
57
  "heredoc": "^1.3.1",
58
58
  "http-status-codes": "^2.3.0",
59
59
  "json-pointer": "^0.6.2",
60
- "kubernetes-fluent-client": "3.11.0",
61
- "pino": "10.3.0",
60
+ "kubernetes-fluent-client": "3.11.2",
61
+ "pino": "10.3.1",
62
62
  "pino-pretty": "13.1.3",
63
63
  "prom-client": "15.1.3",
64
64
  "quicktype-core": "^23.2.6",
@@ -66,8 +66,8 @@
66
66
  "ts-morph": "^27.0.0"
67
67
  },
68
68
  "devDependencies": {
69
- "@commitlint/cli": "20.4.0",
70
- "@commitlint/config-conventional": "20.4.0",
69
+ "@commitlint/cli": "20.4.1",
70
+ "@commitlint/config-conventional": "20.4.1",
71
71
  "@semantic-release/commit-analyzer": "^13.0.1",
72
72
  "@semantic-release/git": "^10.0.1",
73
73
  "@semantic-release/npm": "^13.0.0",
@@ -87,9 +87,10 @@
87
87
  "globals": "^17.0.0",
88
88
  "husky": "^9.1.6",
89
89
  "js-yaml": "^4.1.0",
90
+ "selfsigned": "^5.5.0",
90
91
  "shellcheck": "^4.1.0",
91
92
  "tsx": "^4.20.3",
92
- "undici": "7.19.2",
93
+ "undici": "7.21.0",
93
94
  "vitest": "^4.0.4"
94
95
  },
95
96
  "overrides": {
@@ -7,7 +7,7 @@ import { validateCapabilityNames } from "../../lib/helpers";
7
7
  import { BuildOptions, context, BuildContext } from "esbuild";
8
8
  import { Assets } from "../../lib/assets/assets";
9
9
  import { resolve } from "path";
10
- import { promises as fs, accessSync, constants, statSync } from "fs";
10
+ import { promises as fs, accessSync, constants, statSync, readFileSync } from "fs";
11
11
  import { generateAllYaml } from "../../lib/assets/yaml/generateAllYaml";
12
12
  import { webhookConfigGenerator } from "../../lib/assets/webhooks";
13
13
  import { generateZarfYamlGeneric } from "../../lib/assets/yaml/generateZarfYaml";
@@ -42,6 +42,31 @@ export function fileExists(entryPoint: string = "pepr.ts"): string {
42
42
  return entryPoint;
43
43
  }
44
44
 
45
+ /**
46
+ * Determine the module format based on package.json "type" field.
47
+ *
48
+ * @param packageJsonPath - Path to the package.json file for auto-detection
49
+ * @returns "cjs" or "esm"
50
+ */
51
+ export function determineModuleFormat(packageJsonPath: string): "cjs" | "esm" {
52
+ try {
53
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
54
+ if (pkg.type === "module") {
55
+ return "esm";
56
+ }
57
+ if (pkg.type !== "commonjs") {
58
+ // No type field specified - warn user
59
+ Log.warn(
60
+ "No 'type' field specified in package.json. Defaulting to CommonJS format. To use ES modules, add '\"type\": \"module\"' to your tsconfig.json or package.json.",
61
+ );
62
+ }
63
+ } catch {
64
+ // If can't read package.json, default to cjs
65
+ }
66
+
67
+ return "cjs";
68
+ }
69
+
45
70
  interface ImageOptions {
46
71
  customImage?: string;
47
72
  registryInfo?: string;
@@ -17,6 +17,14 @@ export type BuildModuleReturn = {
17
17
  cfg: PeprConfig;
18
18
  uuid: string;
19
19
  };
20
+
21
+ export interface BuildModuleOptions {
22
+ reloader?: Reloader;
23
+ entryPoint?: string;
24
+ embed?: boolean;
25
+ format?: "cjs" | "esm";
26
+ }
27
+
20
28
  // Create a list of external libraries to exclude from the bundle, these are already stored in the container
21
29
  const externalLibs = Object.keys(dependencies);
22
30
 
@@ -26,29 +34,59 @@ externalLibs.push("pepr");
26
34
  // Add the kubernetes client to the list of external libraries as it is pulled in by kubernetes-fluent-client
27
35
  externalLibs.push("@kubernetes/client-node");
28
36
 
37
+ /**
38
+ * Resolve the esbuild output format and file path for the build.
39
+ *
40
+ * Embedded builds are fork()ed by loadCapabilities() to extract capabilities, so they must
41
+ * use CJS format. When the user's package has "type": "module", Node.js treats .js files as
42
+ * ESM regardless of their content, so we also rename the output to .cjs for correct parsing.
43
+ */
44
+ function resolveFormatAndPath(
45
+ embed: boolean,
46
+ requestedFormat: "cjs" | "esm",
47
+ loadedPath: string,
48
+ ): { format: "cjs" | "esm"; path: string } {
49
+ if (embed) {
50
+ return {
51
+ format: "cjs",
52
+ path: requestedFormat === "esm" ? loadedPath.replace(/\.js$/, ".cjs") : loadedPath,
53
+ };
54
+ }
55
+ return { format: requestedFormat, path: loadedPath };
56
+ }
57
+
29
58
  export async function buildModule(
30
59
  outputDir: string,
31
- reloader?: Reloader,
32
- entryPoint = "pepr.ts",
33
- embed = true,
60
+ options: BuildModuleOptions = {},
34
61
  ): Promise<BuildModuleReturn | void> {
62
+ const {
63
+ reloader,
64
+ entryPoint = "pepr.ts",
65
+ embed = true,
66
+ format: requestedFormat = "cjs",
67
+ } = options;
68
+
35
69
  try {
36
- const { cfg, modulePath, path, uuid } = await loadModule(outputDir, entryPoint);
70
+ const { cfg, modulePath, path: loadedPath, uuid } = await loadModule(outputDir, entryPoint);
71
+ const { format, path } = resolveFormatAndPath(embed, requestedFormat, loadedPath);
37
72
 
38
73
  await checkFormat();
39
- // Resolve node_modules folder (in support of npm workspaces!)
40
- const npmRoot = execFileSync("npm", ["root"]).toString().trim();
41
74
 
42
- // Run `tsc` to validate the module's types & output sourcemaps
43
- const args = ["--project", `${modulePath}/tsconfig.json`, "--outdir", outputDir];
44
- execFileSync(`${npmRoot}/.bin/tsc`, args);
75
+ // Resolve node_modules folder (in support of npm workspaces!) and run tsc
76
+ const npmRoot = execFileSync("npm", ["root"]).toString().trim();
77
+ execFileSync(`${npmRoot}/.bin/tsc`, [
78
+ "--project",
79
+ `${modulePath}/tsconfig.json`,
80
+ "--outdir",
81
+ outputDir,
82
+ ]);
45
83
 
46
84
  // Common build options for all builds
47
85
  const ctxCfg: BuildOptions = {
48
86
  bundle: true,
49
87
  entryPoints: [entryPoint],
50
88
  external: externalLibs,
51
- format: "cjs",
89
+ format: format,
52
90
  keepNames: true,
53
91
  legalComments: "external",
54
92
  metafile: true,
@@ -87,8 +125,10 @@ export async function buildModule(
87
125
  // Don't minify
88
126
  ctxCfg.minify = false;
89
127
 
90
- // Preserve the original file name
91
- ctxCfg.outfile = resolve(outputDir, basename(entryPoint, extname(entryPoint))) + ".js";
128
+ // Preserve the original file name with appropriate extension for format
129
+ const outputExtension = format === "esm" ? ".mjs" : ".js";
130
+ ctxCfg.outfile =
131
+ resolve(outputDir, basename(entryPoint, extname(entryPoint))) + outputExtension;
92
132
 
93
133
  // Don't bundle
94
134
  ctxCfg.packages = "external";
@@ -7,6 +7,7 @@ import { Option } from "commander";
7
7
  import { parseTimeout } from "../../lib/helpers";
8
8
  import {
9
9
  determineRbacMode,
10
+ determineModuleFormat,
10
11
  assignImage,
11
12
  createOutputDirectory,
12
13
  handleValidCapabilityNames,
@@ -15,8 +16,79 @@ import {
15
16
  generateYamlAndWriteToDisk,
16
17
  fileExists,
17
18
  } from "./build.helpers";
18
- import { buildModule } from "./buildModule";
19
+ import { buildModule, BuildModuleReturn } from "./buildModule";
19
20
  import Log from "../../lib/telemetry/logger";
21
+ import { resolve } from "path";
22
+
23
+ interface BuildOpts {
24
+ customName?: string;
25
+ customImage?: string;
26
+ registryInfo?: string;
27
+ registry?: string;
28
+ rbacMode?: string;
29
+ timeout?: number;
30
+ embed: boolean;
31
+ withPullSecret: string;
32
+ zarf: string;
33
+ }
34
+
35
+ async function generateDeploymentAssets(
36
+ buildResult: BuildModuleReturn,
37
+ opts: BuildOpts,
38
+ outputDir: string,
39
+ ): Promise<Assets> {
40
+ const { cfg, path } = buildResult;
41
+
42
+ const image = assignImage({
43
+ customImage: opts.customImage,
44
+ registryInfo: opts.registryInfo,
45
+ peprVersion: cfg.pepr.peprVersion,
46
+ registry: opts.registry,
47
+ });
48
+
49
+ if (opts.timeout !== undefined) {
50
+ cfg.pepr.webhookTimeout = opts.timeout;
51
+ }
52
+
53
+ if (opts.registryInfo !== undefined) {
54
+ Log.info(`Including ${cfg.pepr.includedFiles.length} files in controller image.`);
55
+ await handleCustomImageBuild(
56
+ cfg.pepr.includedFiles,
57
+ cfg.pepr.peprVersion,
58
+ cfg.description,
59
+ image,
60
+ );
61
+ }
62
+
63
+ const assets = new Assets(
64
+ {
65
+ ...cfg.pepr,
66
+ appVersion: cfg.version,
67
+ description: cfg.description,
68
+ alwaysIgnore: { namespaces: cfg.pepr.alwaysIgnore?.namespaces },
69
+ rbacMode: determineRbacMode(opts, cfg),
70
+ },
71
+ path,
72
+ opts.withPullSecret === "" ? [] : [opts.withPullSecret],
73
+ );
74
+
75
+ if (image !== "") assets.image = image;
76
+
77
+ if (!validImagePullSecret(opts.withPullSecret)) {
78
+ throw new Error("Invalid imagePullSecret. Please provide a valid name as defined in RFC 1123.");
79
+ }
80
+
81
+ handleValidCapabilityNames(assets.capabilities);
82
+ await generateYamlAndWriteToDisk({
83
+ uuid: cfg.pepr.uuid,
84
+ outputDir,
85
+ imagePullSecret: opts.withPullSecret,
86
+ zarf: opts.zarf,
87
+ assets,
88
+ });
89
+
90
+ return assets;
91
+ }
20
92
 
21
93
  export default function (program: Command): void {
22
94
  program
@@ -77,83 +149,29 @@ export default function (program: Command): void {
77
149
  )
78
150
  .action(async opts => {
79
151
  const outputDir = await createOutputDirectory(opts.output);
152
+ const format = determineModuleFormat(resolve(process.cwd(), "package.json"));
80
153
 
81
- // Build the module
82
- const buildModuleResult = await buildModule(
83
- outputDir,
84
- undefined,
85
- opts.entryPoint,
86
- opts.embed,
87
- );
88
-
89
- const { cfg, path } = buildModuleResult!;
90
- // override the name if provided
91
- if (opts.customName) {
92
- process.env.PEPR_CUSTOM_BUILD_NAME = opts.customName;
93
- }
94
-
95
- const image = assignImage({
96
- customImage: opts.customImage,
97
- registryInfo: opts.registryInfo,
98
- peprVersion: cfg.pepr.peprVersion,
99
- registry: opts.registry,
154
+ const buildModuleResult = await buildModule(outputDir, {
155
+ entryPoint: opts.entryPoint,
156
+ embed: opts.embed,
157
+ format,
100
158
  });
101
159
 
102
- // Check if there is a custom timeout defined
103
- if (opts.timeout !== undefined) {
104
- cfg.pepr.webhookTimeout = opts.timeout;
160
+ if (!buildModuleResult) {
161
+ return;
105
162
  }
106
163
 
107
- if (opts.registryInfo !== undefined) {
108
- Log.info(`Including ${cfg.pepr.includedFiles.length} files in controller image.`);
164
+ const { path } = buildModuleResult;
109
165
 
110
- // only actually build/push if there are files to include
111
- await handleCustomImageBuild(
112
- cfg.pepr.includedFiles,
113
- cfg.pepr.peprVersion,
114
- cfg.description,
115
- image,
116
- );
166
+ if (opts.customName) {
167
+ process.env.PEPR_CUSTOM_BUILD_NAME = opts.customName;
117
168
  }
118
169
 
119
- // If building without embedding, exit after building
120
170
  if (!opts.embed) {
121
171
  Log.info(`Module built successfully at ${path}`);
122
172
  return;
123
173
  }
124
174
 
125
- // Generate a secret for the module
126
- const assets = new Assets(
127
- {
128
- ...cfg.pepr,
129
- appVersion: cfg.version,
130
- description: cfg.description,
131
- alwaysIgnore: {
132
- namespaces: cfg.pepr.alwaysIgnore?.namespaces,
133
- },
134
- // Can override the rbacMode with the CLI option
135
- rbacMode: determineRbacMode(opts, cfg),
136
- },
137
- path,
138
- opts.withPullSecret === "" ? [] : [opts.withPullSecret],
139
- );
140
-
141
- if (image !== "") assets.image = image;
142
-
143
- // Ensure imagePullSecret is valid
144
- if (!validImagePullSecret(opts.withPullSecret)) {
145
- throw new Error(
146
- "Invalid imagePullSecret. Please provide a valid name as defined in RFC 1123.",
147
- );
148
- }
149
-
150
- handleValidCapabilityNames(assets.capabilities);
151
- await generateYamlAndWriteToDisk({
152
- uuid: cfg.pepr.uuid,
153
- outputDir,
154
- imagePullSecret: opts.withPullSecret,
155
- zarf: opts.zarf,
156
- assets,
157
- });
175
+ await generateDeploymentAssets(buildModuleResult, opts, outputDir);
158
176
  });
159
177
  }
@@ -7,7 +7,7 @@ import { CapabilityExport } from "../../lib/types";
7
7
  import { buildModule } from "../build/buildModule";
8
8
 
9
9
  export async function buildAndDeployModule(image: string, force: boolean): Promise<void> {
10
- const builtModule = await buildModule("dist");
10
+ const builtModule = await buildModule("dist", {});
11
11
  if (!builtModule) {
12
12
  return;
13
13
  }
package/src/cli/dev.ts CHANGED
@@ -105,18 +105,20 @@ export default function (program: Command): void {
105
105
  });
106
106
  };
107
107
 
108
- await buildModule("dist", async r => {
109
- if (r.errors.length > 0) {
110
- Log.error(`Error compiling module: ${r.errors}`);
111
- return;
112
- }
108
+ await buildModule("dist", {
109
+ reloader: async r => {
110
+ if (r.errors.length > 0) {
111
+ Log.error(`Error compiling module: ${r.errors}`);
112
+ return;
113
+ }
113
114
 
114
- if (program) {
115
- program.once("exit", runFork);
116
- program.kill("SIGKILL");
117
- } else {
118
- await runFork();
119
- }
115
+ if (program) {
116
+ program.once("exit", runFork);
117
+ program.kill("SIGKILL");
118
+ } else {
119
+ await runFork();
120
+ }
121
+ },
120
122
  });
121
123
  } catch (e) {
122
124
  Log.error(`Error deploying module:`, e);
@@ -225,6 +225,7 @@ export type WatchEventArgs<K extends WatchEvent, T extends GenericClass> = {
225
225
  [WatchEvent.RECONNECT_PENDING]: undefined;
226
226
  [WatchEvent.DATA]: undefined;
227
227
  [WatchEvent.INC_RESYNC_FAILURE_COUNT]: number;
228
+ [WatchEvent.WATCH_ERROR]: Error;
228
229
  }[K];
229
230
 
230
231
  export type LogEventFunction = (event: WatchEvent, message?: string) => void;
@@ -245,6 +246,7 @@ export function registerWatchEventHandlers(
245
246
  { cause: err },
246
247
  );
247
248
  },
249
+ [WatchEvent.WATCH_ERROR]: err => logEvent(WatchEvent.WATCH_ERROR, err.message),
248
250
  [WatchEvent.CONNECT]: url => logEvent(WatchEvent.CONNECT, url),
249
251
  [WatchEvent.DATA_ERROR]: err => logEvent(WatchEvent.DATA_ERROR, err.message),
250
252
  [WatchEvent.RECONNECT]: retryCount =>
@@ -6,8 +6,8 @@
6
6
  "emitDeclarationOnly": true,
7
7
  "esModuleInterop": true,
8
8
  "lib": ["ES2022"],
9
- "module": "NodeNext",
10
- "moduleResolution": "NodeNext",
9
+ "module": "es2022",
10
+ "moduleResolution": "bundler",
11
11
  "outDir": "dist",
12
12
  "resolveJsonModule": true,
13
13
  "rootDir": ".",
@@ -1,30 +0,0 @@
1
- {
2
- "name": "pepr-dev-module",
3
- "description": "static test package.json for local pepr core development",
4
- "pepr": {
5
- "name": "Development Module",
6
- "uuid": "20e17cf6-a2e4-46b2-b626-75d88d96c88b",
7
- "description": "Development module for pepr",
8
- "onError": "reject",
9
- "logLevel": "debug",
10
- "customLabels": {
11
- "namespace": {
12
- "pepr.dev": ""
13
- }
14
- },
15
- "alwaysIgnore": {
16
- "namespaces": []
17
- },
18
- "admission": {
19
- "alwaysIgnore": {
20
- "namespaces": []
21
- }
22
- },
23
- "watcher": {
24
- "alwaysIgnore": {
25
- "namespaces": []
26
- }
27
- },
28
- "includedFiles": []
29
- }
30
- }