symlx 0.1.11 → 0.1.13

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
@@ -14,7 +14,7 @@ In a CLI project with:
14
14
  run:
15
15
 
16
16
  ```bash
17
- symlx link
17
+ symlx serve
18
18
  ```
19
19
 
20
20
  Then use your CLI normally:
@@ -23,7 +23,7 @@ Then use your CLI normally:
23
23
  awesome-cli --help
24
24
  ```
25
25
 
26
- Use `symlx serve` when you want temporary session-scoped links with auto-cleanup on exit.
26
+ Use `symlx link` when you want one-shot linking without keeping a live file-watcher session open.
27
27
 
28
28
  ---
29
29
 
@@ -203,6 +203,7 @@ Current launcher inference:
203
203
 
204
204
  - `.js`, `.mjs`, `.cjs` -> Node launcher
205
205
  - `.ts`, `.tsx`, `.mts`, `.cts` -> `tsx` launcher
206
+ - if a TypeScript target declares `#!/usr/bin/env node`, symlx fails early and tells you to use `tsx` shebang or remove shebang for launcher inference
206
207
 
207
208
  TypeScript runtime resolution order is:
208
209
 
@@ -287,6 +288,11 @@ symlx serve --collision fail
287
288
 
288
289
  Install `tsx` in the project or make `tsx` available on `PATH`.
289
290
 
291
+ ## "typescript target uses node shebang and is not directly runnable"
292
+
293
+ - replace shebang with `#!/usr/bin/env tsx`
294
+ - or remove shebang and let symlx infer launcher by file type
295
+
290
296
  ## "package.json not found"
291
297
 
292
298
  Run in your project root, or pass bins inline/config.
@@ -5,8 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.prepareBinTargets = prepareBinTargets;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
8
9
  const launchers_1 = require("./launchers");
9
10
  const shebang_1 = require("./shebang");
11
+ const TYPESCRIPT_TARGET_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
10
12
  function isExecutable(filePath) {
11
13
  if (process.platform === 'win32') {
12
14
  return true;
@@ -53,6 +55,17 @@ function addUnsupportedWithoutShebangIssue(issues, name, target, reason) {
53
55
  hint: 'explicitly specify a shebang at the top of the target file to declare its runner',
54
56
  });
55
57
  }
58
+ function isTypeScriptTarget(targetPath) {
59
+ return TYPESCRIPT_TARGET_EXTENSIONS.has(node_path_1.default.extname(targetPath).toLowerCase());
60
+ }
61
+ function addInvalidTypeScriptNodeShebangIssue(issues, name, target) {
62
+ issues.push({
63
+ name,
64
+ target,
65
+ reason: 'typescript target uses node shebang and is not directly runnable',
66
+ hint: 'use #!/usr/bin/env tsx or remove shebang to use launcher inference',
67
+ });
68
+ }
56
69
  function prepareBinTargets(cwd, bin, options = {}) {
57
70
  const currentPath = options.currentPath ?? process.env.PATH;
58
71
  const preparedTargets = [];
@@ -86,7 +99,13 @@ function prepareBinTargets(cwd, bin, options = {}) {
86
99
  });
87
100
  continue;
88
101
  }
89
- if ((0, shebang_1.hasShebang)(target)) {
102
+ const shebang = (0, shebang_1.readShebang)(target);
103
+ if (shebang) {
104
+ const shebangRuntime = (0, shebang_1.extractShebangRuntime)(shebang);
105
+ if (isTypeScriptTarget(target) && shebangRuntime === 'node') {
106
+ addInvalidTypeScriptNodeShebangIssue(issues, name, target);
107
+ continue;
108
+ }
90
109
  const executableIssue = ensureExecutable(target, stats.mode);
91
110
  if (executableIssue) {
92
111
  issues.push({
@@ -4,8 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.readShebang = readShebang;
7
+ exports.extractShebangRuntime = extractShebangRuntime;
8
+ exports.readShebangRuntime = readShebangRuntime;
7
9
  exports.hasShebang = hasShebang;
8
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
9
12
  function readShebang(filePath) {
10
13
  const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
11
14
  const firstLine = raw.split(/\r?\n/, 1)[0]?.replace(/^\uFEFF/, '');
@@ -14,6 +17,38 @@ function readShebang(filePath) {
14
17
  }
15
18
  return firstLine;
16
19
  }
20
+ function tokenizeShebangCommand(shebang) {
21
+ return shebang
22
+ .slice(2)
23
+ .trim()
24
+ .split(/\s+/)
25
+ .map((token) => token.replace(/^['"]|['"]$/g, ''))
26
+ .filter(Boolean);
27
+ }
28
+ function extractShebangRuntime(shebang) {
29
+ const tokens = tokenizeShebangCommand(shebang);
30
+ if (tokens.length === 0) {
31
+ return undefined;
32
+ }
33
+ const command = tokens[0];
34
+ const commandBase = node_path_1.default.basename(command).toLowerCase();
35
+ if (commandBase === 'env') {
36
+ // env-style shebangs can include flags before the actual runtime command.
37
+ const runtimeToken = tokens.slice(1).find((token) => !token.startsWith('-'));
38
+ if (!runtimeToken) {
39
+ return undefined;
40
+ }
41
+ return node_path_1.default.basename(runtimeToken).toLowerCase();
42
+ }
43
+ return commandBase;
44
+ }
45
+ function readShebangRuntime(filePath) {
46
+ const shebang = readShebang(filePath);
47
+ if (!shebang) {
48
+ return undefined;
49
+ }
50
+ return extractShebangRuntime(shebang);
51
+ }
17
52
  function hasShebang(filePath) {
18
53
  return Boolean(readShebang(filePath));
19
54
  }
package/package.json CHANGED
@@ -1,8 +1,20 @@
1
1
  {
2
2
  "name": "symlx",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Temporary local CLI bin linker",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/emekaorji/symlx.git"
9
+ },
10
+ "homepage": "https://github.com/emekaorji/symlx#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/emekaorji/symlx/issues"
13
+ },
14
+ "preferGlobal": true,
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
6
18
  "bin": {
7
19
  "symlx": "dist/cli.js",
8
20
  "cx": "dist/cli.js"
@@ -14,7 +26,13 @@
14
26
  "keywords": [
15
27
  "cli",
16
28
  "bin",
17
- "dev"
29
+ "command-linker",
30
+ "local-cli",
31
+ "developer-tooling",
32
+ "symlink",
33
+ "typescript",
34
+ "nodejs",
35
+ "npm-link"
18
36
  ],
19
37
  "engines": {
20
38
  "node": ">=18.0.0"