pi-revit 0.1.4 → 0.1.6

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/bin/pi-revit.js CHANGED
@@ -1,95 +1,95 @@
1
- #!/usr/bin/env node
2
- const { spawnSync } = require("node:child_process");
3
- const fs = require("node:fs");
4
- const path = require("node:path");
5
-
6
- const root = path.resolve(__dirname, "..");
7
- const scriptsDir = path.join(root, "scripts");
8
-
9
- function usage() {
10
- console.log(`pi-revit installer\n\nUsage:\n npx.cmd -y pi-revit\n\nWhat it does on Windows:\n 1. Runs: pi install npm:pi-revit\n 2. Builds and deploys the Revit bridge add-in\n 3. Creates the Documents\\pi-revit workspace and global pi-revit command\n\nClose Revit before running. Revit 2025, 2026, or 2027 and the matching .NET SDK are required.`);
11
- }
12
-
13
- function fail(message) {
14
- console.error(`\npi-revit install failed: ${message}`);
15
- process.exit(1);
16
- }
17
-
18
- function commandExists(command) {
19
- return spawnSync("where.exe", [command], { stdio: "ignore" }).status === 0;
20
- }
21
-
22
- function run(title, command, args) {
23
- console.log(`\n==> ${title}`);
24
- const result = spawnSync(command, args, { stdio: "inherit", shell: false });
25
- if (result.error) fail(`${command} could not be started: ${result.error.message}`);
26
- if (result.status !== 0) process.exit(result.status ?? 1);
27
- }
28
-
29
- function runCmd(title, commandLine) {
30
- console.log(`\n==> ${title}`);
31
- // On Windows, npm/pi are usually .cmd shims. Run them through cmd.exe so PATHEXT
32
- // resolution works from npx's Node process just like it does in PowerShell/CMD.
33
- const result = spawnSync("cmd.exe", ["/d", "/s", "/c", commandLine], { stdio: "inherit" });
34
- if (result.error) fail(`${commandLine} could not be started: ${result.error.message}`);
35
- if (result.status !== 0) process.exit(result.status ?? 1);
36
- }
37
-
38
- function runPowerShellScript(scriptName) {
39
- const scriptPath = path.join(scriptsDir, scriptName);
40
- if (!fs.existsSync(scriptPath)) fail(`missing script: ${scriptPath}`);
41
- run(scriptName, "powershell.exe", ["-ExecutionPolicy", "Bypass", "-File", scriptPath]);
42
- }
43
-
44
- function revitIsRunning() {
45
- const result = spawnSync(
46
- "powershell.exe",
47
- ["-NoProfile", "-Command", "if (Get-Process Revit -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"],
48
- { stdio: "ignore" },
49
- );
50
- return result.status === 0;
51
- }
52
-
53
- function waitForEnter() {
54
- console.log("\nRevit appears to be running. Close Revit now, then press Enter to continue (or Ctrl+C to cancel).");
55
- try {
56
- fs.readSync(0, Buffer.alloc(1), 0, 1);
57
- } catch {
58
- // Non-interactive shell: fall through to the second running check.
59
- }
60
- }
61
-
62
- if (process.argv.includes("--help") || process.argv.includes("-h")) {
63
- usage();
64
- process.exit(0);
65
- }
66
-
67
- if (process.platform !== "win32") {
68
- fail("pi-revit's Revit bridge installer supports Windows only.");
69
- }
70
-
71
- if (!commandExists("pi")) {
72
- fail("the 'pi' command was not found on PATH. Install Pi first: npm install -g --ignore-scripts @earendil-works/pi-coding-agent");
73
- }
74
-
75
- if (!commandExists("dotnet")) {
76
- fail("the 'dotnet' command was not found on PATH. Install the .NET SDK required by your Revit version.");
77
- }
78
-
79
- if (revitIsRunning()) {
80
- waitForEnter();
81
- if (revitIsRunning()) fail("Revit is still running. Close Revit and run 'npx.cmd -y pi-revit' again.");
82
- }
83
-
84
- console.log("pi-revit full installer");
85
- console.log("This installs the Pi package, deploys the Revit add-in, and creates the workspace/global command.");
86
-
87
- runCmd("Install the Pi package from npm", "pi install npm:pi-revit");
88
- runPowerShellScript("deploy.ps1");
89
- runPowerShellScript("setup-workspace.ps1");
90
-
91
- console.log("\npi-revit installed.");
92
- console.log("Next steps:");
93
- console.log(" 1. Start Revit and click Always Load if prompted.");
94
- console.log(" 2. Open a project.");
95
- console.log(" 3. Run: pi-revit");
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require("node:child_process");
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const root = path.resolve(__dirname, "..");
7
+ const scriptsDir = path.join(root, "scripts");
8
+
9
+ function usage() {
10
+ console.log(`pi-revit installer\n\nUsage:\n npx.cmd -y pi-revit\n\nWhat it does on Windows:\n 1. Runs: pi install npm:pi-revit\n 2. Builds and deploys the Revit bridge add-in\n 3. Creates the Documents\\pi-revit workspace and global pi-revit command\n\nClose Revit before running. Revit 2025, 2026, or 2027 and the matching .NET SDK are required.`);
11
+ }
12
+
13
+ function fail(message) {
14
+ console.error(`\npi-revit install failed: ${message}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ function commandExists(command) {
19
+ return spawnSync("where.exe", [command], { stdio: "ignore" }).status === 0;
20
+ }
21
+
22
+ function run(title, command, args) {
23
+ console.log(`\n==> ${title}`);
24
+ const result = spawnSync(command, args, { stdio: "inherit", shell: false });
25
+ if (result.error) fail(`${command} could not be started: ${result.error.message}`);
26
+ if (result.status !== 0) process.exit(result.status ?? 1);
27
+ }
28
+
29
+ function runCmd(title, commandLine) {
30
+ console.log(`\n==> ${title}`);
31
+ // On Windows, npm/pi are usually .cmd shims. Run them through cmd.exe so PATHEXT
32
+ // resolution works from npx's Node process just like it does in PowerShell/CMD.
33
+ const result = spawnSync("cmd.exe", ["/d", "/s", "/c", commandLine], { stdio: "inherit" });
34
+ if (result.error) fail(`${commandLine} could not be started: ${result.error.message}`);
35
+ if (result.status !== 0) process.exit(result.status ?? 1);
36
+ }
37
+
38
+ function runPowerShellScript(scriptName) {
39
+ const scriptPath = path.join(scriptsDir, scriptName);
40
+ if (!fs.existsSync(scriptPath)) fail(`missing script: ${scriptPath}`);
41
+ run(scriptName, "powershell.exe", ["-ExecutionPolicy", "Bypass", "-File", scriptPath]);
42
+ }
43
+
44
+ function revitIsRunning() {
45
+ const result = spawnSync(
46
+ "powershell.exe",
47
+ ["-NoProfile", "-Command", "if (Get-Process Revit -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"],
48
+ { stdio: "ignore" },
49
+ );
50
+ return result.status === 0;
51
+ }
52
+
53
+ function waitForEnter() {
54
+ console.log("\nRevit appears to be running. Close Revit now, then press Enter to continue (or Ctrl+C to cancel).");
55
+ try {
56
+ fs.readSync(0, Buffer.alloc(1), 0, 1);
57
+ } catch {
58
+ // Non-interactive shell: fall through to the second running check.
59
+ }
60
+ }
61
+
62
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
63
+ usage();
64
+ process.exit(0);
65
+ }
66
+
67
+ if (process.platform !== "win32") {
68
+ fail("pi-revit's Revit bridge installer supports Windows only.");
69
+ }
70
+
71
+ if (!commandExists("pi")) {
72
+ fail("the 'pi' command was not found on PATH. Install Pi first: npm install -g --ignore-scripts @earendil-works/pi-coding-agent");
73
+ }
74
+
75
+ if (!commandExists("dotnet")) {
76
+ fail("the 'dotnet' command was not found on PATH. Install the .NET SDK required by your Revit version.");
77
+ }
78
+
79
+ if (revitIsRunning()) {
80
+ waitForEnter();
81
+ if (revitIsRunning()) fail("Revit is still running. Close Revit and run 'npx.cmd -y pi-revit' again.");
82
+ }
83
+
84
+ console.log("pi-revit full installer");
85
+ console.log("This installs the Pi package, deploys the Revit add-in, and creates the workspace/global command.");
86
+
87
+ runCmd("Install the Pi package from npm", "pi install npm:pi-revit");
88
+ runPowerShellScript("deploy.ps1");
89
+ runPowerShellScript("setup-workspace.ps1");
90
+
91
+ console.log("\npi-revit installed.");
92
+ console.log("Next steps:");
93
+ console.log(" 1. Start Revit and click Always Load if prompted.");
94
+ console.log(" 2. Open a project.");
95
+ console.log(" 3. Run: pi-revit");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-revit",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Native Pi connector for Autodesk Revit. Run npx.cmd -y pi-revit for the full Windows install.",
5
5
  "author": "Ahmad Altahlawi",
6
6
  "license": "MIT",
@@ -40,14 +40,44 @@ Write-Host "Workspace ready: $WorkspaceDir" -ForegroundColor Green
40
40
 
41
41
  # 2. Global pi-revit command next to pi (that directory is on PATH by definition).
42
42
  # The template's workspace path is rewritten to honor -WorkspaceDir.
43
- $pi = Get-Command pi -ErrorAction SilentlyContinue
43
+ # Under `npx pi-revit`, npx prepends its ephemeral cache's node_modules\.bin to
44
+ # PATH, and that folder holds a pi shim pulled in via peerDependencies. A launcher
45
+ # written next to that shim lands in a folder that is neither on the user's own
46
+ # PATH nor long-lived, so skip pi entries inside this package's node_modules tree
47
+ # or any npx cache and use the user's permanent pi instead.
48
+ function Get-PermanentPiCommand {
49
+ # Long-form both sides of the prefix comparison: PATH entries may carry 8.3
50
+ # short names (AHMAD~1.TAH) while $PSScriptRoot is normalized, and a form
51
+ # mismatch would silently defeat the own-tree exclusion.
52
+ function Resolve-LongPath([string]$p) {
53
+ try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
54
+ }
55
+ $ownTree = $null
56
+ $repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
57
+ $idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
58
+ if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
59
+ Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
60
+ if (-not $_.Source) { return $false }
61
+ $src = Resolve-LongPath $_.Source
62
+ ($src -notmatch '\\_npx\\') -and
63
+ (-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
64
+ } | Select-Object -First 1
65
+ }
66
+
67
+ $pi = Get-PermanentPiCommand
44
68
  if ($pi) {
45
69
  $binDir = Split-Path $pi.Source -Parent
46
- $globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd')
47
- $globalCmd = $globalCmd -replace '^set "WORKSPACE=.*$', ('set "WORKSPACE=' + $WorkspaceDir + '"')
48
- Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Ascii
70
+ # Literal line swap (no regex): the workspace path may contain characters
71
+ # like $ that a -replace replacement string would misinterpret.
72
+ $globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd') | ForEach-Object {
73
+ if ($_ -like 'set "WORKSPACE=*') { 'set "WORKSPACE=' + $WorkspaceDir + '"' } else { $_ }
74
+ }
75
+ # cmd.exe parses .cmd files in the console's OEM code page: write the launcher
76
+ # in that encoding so workspace paths with non-ASCII characters (user names,
77
+ # localized folder names) stay intact.
78
+ Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Oem
49
79
  Write-Host "Global command installed: $(Join-Path $binDir 'pi-revit.cmd')" -ForegroundColor Green
50
80
  Write-Host 'Run pi-revit from any terminal (pi-revit <project> for a project folder).'
51
81
  } else {
52
- Write-Warning 'pi command not found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then re-run this script to get the global pi-revit command.'
82
+ Write-Warning 'No permanent pi command found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then run npx.cmd -y pi-revit again (or re-run this script) to get the global pi-revit command.'
53
83
  }
@@ -54,36 +54,65 @@ foreach ($v in $versions) {
54
54
  Remove-IfExists (Join-Path $dir 'RevitBridge') "Revit $v add-in folder"
55
55
  }
56
56
 
57
- # 2. Global pi-revit command next to pi.
57
+ # 2. Global pi-revit command next to the user's permanent pi. When this script runs
58
+ # from an npx cache or a node_modules copy, PATH may lead Get-Command to a pi shim
59
+ # inside that ephemeral tree first (see setup-workspace.ps1) — the launcher was
60
+ # never installed there, so resolve the permanent pi the same way setup does.
61
+ function Get-PermanentPiCommand {
62
+ # Long-form both sides of the prefix comparison: PATH entries may carry 8.3
63
+ # short names (AHMAD~1.TAH) while $PSScriptRoot is normalized, and a form
64
+ # mismatch would silently defeat the own-tree exclusion.
65
+ function Resolve-LongPath([string]$p) {
66
+ try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
67
+ }
68
+ $ownTree = $null
69
+ $repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
70
+ $idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
71
+ if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
72
+ Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
73
+ if (-not $_.Source) { return $false }
74
+ $src = Resolve-LongPath $_.Source
75
+ ($src -notmatch '\\_npx\\') -and
76
+ (-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
77
+ } | Select-Object -First 1
78
+ }
79
+
58
80
  $pi = Get-Command pi -ErrorAction SilentlyContinue
59
- if ($pi) {
60
- $binDir = Split-Path $pi.Source -Parent
81
+ $piPermanent = Get-PermanentPiCommand
82
+ if ($piPermanent) {
83
+ $binDir = Split-Path $piPermanent.Source -Parent
61
84
  Remove-IfExists (Join-Path $binDir 'pi-revit.cmd') 'global pi-revit command'
62
85
  } else {
63
- Write-Host 'pi not found on PATH; skipping the global pi-revit command.'
86
+ Write-Host 'No permanent pi found on PATH; skipping the global pi-revit command.'
64
87
  }
65
88
 
66
89
  # 3. Bridge runtime folder (connection token; recreated on each Revit start).
67
90
  Remove-IfExists (Join-Path $env:APPDATA 'RevitBridge') 'bridge runtime folder'
68
91
 
69
- # 4. Pi package registration. Mirror the documented install command exactly:
70
- # `pi install ./` stores the package keyed by a path resolved relative to Pi's
71
- # settings file, so removal must use the same `./` form from the repo root.
72
- # An absolute path may not match the original registration. The repo root is
73
- # the parent of this script's folder.
92
+ # 4. Pi package registration. Pi keys a package by its install source: the npm
93
+ # installer (npx.cmd -y pi-revit) registers `npm:pi-revit`, while an install
94
+ # from a clone of this repo (`pi install ./`) registers the repo path resolved
95
+ # from the repo root. Try both forms; each succeeds only for the source that
96
+ # is actually registered, so a machine with both gets both removed.
74
97
  if ($pi) {
75
98
  $repoRoot = Split-Path $PSScriptRoot -Parent
76
99
  Push-Location $repoRoot
77
100
  try {
78
- $global:LASTEXITCODE = 0
79
- & pi remove ./
80
- if ($LASTEXITCODE -eq 0) {
81
- Write-Host 'Removed the pi-revit Pi package.' -ForegroundColor Green
101
+ $removed = @()
102
+ foreach ($source in @('npm:pi-revit', './')) {
103
+ $global:LASTEXITCODE = 0
104
+ & pi remove $source
105
+ if ($LASTEXITCODE -eq 0) {
106
+ $removed += $source
107
+ }
108
+ }
109
+ if ($removed.Count -gt 0) {
110
+ Write-Host "Removed the pi-revit Pi package ($($removed -join ', '))." -ForegroundColor Green
82
111
  } else {
83
- Write-Warning "pi remove exited with code $LASTEXITCODE; run 'pi remove ./' from this repo manually."
112
+ Write-Warning "No pi-revit Pi package registration was found. If pi still lists it, run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source)."
84
113
  }
85
114
  } catch {
86
- Write-Warning "Could not auto-remove the Pi package; run 'pi remove ./' from this repo manually. ($_)"
115
+ Write-Warning "Could not auto-remove the Pi package; run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source). ($_)"
87
116
  } finally {
88
117
  Pop-Location
89
118
  }