@atlisp/cli 1.2.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/.github/workflows/publish.yml +18 -0
- package/.github/workflows/test.yml +14 -0
- package/README.md +88 -0
- package/__tests__/__snapshots__/cli.test.js.snap +12 -0
- package/__tests__/build.test.js +48 -0
- package/__tests__/cli.test.js +246 -0
- package/__tests__/config.test.js +71 -0
- package/__tests__/deps.test.js +64 -0
- package/__tests__/index.test.js +63 -0
- package/__tests__/lint.test.js +58 -0
- package/atlisp.js +219 -0
- package/commands/batch.js +14 -0
- package/commands/build.js +267 -0
- package/commands/checksum.js +23 -0
- package/commands/config.js +264 -0
- package/commands/dcl-migrate.js +62 -0
- package/commands/doc.js +45 -0
- package/commands/doctor.js +292 -0
- package/commands/find.js +14 -0
- package/commands/index.js +33 -0
- package/commands/info.js +46 -0
- package/commands/init.js +202 -0
- package/commands/install.js +23 -0
- package/commands/lint.js +79 -0
- package/commands/list.js +99 -0
- package/commands/publish.js +237 -0
- package/commands/pull.js +118 -0
- package/commands/qrcode.js +33 -0
- package/commands/test.js +223 -0
- package/commands/watch.js +47 -0
- package/completion/atlisp-completion.bash +107 -0
- package/completion/atlisp-completion.ps1 +64 -0
- package/index.js +13 -0
- package/kernel-order.json +28 -0
- package/lib/atlisp.js +15 -0
- package/lib/batch.js +173 -0
- package/lib/cad.js +199 -0
- package/lib/common.js +189 -0
- package/lib/deps.js +121 -0
- package/lib/docgen.js +93 -0
- package/lib/find.js +154 -0
- package/lib/mcp-client.js +203 -0
- package/package.json +49 -0
- package/scripts/add-deps.js +81 -0
- package/scripts/fix-deps.js +98 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# @lisp CLI bash/zsh completion
|
|
3
|
+
# Source this file in your .bashrc or .zshrc:
|
|
4
|
+
# source /path/to/atlisp-completion.bash
|
|
5
|
+
|
|
6
|
+
_atlisp_completions() {
|
|
7
|
+
local cur prev words cword
|
|
8
|
+
if type _get_comp_words_by_ref &>/dev/null 2>&1; then
|
|
9
|
+
_get_comp_words_by_ref cur prev words cword
|
|
10
|
+
else
|
|
11
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
12
|
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
local commands="install pull remove list search init checksum config login build build-module deploy test batch find check doctor qrcode qrcode-ascii info doc sync --help --version"
|
|
16
|
+
|
|
17
|
+
local templates="basic dialog ai enterprise"
|
|
18
|
+
local platforms="sbcl cad"
|
|
19
|
+
local config_actions="get set list"
|
|
20
|
+
local sync_actions="--push --pull --status"
|
|
21
|
+
local tools="at-pm base network pkgman qrencode userman"
|
|
22
|
+
|
|
23
|
+
if [[ $cword -eq 1 ]]; then
|
|
24
|
+
COMPREPLY=($(compgen -W "${commands}" -- "${cur}"))
|
|
25
|
+
return 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
case "${prev}" in
|
|
29
|
+
install)
|
|
30
|
+
COMPREPLY=($(compgen -W "--auto" -- "${cur}"))
|
|
31
|
+
;;
|
|
32
|
+
pull|remove|info)
|
|
33
|
+
COMPREPLY=($(compgen -W "--auto --download" -- "${cur}"))
|
|
34
|
+
;;
|
|
35
|
+
init)
|
|
36
|
+
COMPREPLY=($(compgen -W "--template" -- "${cur}"))
|
|
37
|
+
;;
|
|
38
|
+
build-module|bm)
|
|
39
|
+
COMPREPLY=($(compgen -W "${tools}" -- "${cur}"))
|
|
40
|
+
;;
|
|
41
|
+
test)
|
|
42
|
+
COMPREPLY=($(compgen -W "--platform --list --load --auto" -- "${cur}"))
|
|
43
|
+
;;
|
|
44
|
+
doctor)
|
|
45
|
+
COMPREPLY=($(compgen -W "--fix" -- "${cur}"))
|
|
46
|
+
;;
|
|
47
|
+
config)
|
|
48
|
+
COMPREPLY=($(compgen -W "${config_actions}" -- "${cur}"))
|
|
49
|
+
;;
|
|
50
|
+
sync)
|
|
51
|
+
COMPREPLY=($(compgen -W "${sync_actions}" -- "${cur}"))
|
|
52
|
+
;;
|
|
53
|
+
qrcode|qrcode-ascii|qr|qra|doc|checksum|search)
|
|
54
|
+
# free text argument, no completion
|
|
55
|
+
COMPREPLY=()
|
|
56
|
+
;;
|
|
57
|
+
--template)
|
|
58
|
+
COMPREPLY=($(compgen -W "${templates}" -- "${cur}"))
|
|
59
|
+
;;
|
|
60
|
+
--platform)
|
|
61
|
+
COMPREPLY=($(compgen -W "${platforms}" -- "${cur}"))
|
|
62
|
+
;;
|
|
63
|
+
--out)
|
|
64
|
+
# file path completion
|
|
65
|
+
COMPREPLY=($(compgen -f -- "${cur}"))
|
|
66
|
+
;;
|
|
67
|
+
--path)
|
|
68
|
+
COMPREPLY=($(compgen -d -- "${cur}"))
|
|
69
|
+
;;
|
|
70
|
+
get|set)
|
|
71
|
+
# config key completion
|
|
72
|
+
local config_keys="user-email user-token server-url installDir draw-scale sso-type sso-url sso-client-id enterprise-registry"
|
|
73
|
+
COMPREPLY=($(compgen -W "${config_keys}" -- "${cur}"))
|
|
74
|
+
;;
|
|
75
|
+
*)
|
|
76
|
+
if [[ $cword -eq 2 ]]; then
|
|
77
|
+
case "${COMP_WORDS[1]}" in
|
|
78
|
+
config)
|
|
79
|
+
COMPREPLY=($(compgen -W "${config_actions}" -- "${cur}"))
|
|
80
|
+
;;
|
|
81
|
+
build-module|bm)
|
|
82
|
+
COMPREPLY=($(compgen -W "${tools}" -- "${cur}"))
|
|
83
|
+
;;
|
|
84
|
+
test)
|
|
85
|
+
COMPREPLY=($(compgen -W "--platform --list --load --auto" -- "${cur}"))
|
|
86
|
+
;;
|
|
87
|
+
sync)
|
|
88
|
+
COMPREPLY=($(compgen -W "${sync_actions}" -- "${cur}"))
|
|
89
|
+
;;
|
|
90
|
+
batch)
|
|
91
|
+
COMPREPLY=($(compgen -W "--path --expr --save --visible --force" -- "${cur}"))
|
|
92
|
+
;;
|
|
93
|
+
find)
|
|
94
|
+
COMPREPLY=($(compgen -W "--path --name --table" -- "${cur}"))
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
fi
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Register completion for both 'atlisp' and 'atlisp-node'
|
|
103
|
+
if type complete &>/dev/null 2>&1; then
|
|
104
|
+
complete -F _atlisp_completions atlisp atlisp-node
|
|
105
|
+
elif type compdef &>/dev/null 2>&1; then
|
|
106
|
+
compdef _atlisp_completions atlisp atlisp-node
|
|
107
|
+
fi
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @lisp CLI PowerShell completion
|
|
2
|
+
# Add to your $PROFILE:
|
|
3
|
+
# . C:\path\to\atlisp-completion.ps1
|
|
4
|
+
|
|
5
|
+
$atlispCommands = @(
|
|
6
|
+
'install', 'pull', 'remove', 'list', 'search', 'init', 'checksum',
|
|
7
|
+
'config', 'login', 'build', 'build-module', 'deploy', 'test',
|
|
8
|
+
'batch', 'find', 'check', 'doctor', 'qrcode', 'qrcode-ascii',
|
|
9
|
+
'info', 'doc', 'sync', '--help', '--version'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
$atlispTemplates = @('basic', 'dialog', 'ai', 'enterprise')
|
|
13
|
+
$atlispPlatforms = @('sbcl', 'cad')
|
|
14
|
+
$atlispConfigActions = @('get', 'set', 'list')
|
|
15
|
+
$atlispTools = @('at-pm', 'base', 'network', 'pkgman', 'qrencode', 'userman')
|
|
16
|
+
$atlispConfigKeys = @(
|
|
17
|
+
'user-email', 'user-token', 'server-url', 'installDir',
|
|
18
|
+
'draw-scale', 'sso-type', 'sso-url', 'sso-client-id',
|
|
19
|
+
'enterprise-registry'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
Register-ArgumentCompleter -Native -CommandName atlisp -ScriptBlock {
|
|
23
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
24
|
+
|
|
25
|
+
$commandElements = $commandAst.CommandElements
|
|
26
|
+
$currentIndex = $commandElements.Count - 1
|
|
27
|
+
$parentCommand = if ($currentIndex -ge 1) { $commandElements[1].Value } else { $null }
|
|
28
|
+
$grandParentCommand = if ($currentIndex -ge 2) { $commandElements[2].Value } else { $null }
|
|
29
|
+
|
|
30
|
+
function Complete-From($values) {
|
|
31
|
+
$values | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { "'$_'" }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if ($currentIndex -eq 1) {
|
|
35
|
+
return Complete-From $atlispCommands
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
switch ($parentCommand) {
|
|
39
|
+
'install' { return Complete-From @('--auto') }
|
|
40
|
+
'pull' { return Complete-From @('--download', '--auto') }
|
|
41
|
+
'remove' { return Complete-From @('--auto') }
|
|
42
|
+
'init' { return Complete-From @('--template') }
|
|
43
|
+
'build-module' { return Complete-From $atlispTools }
|
|
44
|
+
'test' { return Complete-From @('--platform', '--list', '--load', '--auto') }
|
|
45
|
+
'doctor' { return Complete-From @('--fix') }
|
|
46
|
+
'config' {
|
|
47
|
+
if ($currentIndex -eq 2) { return Complete-From $atlispConfigActions }
|
|
48
|
+
if ($grandParentCommand -in @('get', 'set')) { return Complete-From $atlispConfigKeys }
|
|
49
|
+
}
|
|
50
|
+
'sync' { return Complete-From @('--push', '--pull', '--status') }
|
|
51
|
+
'batch' { return Complete-From @('--path', '--expr', '--save', '--visible', '--force') }
|
|
52
|
+
'find' { return Complete-From @('--path', '--name', '--table') }
|
|
53
|
+
'doc' { return $null }
|
|
54
|
+
'qrcode' { return $null }
|
|
55
|
+
'qrcode-ascii' { return $null }
|
|
56
|
+
'search' { return $null }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Complete flag values
|
|
60
|
+
switch ($grandParentCommand) {
|
|
61
|
+
'--template' { return Complete-From $atlispTemplates }
|
|
62
|
+
'--platform' { return Complete-From $atlispPlatforms }
|
|
63
|
+
}
|
|
64
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const lib = require("./lib/atlisp");
|
|
2
|
+
const { batch } = require("./lib/batch");
|
|
3
|
+
const { find, CAD_TABLES } = require("./lib/find");
|
|
4
|
+
const cad = require("./lib/cad");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
atlisp: lib.atlisp,
|
|
8
|
+
...lib,
|
|
9
|
+
batch,
|
|
10
|
+
find,
|
|
11
|
+
CAD_TABLES,
|
|
12
|
+
cad,
|
|
13
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[
|
|
2
|
+
"compat/cl-functions.lsp", "compat/cl-macros.lsp", "platform-api.lsp",
|
|
3
|
+
"lib/string-utils.lsp", "lib/version-utils.lsp", "lib/file-utils.lsp", "lib/dcl.lsp",
|
|
4
|
+
"event-bus.lsp", "base-function.lsp",
|
|
5
|
+
"platform.lsp", "version.lsp", "constants.lsp", "bootstrap.lsp", "error/safe-eval.lsp", "error/handler.lsp",
|
|
6
|
+
"log.lsp", "i18n.lsp",
|
|
7
|
+
"net/ax-request.lsp", "net/cache.lsp", "net/checksum.lsp",
|
|
8
|
+
"net/headers.lsp", "net/http.lsp", "net/mid.lsp",
|
|
9
|
+
"net/module.lsp", "net/probe.lsp", "net/progress.lsp",
|
|
10
|
+
"net/proxy.lsp", "net/registry.lsp", "net/reload.lsp",
|
|
11
|
+
"net/uri.lsp", "net/wget.lsp",
|
|
12
|
+
"funlib/record.lsp", "funlib/download.lsp", "funlib/browse.lsp", "@lisp-env.lsp",
|
|
13
|
+
"setup.lsp",
|
|
14
|
+
"sub-system/help-system.lsp",
|
|
15
|
+
"sub-system/config/store.lsp", "sub-system/config/migrate.lsp", "sub-system/config/dialog.lsp",
|
|
16
|
+
"sub-system/ui.lsp", "sub-system/ui/hotkey.lsp", "sub-system/ui/menu.lsp", "sub-system/ui/panel.lsp",
|
|
17
|
+
"sub-system/voice.lsp",
|
|
18
|
+
"sub-system/package/lifecycle.lsp", "sub-system/package/lock.lsp", "sub-system/package/persist.lsp",
|
|
19
|
+
"sub-system/package/load.lsp", "sub-system/package/version.lsp", "sub-system/package/cmd.lsp",
|
|
20
|
+
"sub-system/user-manager.lsp", "sub-system/update.lsp",
|
|
21
|
+
"sub-system/condition/cn-commands.lsp", "sub-system/condition/nlp.lsp", "sub-system/condition/reactor.lsp",
|
|
22
|
+
"sub-system/cache.lsp", "sub-system/scheduler.lsp",
|
|
23
|
+
"sub-system/audit.lsp", "sub-system/permission.lsp",
|
|
24
|
+
"sub-system/config-sync/file.lsp", "sub-system/config-sync/config.lsp",
|
|
25
|
+
"sub-system/config/config.lsp",
|
|
26
|
+
"sub-system/command.lsp",
|
|
27
|
+
"init/compat.lsp", "init/steps.lsp", "init/sequence.lsp"
|
|
28
|
+
]
|
package/lib/atlisp.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const common = require("./common");
|
|
2
|
+
const cad = require("./cad");
|
|
3
|
+
const batchMod = require("./batch");
|
|
4
|
+
const findMod = require("./find");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
atlisp: () => "atlisp - CAD package manager!",
|
|
8
|
+
...common,
|
|
9
|
+
...cad,
|
|
10
|
+
batch: batchMod.batch,
|
|
11
|
+
find: findMod.find,
|
|
12
|
+
normalizeTable: findMod.normalizeTable,
|
|
13
|
+
CAD_TABLES: findMod.CAD_TABLES,
|
|
14
|
+
gatherDwgs: batchMod.gatherDwgs,
|
|
15
|
+
};
|
package/lib/batch.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const { resolveAppId, isWindows } = require("./cad");
|
|
6
|
+
|
|
7
|
+
function gatherDwgs(predir) {
|
|
8
|
+
if (!predir) return [];
|
|
9
|
+
const absPath = path.resolve(predir);
|
|
10
|
+
if (!fs.existsSync(absPath)) return [];
|
|
11
|
+
const stat = fs.statSync(absPath);
|
|
12
|
+
if (stat.isFile()) {
|
|
13
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
14
|
+
if (ext === ".dwg") return [absPath];
|
|
15
|
+
if (ext === ".txt" || ext === ".csv") {
|
|
16
|
+
return fs.readFileSync(absPath, "utf-8")
|
|
17
|
+
.split("\n")
|
|
18
|
+
.map(l => l.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map(l => path.resolve(l));
|
|
21
|
+
}
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
return walkDwgs(absPath);
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function walkDwgs(dir) {
|
|
31
|
+
let results = [];
|
|
32
|
+
try {
|
|
33
|
+
const list = fs.readdirSync(dir);
|
|
34
|
+
for (const entry of list) {
|
|
35
|
+
const full = path.join(dir, entry);
|
|
36
|
+
const stat = fs.statSync(full);
|
|
37
|
+
if (stat.isDirectory()) {
|
|
38
|
+
results = results.concat(walkDwgs(full));
|
|
39
|
+
} else if (path.extname(full).toLowerCase() === ".dwg") {
|
|
40
|
+
results.push(full);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function batchPS(appId, profile, dwgList, expr, save, visible, preserveTime) {
|
|
48
|
+
const dwgJson = JSON.stringify(dwgList);
|
|
49
|
+
const escExpr = expr.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
50
|
+
return `
|
|
51
|
+
$dwgs = @(${dwgList.map(d => `'${d.replace(/'/g, "''")}'`).join(", ")})
|
|
52
|
+
$appId = "${resolveAppId(appId)}"
|
|
53
|
+
$profile = "${profile || ""}"
|
|
54
|
+
$save = ${save ? "$true" : "$false"}
|
|
55
|
+
$visible = ${visible ? "$true" : "$false"}
|
|
56
|
+
$preserveTime = ${preserveTime ? "$true" : "$false"}
|
|
57
|
+
$expr = '${escExpr}'
|
|
58
|
+
$total = $dwgs.Count
|
|
59
|
+
$i = 0
|
|
60
|
+
$failed = 0
|
|
61
|
+
|
|
62
|
+
$acad = $null
|
|
63
|
+
try { $acad = [System.Runtime.InteropServices.Marshal]::GetActiveObject($appId) } catch {}
|
|
64
|
+
if (-not $acad) { $acad = New-Object -ComObject $appId }
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
if ($profile -ne "") {
|
|
68
|
+
$profiles = $acad.Preferences.Profiles.GetAllProfileNames()
|
|
69
|
+
if ($profiles -contains $profile) { $acad.Preferences.Profiles.ActiveProfile = $profile }
|
|
70
|
+
}
|
|
71
|
+
$acad.Visible = $visible
|
|
72
|
+
if ($acad.Documents.Count -eq 0) { $acad.Documents.Add() }
|
|
73
|
+
Start-Sleep -Seconds 3
|
|
74
|
+
|
|
75
|
+
while ($acad.Documents.Count -gt 0) {
|
|
76
|
+
try { $acad.ActiveDocument.Close($false) } catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
foreach ($dwg in $dwgs) {
|
|
80
|
+
$i++
|
|
81
|
+
Write-Output "[$i/$total] $dwg"
|
|
82
|
+
try {
|
|
83
|
+
if ($preserveTime) { $stInfo = [System.IO.File]::GetLastWriteTime($dwg) }
|
|
84
|
+
$doc = $acad.Documents.Open($dwg, $false)
|
|
85
|
+
Start-Sleep -Seconds 2
|
|
86
|
+
$doc.SendCommand($expr + " ")
|
|
87
|
+
Start-Sleep -Seconds 2
|
|
88
|
+
if ($save) {
|
|
89
|
+
$doc.Save()
|
|
90
|
+
Write-Output " saved"
|
|
91
|
+
}
|
|
92
|
+
if ($preserveTime) {
|
|
93
|
+
[System.IO.File]::SetLastWriteTime($dwg, $stInfo)
|
|
94
|
+
}
|
|
95
|
+
$doc.Close($false)
|
|
96
|
+
} catch {
|
|
97
|
+
Write-Output " ERROR: $($_.Exception.Message)"
|
|
98
|
+
$failed++
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
Write-Output "FATAL: $($_.Exception.Message)"
|
|
103
|
+
$failed = $total
|
|
104
|
+
} finally {
|
|
105
|
+
try { if ($acad) { $acad.Quit() } } catch {}
|
|
106
|
+
}
|
|
107
|
+
Write-Output "DONE: processed=$total failed=$failed"
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function batch(options) {
|
|
112
|
+
if (!isWindows()) {
|
|
113
|
+
console.error("batch 命令仅支持 Windows CAD COM 自动化。");
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { path: predir, expr, save, force, visible, mt, appId, profile } = options;
|
|
118
|
+
|
|
119
|
+
if (!expr) {
|
|
120
|
+
console.error("错误: 请使用 --expr 指定要执行的 Lisp 表达式。");
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let dwgs = gatherDwgs(predir);
|
|
125
|
+
if (dwgs.length === 0) {
|
|
126
|
+
console.error("错误: 未找到 DWG 文件。请使用 --path 指定包含 DWG 的目录、单个 DWG 文件或 .txt 文件列表。");
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const totalStr = dwgs.length > 2 ? ` (显示前 2 个)` : "";
|
|
131
|
+
console.log(" 找到 " + dwgs.length + " 个 DWG 文件" + totalStr);
|
|
132
|
+
dwgs.slice(0, 2).forEach(d => console.log(" " + d));
|
|
133
|
+
console.log(" 表达式: " + expr);
|
|
134
|
+
console.log(" 保存: " + (save ? "是" : "否"));
|
|
135
|
+
console.log(" 可见: " + (visible ? "是" : "否"));
|
|
136
|
+
|
|
137
|
+
if (!force) {
|
|
138
|
+
console.log(" 使用 --force 跳过确认直接执行。");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const PS = batchPS(appId, profile, dwgs, expr, save, visible, false);
|
|
142
|
+
const scriptPath = path.join(os.tmpdir(), "atlisp-batch-" + Date.now() + ".ps1");
|
|
143
|
+
fs.writeFileSync(scriptPath, PS, "utf-8");
|
|
144
|
+
|
|
145
|
+
console.log("\n 开始批处理...");
|
|
146
|
+
try {
|
|
147
|
+
const out = execSync(`powershell -ExecutionPolicy Bypass -File "${scriptPath}"`, {
|
|
148
|
+
encoding: "utf-8",
|
|
149
|
+
timeout: Math.max(60000, dwgs.length * 15000),
|
|
150
|
+
maxBuffer: 1024 * 1024,
|
|
151
|
+
});
|
|
152
|
+
const lines = out.split("\n").filter(Boolean);
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
if (line.startsWith("DONE:")) {
|
|
155
|
+
const m = line.match(/processed=(\d+) failed=(\d+)/);
|
|
156
|
+
if (m) {
|
|
157
|
+
console.log(" 处理完成: " + m[1] + " 个文件, " + m[2] + " 个失败");
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
console.log(" " + line);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const failedMatch = out.match(/failed=(\d+)/);
|
|
164
|
+
return failedMatch && parseInt(failedMatch[1]) === 0;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error(" 批处理失败: " + e.message);
|
|
167
|
+
return false;
|
|
168
|
+
} finally {
|
|
169
|
+
try { fs.unlinkSync(scriptPath); } catch (e) {}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { batch, gatherDwgs };
|
package/lib/cad.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const { installCode, ATLISP_DIR } = require("./common");
|
|
6
|
+
|
|
7
|
+
const PLATFORM_MAP = {
|
|
8
|
+
autocad: "AutoCAD.Application",
|
|
9
|
+
zwcad: "ZWCAD.Application",
|
|
10
|
+
gstarcad: "GStarCAD.Application",
|
|
11
|
+
bricscad: "BricscadApp.AcadApplication",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function resolveAppId(app) {
|
|
15
|
+
if (!app || app === "auto") return "AutoCAD.Application";
|
|
16
|
+
const lower = app.toLowerCase();
|
|
17
|
+
for (const [name, id] of Object.entries(PLATFORM_MAP)) {
|
|
18
|
+
if (lower === name) return id;
|
|
19
|
+
}
|
|
20
|
+
for (const [name, id] of Object.entries(PLATFORM_MAP)) {
|
|
21
|
+
if (lower.startsWith(name + ".")) return app;
|
|
22
|
+
}
|
|
23
|
+
return app;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function execPS(script) {
|
|
27
|
+
const psPath = path.join(os.tmpdir(), "atlisp-cad-" + Date.now() + ".ps1");
|
|
28
|
+
fs.writeFileSync(psPath, script, "utf-8");
|
|
29
|
+
try {
|
|
30
|
+
const out = execSync(`powershell -ExecutionPolicy Bypass -File "${psPath}"`, {
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
timeout: 120000,
|
|
33
|
+
});
|
|
34
|
+
return out.trim();
|
|
35
|
+
} finally {
|
|
36
|
+
try { fs.unlinkSync(psPath); } catch (e) {}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Try GetActiveObject first (running CAD), fall back to New-Object
|
|
41
|
+
const GET_CAD_PS = (appId) => `
|
|
42
|
+
$acad = $null
|
|
43
|
+
try { $acad = [System.Runtime.InteropServices.Marshal]::GetActiveObject("${appId}") } catch {}
|
|
44
|
+
if (-not $acad) { $acad = New-Object -ComObject "${appId}" }
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const INSTALL_PS = (appId, profile) => `
|
|
48
|
+
${GET_CAD_PS(appId)}
|
|
49
|
+
try {
|
|
50
|
+
${profile ? `$profiles = $acad.Preferences.Profiles.GetAllProfileNames()
|
|
51
|
+
if ($profiles -contains "${profile}") { $acad.Preferences.Profiles.ActiveProfile = "${profile}" }`
|
|
52
|
+
: ""}
|
|
53
|
+
if ($acad.Documents.Count -eq 0) { $acad.Documents.Add() }
|
|
54
|
+
$acad.Visible = $false
|
|
55
|
+
$acad.ActiveDocument.SendCommand('${installCode} ')
|
|
56
|
+
Start-Sleep -Seconds 3
|
|
57
|
+
$acad.ActiveDocument.SendCommand('(@::set-config \\'@::tips-currpage 2\\') ')
|
|
58
|
+
Start-Sleep -Seconds 1
|
|
59
|
+
Write-Output "OK"
|
|
60
|
+
} catch {
|
|
61
|
+
Write-Output "ERROR: $($_.Exception.Message)"
|
|
62
|
+
} finally {
|
|
63
|
+
if ($acad -and $acad.Visible -eq $false) {
|
|
64
|
+
try { $acad.Visible = $true } catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const SEND_PS = (appId, profile, lispCode) => `
|
|
70
|
+
${GET_CAD_PS(appId)}
|
|
71
|
+
try {
|
|
72
|
+
${profile ? `$profiles = $acad.Preferences.Profiles.GetAllProfileNames()
|
|
73
|
+
if ($profiles -contains "${profile}") { $acad.Preferences.Profiles.ActiveProfile = "${profile}" }`
|
|
74
|
+
: ""}
|
|
75
|
+
if ($acad.Documents.Count -eq 0) { $acad.Documents.Add() }
|
|
76
|
+
Start-Sleep -Seconds 2
|
|
77
|
+
$acad.ActiveDocument.SendCommand('${lispCode} ')
|
|
78
|
+
Start-Sleep -Seconds 3
|
|
79
|
+
Write-Output "OK"
|
|
80
|
+
} catch {
|
|
81
|
+
Write-Output "ERROR: $($_.Exception.Message)"
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
function isWindows() {
|
|
86
|
+
return process.platform === "win32";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function checkCAD() {
|
|
90
|
+
if (!isWindows()) {
|
|
91
|
+
return { ok: false, error: "CAD COM automation 仅支持 Windows" };
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const out = execPS(`
|
|
95
|
+
$acad = $null
|
|
96
|
+
try { $acad = [System.Runtime.InteropServices.Marshal]::GetActiveObject("AutoCAD.Application") } catch {}
|
|
97
|
+
if (-not $acad) { $acad = New-Object -ComObject "AutoCAD.Application" }
|
|
98
|
+
try {
|
|
99
|
+
Write-Output "$($acad.Name)|$($acad.Version)"
|
|
100
|
+
} catch { Write-Output "ERROR: $($_.Exception.Message)" }
|
|
101
|
+
`);
|
|
102
|
+
if (out.startsWith("ERROR")) {
|
|
103
|
+
return { ok: false, error: out.replace("ERROR: ", "") };
|
|
104
|
+
}
|
|
105
|
+
const parts = out.split("|");
|
|
106
|
+
return { ok: true, name: parts[0] || "AutoCAD", version: parts[1] || "" };
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { ok: false, error: e.message };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function installAtLisp(appId, profile) {
|
|
113
|
+
if (!isWindows()) {
|
|
114
|
+
console.log(" CAD COM 自动化仅支持 Windows。请手动将安装代码粘贴到 CAD 命令行:");
|
|
115
|
+
console.log(" " + installCode);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
console.log(" 正在启动 CAD...");
|
|
119
|
+
try {
|
|
120
|
+
const out = execPS(INSTALL_PS(resolveAppId(appId), profile));
|
|
121
|
+
if (out.startsWith("ERROR")) {
|
|
122
|
+
console.error(" CAD 错误: " + out.replace("ERROR: ", ""));
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
console.log(" ✓ @lisp 安装成功!");
|
|
126
|
+
return true;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error(" 执行失败: " + e.message);
|
|
129
|
+
console.log(" 请手动将安装代码粘贴到 CAD 命令行:");
|
|
130
|
+
console.log(" " + installCode);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function sendLisp(lispCode, appId, profile) {
|
|
136
|
+
if (!isWindows()) {
|
|
137
|
+
console.log(" CAD COM 自动化仅支持 Windows。");
|
|
138
|
+
return { ok: false, error: "不支持当前平台" };
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const out = execPS(SEND_PS(resolveAppId(appId), profile, lispCode));
|
|
142
|
+
if (out.startsWith("ERROR")) {
|
|
143
|
+
return { ok: false, error: out.replace("ERROR: ", "") };
|
|
144
|
+
}
|
|
145
|
+
return { ok: true, output: out };
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return { ok: false, error: e.message };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function pull(pkgName, appId, profile) {
|
|
152
|
+
if (!isWindows()) {
|
|
153
|
+
console.log(" CAD COM 自动化仅支持 Windows。请手动将安装代码粘贴到 CAD 命令行:");
|
|
154
|
+
console.log(" " + (installCode.replace(/\/@/, "/" + pkgName)));
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
console.log(" 正在启动 CAD...");
|
|
158
|
+
try {
|
|
159
|
+
const lisp = `(progn(@::load-module "pkgman")(@::package-install "${pkgName}"))`;
|
|
160
|
+
const out = execPS(SEND_PS(resolveAppId(appId), profile, lisp));
|
|
161
|
+
if (out.startsWith("ERROR")) {
|
|
162
|
+
console.error(" CAD 错误: " + out.replace("ERROR: ", ""));
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
console.log(" ✓ 包 " + pkgName + " 安装成功!");
|
|
166
|
+
return true;
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.error(" 执行失败: " + e.message);
|
|
169
|
+
console.log(" 请手动在 CAD 命令行执行:");
|
|
170
|
+
console.log(" (@::package-install \"" + pkgName + "\")");
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function remove(pkgName, appId, profile) {
|
|
176
|
+
if (!isWindows()) {
|
|
177
|
+
console.log(" CAD COM 自动化仅支持 Windows。请手动在 CAD 命令行执行:");
|
|
178
|
+
console.log(" (@::package-remove \"" + pkgName + "\")");
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
console.log(" 正在启动 CAD...");
|
|
182
|
+
try {
|
|
183
|
+
const lisp = `(progn(@::package-remove "${pkgName}")(if @::flag-menu-need-update (C:@m)))`;
|
|
184
|
+
const out = execPS(SEND_PS(resolveAppId(appId), profile, lisp));
|
|
185
|
+
if (out.startsWith("ERROR")) {
|
|
186
|
+
console.error(" CAD 错误: " + out.replace("ERROR: ", ""));
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
console.log(" ✓ 包 " + pkgName + " 卸载成功!");
|
|
190
|
+
return true;
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.error(" 执行失败: " + e.message);
|
|
193
|
+
console.log(" 请手动在 CAD 命令行执行:");
|
|
194
|
+
console.log(" (@::package-remove \"" + pkgName + "\")");
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { resolveAppId, checkCAD, installAtLisp, pull, remove, sendLisp, isWindows };
|