create-nodejs-fn 0.0.3 → 0.1.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.
package/README.md CHANGED
@@ -236,6 +236,9 @@ createNodejsFnPlugin({
236
236
 
237
237
  // Container port (default: 8080)
238
238
  containerPort: 8080,
239
+
240
+ // Duration before containers are put to sleep (default: "10s")
241
+ sleepAfter: "30s",
239
242
 
240
243
  // External dependencies to install in container
241
244
  external: ["@napi-rs/canvas", "sharp"],
@@ -252,6 +255,10 @@ createNodejsFnPlugin({
252
255
  preInstallCommands: [],
253
256
  postInstallCommands: [],
254
257
  env: { MY_VAR: "value" },
258
+ // Run as a non-root user inside the container
259
+ user: { name: "app", uid: 1000, gid: 1000 },
260
+ // Or replace everything above with a fully custom Dockerfile
261
+ // dockerfilePath: "./containers/native.Dockerfile",
255
262
  },
256
263
 
257
264
  // Environment variables to pass from Worker to Container
@@ -265,6 +272,9 @@ createNodejsFnPlugin({
265
272
  });
266
273
  ```
267
274
 
275
+ - `docker.user` lets you switch the runtime to a non-root user after installs while keeping generated paths (`/app`) writable.
276
+ - To own the entire build, supply `docker: { dockerfilePath: "./containers/native.Dockerfile" }`. The type prevents mixing this with other docker options so you don't accidentally combine incompatible settings. If the custom Dockerfile doesn't already start `server.mjs`, the generator will append `CMD ["node", "./server.mjs"]` to the end.
277
+
268
278
  ---
269
279
 
270
280
  ## 🏗️ Internal Architecture
package/dist/index.d.mts CHANGED
@@ -1,19 +1,38 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- type DockerOptions = {
3
+ type DockerUser = {
4
+ name: string;
5
+ uid?: number;
6
+ gid?: number;
7
+ };
8
+ type GeneratedDockerOptions = {
4
9
  baseImage?: string;
5
10
  systemPackages?: string[];
6
11
  preInstallCommands?: string[];
7
12
  postInstallCommands?: string[];
8
13
  env?: Record<string, string>;
9
14
  extraLines?: string[];
15
+ /**
16
+ * Create and switch to a non-root runtime user.
17
+ * Package installs still run as root; the user is applied before CMD.
18
+ */
19
+ user?: DockerUser;
10
20
  };
21
+ type CustomDockerfileOption = {
22
+ /** Use a fully custom Dockerfile (path resolved from project root). */
23
+ dockerfilePath: string;
24
+ };
25
+ type DockerOptions = GeneratedDockerOptions | CustomDockerfileOption;
11
26
  type Opts = {
12
27
  files?: string[];
13
28
  generatedDir?: string;
14
29
  binding?: string;
15
30
  className?: string;
16
31
  containerPort?: number;
32
+ /**
33
+ * Duration before containers are put to sleep. Accepts the Cloudflare duration string format (e.g. "10s", "5m").
34
+ */
35
+ sleepAfter?: string;
17
36
  external?: string[];
18
37
  docker?: DockerOptions;
19
38
  /**
package/dist/index.mjs CHANGED
@@ -63,6 +63,9 @@ function collectExternalDeps(rootPkgPath, needed) {
63
63
  dependencies: out
64
64
  };
65
65
  }
66
+ function isCustomDockerfile(opts) {
67
+ return Boolean(opts && "dockerfilePath" in opts && typeof opts.dockerfilePath === "string");
68
+ }
66
69
  async function buildContainerServer(opts) {
67
70
  const { mods, outBaseAbs, dockerOpts, containerPort, external, root } = opts;
68
71
  ensureDir(outBaseAbs);
@@ -139,12 +142,35 @@ http.createServer((req, res) => {
139
142
  `
140
143
  );
141
144
  }
145
+ if (isCustomDockerfile(dockerOpts)) {
146
+ const customPath = path.resolve(root, dockerOpts.dockerfilePath);
147
+ if (!fs.existsSync(customPath)) {
148
+ throw new Error(
149
+ `[create-nodejs-fn] Custom Dockerfile not found: ${dockerOpts.dockerfilePath} (resolved to ${customPath})`
150
+ );
151
+ }
152
+ let customDockerfile = fs.readFileSync(customPath, "utf8");
153
+ if (!/server\.mjs/.test(customDockerfile) || !/\b(CMD|ENTRYPOINT)\b/.test(customDockerfile)) {
154
+ const trimmed = customDockerfile.replace(/\s*$/, "");
155
+ const suffix = [
156
+ trimmed,
157
+ "",
158
+ "# create-nodejs-fn runtime start",
159
+ 'CMD ["node", "./server.mjs"]',
160
+ ""
161
+ ].join("\n");
162
+ customDockerfile = suffix;
163
+ }
164
+ writeFileIfChanged(dockerfile, customDockerfile);
165
+ return;
166
+ }
142
167
  const {
143
168
  baseImage = "node:20-slim",
144
169
  systemPackages = [],
145
170
  preInstallCommands = [],
146
171
  postInstallCommands = [],
147
- env: dockerEnv = {}
172
+ env: dockerEnv = {},
173
+ user
148
174
  } = dockerOpts ?? {};
149
175
  const installLines = "RUN corepack enable && pnpm install --prod --no-frozen-lockfile";
150
176
  const sysDeps = systemPackages.length > 0 ? [
@@ -158,6 +184,13 @@ http.createServer((req, res) => {
158
184
  "ENV NODE_ENV=production",
159
185
  ...Object.entries(dockerEnv).map(([k, v]) => `ENV ${k}=${JSON.stringify(v ?? "")}`)
160
186
  ];
187
+ const userLines = user && user.name ? [
188
+ "# Runtime user (from plugin options)",
189
+ `RUN groupadd --system${user.gid ? ` --gid ${user.gid}` : ""} ${user.name} \\`,
190
+ ` && useradd --system --create-home --no-log-init --home-dir /home/${user.name} --gid ${user.name}${user.uid ? ` --uid ${user.uid}` : ""} ${user.name}`,
191
+ `RUN mkdir -p /app && chown -R ${user.name}:${user.name} /app`,
192
+ `USER ${user.name}`
193
+ ] : [];
161
194
  writeFileIfChanged(
162
195
  dockerfile,
163
196
  [
@@ -173,6 +206,7 @@ http.createServer((req, res) => {
173
206
  "COPY ./server.mjs ./server.mjs",
174
207
  ...envLines,
175
208
  ...postRuns,
209
+ ...userLines,
176
210
  `EXPOSE ${containerPort}`,
177
211
  `CMD ["node", "./server.mjs"]`,
178
212
  ""
@@ -529,6 +563,7 @@ function generateWorkerFiles(opts) {
529
563
  binding,
530
564
  className,
531
565
  containerPort,
566
+ sleepAfter,
532
567
  workerEnvVars,
533
568
  clientFileName,
534
569
  doFileName,
@@ -652,7 +687,7 @@ ${envVarEntries.map(([key, src]) => ` ${JSON.stringify(key)}: env[${JSON.string
652
687
  extends: "Container",
653
688
  properties: [
654
689
  { name: "defaultPort", initializer: String(containerPort) },
655
- { name: "sleepAfter", initializer: JSON.stringify("10s") },
690
+ { name: "sleepAfter", initializer: JSON.stringify(sleepAfter) },
656
691
  ...envVarInitializer ? [{ name: "envVars", initializer: envVarInitializer }] : []
657
692
  ]
658
693
  });
@@ -718,6 +753,7 @@ function createNodejsFnPlugin(opts = {}) {
718
753
  const binding = opts.binding ?? "NODEJS_FN";
719
754
  const className = opts.className ?? "NodejsFnContainer";
720
755
  const containerPort = opts.containerPort ?? 8080;
756
+ const sleepAfter = opts.sleepAfter ?? "10s";
721
757
  const external = opts.external ?? [];
722
758
  const docker = opts.docker ?? {};
723
759
  const workerEnvVars = opts.workerEnvVars ?? [];
@@ -819,6 +855,7 @@ function createNodejsFnPlugin(opts = {}) {
819
855
  binding,
820
856
  className,
821
857
  containerPort,
858
+ sleepAfter,
822
859
  workerEnvVars,
823
860
  clientFileName: GENERATED_FILENAMES.client,
824
861
  doFileName: GENERATED_FILENAMES.durableObject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nodejs-fn",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "description": "Vite plugin to enable calling Node.js-dependent functions directly from Cloudflare Workers!!",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",