Last active
July 26, 2022 18:10
-
-
Save dmohs/13e2ea044707b77ff5d2af1a1c8585f4 to your computer and use it in GitHub Desktop.
Executable to run extensionless scripts as a Node module
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!nodem | |
// vim: set syntax=javascript : | |
foo // undefined, check reported line number |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <libgen.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
int len(char *x[]) { | |
for(int i = 0; ; ++i) { | |
if (x[i] == NULL) return i; | |
} | |
} | |
void concat(char *r[], char *a[], char *b[]) { | |
int ri = 0; | |
for (int i = 0; i < len(a); ++i, ++ri) { | |
r[ri] = a[i]; | |
} | |
for (int i = 0; i < len(b); ++i, ++ri) { | |
r[ri] = b[i]; | |
} | |
r[ri] = NULL; | |
} | |
char *mallocAndReadFile(char *path) { | |
FILE *f = fopen(path, "rb"); | |
if (f == NULL) { return NULL; } // perror() for error message | |
fseek(f, 0, SEEK_END); | |
long fsize = ftell(f); | |
fseek(f, 0, SEEK_SET); /* same as rewind(f); */ | |
char *contents = malloc(fsize + 1); | |
fread(contents, fsize, 1, f); | |
fclose(f); | |
contents[fsize] = 0; | |
return contents; | |
} | |
int main(int argc, char *argv[]) { | |
if (len(argv) < 2) { | |
fprintf(stderr, "usage: %s script [arg ...]\n", argv[0]); | |
return 1; | |
} | |
char *scriptPath = argv[1]; | |
char *contents = mallocAndReadFile(scriptPath); | |
if (contents == NULL) { | |
perror(scriptPath); | |
return 2; | |
} | |
// Node chokes on the shebang line, so advance past it. Since we're inserting one line, the | |
// error reporting should remain accurate. | |
char *srcStart = contents; | |
char *firstNl = index(contents, '\n'); | |
if (strncmp(contents, "#!", 2) == 0 && firstNl != NULL) { | |
srcStart = firstNl + 1; | |
} | |
char *srcPrefix = "process.chdir(process.env.PWD);\n"; | |
long srcSize = strlen(srcPrefix) + strlen(srcStart) + 1; | |
char *src = malloc(srcSize); | |
snprintf(src, srcSize, "%s%s", srcPrefix, srcStart); | |
int pwdExprSize = 4 + strlen(getenv("PWD")) + 1; | |
char *pwdExpr = malloc(pwdExprSize); | |
snprintf(pwdExpr, pwdExprSize, "PWD=%s", getenv("PWD")); | |
char *envArgs[] = { | |
pwdExpr, | |
"node", "--input-type=module", | |
"-e", src, | |
NULL | |
}; | |
char *combinedArgs[len(envArgs) + len(argv) + 1]; | |
concat(combinedArgs, envArgs, &argv[1]); | |
if (chdir(dirname(scriptPath)) != 0) { | |
perror(dirname(scriptPath)); | |
return 3; | |
} | |
return execv("/usr/bin/env", combinedArgs); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!nodem | |
// vim: set syntax=javascript : | |
// I name the executable `nodem` for "node module" and have it in my path. | |
import * as fs from 'fs' | |
import * as path from 'path' | |
// This works because ./node_modules/$ is a symlink to its parent. | |
import name from '$/lib/name.mjs' | |
// This works because ./node_modules/name is a skeleton module with a package.json. | |
import {default as nmName} from 'name' | |
export const main = async () => { | |
// argv has the node executable, then this script, then args | |
console.log('argv:', process.argv) | |
console.log('Hello '+name) | |
console.log('Hello '+nmName) | |
// These should all refer to the user's working directory, not the script's. | |
console.log('PWD', process.env.PWD) | |
console.log('dir:', path.resolve('.')) | |
console.log('cwd:', process.cwd()) | |
// async/await works. | |
await fs.promises.access('.', fs.constants.R_OK) | |
// stdin works. | |
console.log('Echoing stdin:') | |
await new Promise(resolve => { | |
process.stdin.pipe(process.stdout) | |
process.stdin.on('end', resolve) | |
}) | |
// Exit codes are reported correctly. | |
process.exit(5) | |
} | |
await main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To compile: