diff --git a/LICENSE-graphlib b/LICENSE-graphlib new file mode 100644 index 0000000..7d7dd94 --- /dev/null +++ b/LICENSE-graphlib @@ -0,0 +1,19 @@ +Copyright (c) 2012-2014 Chris Pettitt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.ja.md b/README.ja.md index 10c6e46..5bf105f 100644 --- a/README.ja.md +++ b/README.ja.md @@ -160,7 +160,7 @@ module.exports = ({ config, graph }) => ({ - [Neoloopy](https://forum.obsidian.md/t/neoloopy-think-in-feedback-loops-a-systems-thinking-tool-in-your-vault/115448) は、手で描いた因果ループ図から強化ループ・均衡ループを見つけます。 - strace や [monkeyfs](https://pypi.org/project/monkeyfs/) はファイルの入出力を横取りします。loopfinder の Python のフックも、同じモンキーパッチの手法です。 -レイアウトは [dagre](https://github.com/dagrejs/dagre)(MIT、`LICENSE-dagre`)。アイコンは [Lucide](https://lucide.dev) の公式 SVG を `scripts/gen-icons.js` で改変せずに取り込んでいます(ISC。`moon` と `info` は Feather 由来で MIT。`LICENSE-lucide`)。 +レイアウトは [dagre](https://github.com/dagrejs/dagre)(MIT、`LICENSE-dagre`)と、それに同梱されている [graphlib](https://github.com/dagrejs/graphlib)(MIT、`LICENSE-graphlib`)。アイコンは [Lucide](https://lucide.dev) の公式 SVG を `scripts/gen-icons.js` で改変せずに取り込んでいます(ISC。`moon` と `info` は Feather 由来で MIT。`LICENSE-lucide`)。 ## 支援について diff --git a/README.md b/README.md index d266655..85e6427 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ module.exports = ({ config, graph }) => ({ - strace and [monkeyfs](https://pypi.org/project/monkeyfs/) intercept file I/O; loopfinder's Python hook uses the same monkey-patching approach. -Layout by [dagre](https://github.com/dagrejs/dagre) (MIT, see `LICENSE-dagre`). Icons from [Lucide](https://lucide.dev), vendored unmodified by `scripts/gen-icons.js` (ISC; `moon` and `info` derive from Feather, MIT; see `LICENSE-lucide`). +Layout by [dagre](https://github.com/dagrejs/dagre) (MIT, see `LICENSE-dagre`), which bundles [graphlib](https://github.com/dagrejs/graphlib) (MIT, see `LICENSE-graphlib`). Icons from [Lucide](https://lucide.dev), vendored unmodified by `scripts/gen-icons.js` (ISC; `moon` and `info` derive from Feather, MIT; see `LICENSE-lucide`). ## Support diff --git a/package.json b/package.json index 8db20cc..fa2c949 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "public/", "LICENSE", "LICENSE-dagre", + "LICENSE-graphlib", "LICENSE-lucide", "README.md", "README.ja.md" diff --git a/public/app.js b/public/app.js index 2cbe3a9..bbfb9ee 100644 --- a/public/app.js +++ b/public/app.js @@ -349,7 +349,8 @@ function fillIcons() { async function main() { fillIcons(); graph = host ? await host.load() : await (await fetch('flow.json', { cache: 'no-store' })).json(); - pickLang(graph.lang); + // Inside VS Code the editor's display language comes before the config's; a choice made here wins over both. + pickLang(host?.lang?.() || graph.lang); const select = document.getElementById('flow'); fillFlows(select); // #flow= opens that flow diff --git a/public/index.html b/public/index.html index 753a50f..807fce1 100644 --- a/public/index.html +++ b/public/index.html @@ -58,6 +58,7 @@

· Ko-fi
dagre (MIT) + · graphlib (MIT) · Lucide (ISC) diff --git a/vscode/.vscodeignore b/vscode/.vscodeignore index 10d1f3b..d7d447f 100644 --- a/vscode/.vscodeignore +++ b/vscode/.vscodeignore @@ -1,3 +1,4 @@ scripts/** **/*.vsix **/__pycache__/** +media/icon.svg diff --git a/vscode/README.ja.md b/vscode/README.ja.md new file mode 100644 index 0000000..eb6ce06 --- /dev/null +++ b/vscode/README.ja.md @@ -0,0 +1,37 @@ +# loopfinder for VS Code + +[English](README.md) + +**ワークスペースのフィードバックループを見つける。意図せず作っていたものも含めて。** + +[loopfinder](https://github.com/MotimotiNotch/loopfinder) の CLI と画面を、エディタの中で使えるようにしたものです。 +Node を入れる必要はありません。エディタに入っている実行環境で動きます(Python は `.py` のスクリプトを記録するときだけ要ります)。 + +## 使い方 + +コマンドパレットから: + +1. **loopfinder: このワークスペースを準備** — `loopfinder/`(config.json・flows.json・AGENTS.md)を作ります。 +2. 使っている AI エージェント(Claude Code・Codex・Cursor・Copilot など)に頼みます: + *"Read loopfinder/AGENTS.md and survey this workspace."* + (**loopfinder: AI エージェントへの依頼文をコピー** でクリップボードに入ります。)エージェントが `loopfinder/flows.json` を書きます。 +3. **loopfinder: 図をビルド** — 書き込みを止めた状態でスクリプトを動かして記録し、図を組み立てます。 +4. **loopfinder: 輪を表示** — 画面を開きます。ビルドするたびに自動で描き直します(コマンドパレットからでも、 + 端末で `loopfinder build` を実行しても)。右の一覧のパスを押すと、そのファイルが開きます。 + +画面の言語は、最初はエディタの表示言語に合わせます。画面の上の言語ボタンで切り替えると、そちらが優先されます。 + +## 安全について + +ビルドは**スクリプトを実際に動かします**。書き込みは記録して捨て、通信とほかのプログラムの起動は +`loopfinder.allowNetwork` / `loopfinder.allowSubprocess` を有効にしない限り止めます。記録係であって +サンドボックスではないので、動かしたくないコードは記録しないでください。 + +[信頼](https://code.visualstudio.com/docs/editor/workspace-trust)していないワークスペースでは、準備とビルドは +使えません。ビルド済みの図を見ることはできます。 + +`.loopfinder/`(ビルドの出力)にはファイルのパスが並びます。バージョン管理には入れないでください。 + +## 支援 + +[Ko-fi](https://ko-fi.com/motimotinotch) · MIT License · dagre と graphlib(MIT)、Lucide のアイコン(ISC)を同梱 diff --git a/vscode/README.md b/vscode/README.md index 20de74f..08abec8 100644 --- a/vscode/README.md +++ b/vscode/README.md @@ -1,5 +1,7 @@ # loopfinder for VS Code +[日本語](https://github.com/MotimotiNotch/loopfinder/blob/main/vscode/README.ja.md) + **Find the feedback loops in your workspace — including the ones you never meant to build.** The [loopfinder](https://github.com/MotimotiNotch/loopfinder) CLI and viewer, inside the editor. No Node install @@ -17,6 +19,8 @@ From the Command Palette: 4. **loopfinder: Show the loops** — opens the viewer. It redraws by itself after every build, from the palette or from `loopfinder build` in a terminal. Click a path in the side pane to open the file. +The viewer starts in the editor's display language. Picking a language with the button at the top of the viewer wins over it. + ## Safety Building **really runs your scripts**. Writes are recorded and dropped; network and subprocesses are blocked @@ -30,4 +34,4 @@ build are off; you can still view a graph that is already built. ## Support -[Ko-fi](https://ko-fi.com/motimotinotch) · MIT License · bundles dagre (MIT) and Lucide icons (ISC) +[Ko-fi](https://ko-fi.com/motimotinotch) · MIT License · bundles dagre and graphlib (MIT) and Lucide icons (ISC) diff --git a/vscode/extension.js b/vscode/extension.js index 09d26ff..31ad8d3 100644 --- a/vscode/extension.js +++ b/vscode/extension.js @@ -17,6 +17,8 @@ const PUBLIC = path.join(CORE, 'public'); const { loadConfig } = require(path.join(CORE, 'src', 'config')); const PROMPT = 'Read loopfinder/AGENTS.md and survey this workspace.'; +// Messages go through vscode.l10n (l10n/bundle.l10n.ja.json); the menus through package.nls.*.json. +const t = (message, ...args) => vscode.l10n.t(message, ...args); const configOf = folder => path.join(folder, 'loopfinder', 'config.json'); let output; @@ -27,16 +29,16 @@ async function pickFolder(needConfig) { const folders = (vscode.workspace.workspaceFolders || []).filter(f => f.uri.scheme === 'file'); const candidates = needConfig ? folders.filter(f => fs.existsSync(configOf(f.uri.fsPath))) : folders; if (!candidates.length) { - if (!folders.length) vscode.window.showWarningMessage('loopfinder: open a folder first.'); + if (!folders.length) vscode.window.showWarningMessage(t('loopfinder: open a folder first.')); else { - const init = 'Set up'; - const choice = await vscode.window.showWarningMessage('loopfinder: no loopfinder/config.json in this workspace.', ...(vscode.workspace.isTrusted ? [init] : [])); + const init = t('Set up'); + const choice = await vscode.window.showWarningMessage(t('loopfinder: no loopfinder/config.json in this workspace.'), ...(vscode.workspace.isTrusted ? [init] : [])); if (choice === init) vscode.commands.executeCommand('loopfinder.init'); } return null; } if (candidates.length === 1) return candidates[0].uri.fsPath; - const pick = await vscode.window.showQuickPick(candidates.map(f => ({ label: f.name, description: f.uri.fsPath })), { placeHolder: 'Which folder?' }); + const pick = await vscode.window.showQuickPick(candidates.map(f => ({ label: f.name, description: f.uri.fsPath })), { placeHolder: t('Which folder?') }); return pick ? pick.description : null; } @@ -57,7 +59,7 @@ function runCli(folder, args, token) { // `enablement` only greys out the menus; a command run from code still arrives here. function trusted() { if (vscode.workspace.isTrusted) return true; - vscode.window.showWarningMessage('loopfinder: trust this workspace first. Building runs its scripts.'); + vscode.window.showWarningMessage(t('loopfinder: trust this workspace first. Building runs its scripts.')); return false; } @@ -66,16 +68,16 @@ async function cmdInit() { const folder = await pickFolder(false); if (!folder) return; const code = await runCli(folder, ['init']); - if (code !== 0) { showFailure('init'); return; } - const copy = 'Copy the request'; + if (code !== 0) { showFailure(t('loopfinder: init failed.')); return; } + const copy = t('Copy the request'); const choice = await vscode.window.showInformationMessage( - `loopfinder: set up. Next, ask your AI agent: "${PROMPT}" It writes loopfinder/flows.json; then build.`, copy); + t('loopfinder: set up. Next, ask your AI agent: "{0}" It writes loopfinder/flows.json; then build.', PROMPT), copy); if (choice === copy) cmdCopyPrompt(); } function cmdCopyPrompt() { vscode.env.clipboard.writeText(PROMPT); - vscode.window.setStatusBarMessage('loopfinder: copied the request for your AI agent', 4000); + vscode.window.setStatusBarMessage(t('loopfinder: copied the request for your AI agent'), 4000); } async function cmdBuild() { @@ -87,16 +89,16 @@ async function cmdBuild() { if (settings.get('allowNetwork')) args.push('--allow-network'); if (settings.get('allowSubprocess')) args.push('--allow-subprocess'); const code = await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: 'loopfinder: tracing the scripts and building the graph', cancellable: true }, + { location: vscode.ProgressLocation.Notification, title: t('loopfinder: tracing the scripts and building the graph'), cancellable: true }, (_, token) => runCli(folder, args, token)); - if (code !== 0) { showFailure('build'); return; } + if (code !== 0) { showFailure(t('loopfinder: build failed.')); return; } // An open viewer picks the new graph up through its watcher. if (!viewer || viewer.folder !== folder) show(folder); } -async function showFailure(what) { - const open = 'Show output'; - if (await vscode.window.showErrorMessage(`loopfinder: ${what} failed.`, open) === open) output.show(); +async function showFailure(message) { + const open = t('Show output'); + if (await vscode.window.showErrorMessage(message, open) === open) output.show(); } async function cmdShow() { @@ -111,8 +113,8 @@ function readGraph(config) { async function show(folder) { const config = loadConfig(configOf(folder)); if (!fs.existsSync(config.output)) { - const build = 'Build'; - const choice = await vscode.window.showInformationMessage('loopfinder: no graph yet. Build it first.', ...(vscode.workspace.isTrusted ? [build] : [])); + const build = t('Build'); + const choice = await vscode.window.showInformationMessage(t('loopfinder: no graph yet. Build it first.'), ...(vscode.workspace.isTrusted ? [build] : [])); if (choice === build) cmdBuild(); return; } @@ -128,7 +130,8 @@ async function show(folder) { const send = () => { const graph = readGraph(config); // A half-written file fails to parse; the watcher fires again when the write finishes. - if (graph) panel.webview.postMessage({ type: 'graph', graph }); + // The editor's display language is the viewer's default language (a choice made in the viewer wins). + if (graph) panel.webview.postMessage({ type: 'graph', graph, lang: vscode.env.language }); }; panel.webview.onDidReceiveMessage(m => { if (m?.type === 'ready') send(); @@ -158,7 +161,7 @@ function openPath(config, p) { try { stat = fs.statSync(file); } catch { /* not there */ } if (stat?.isFile()) vscode.commands.executeCommand('vscode.open', vscode.Uri.file(file), { preview: true, viewColumn: vscode.ViewColumn.Beside }); else if (stat?.isDirectory()) vscode.commands.executeCommand('revealInExplorer', vscode.Uri.file(file)); - else vscode.window.showInformationMessage(`loopfinder: not a single file: ${p}`); + else vscode.window.showInformationMessage(t('loopfinder: not a single file: {0}', p)); } // The viewer's own index.html, with its files served through the webview and a CSP that allows only them. diff --git a/vscode/icon.png b/vscode/icon.png new file mode 100644 index 0000000..3a431e8 Binary files /dev/null and b/vscode/icon.png differ diff --git a/vscode/l10n/bundle.l10n.ja.json b/vscode/l10n/bundle.l10n.ja.json new file mode 100644 index 0000000..9b6ff92 --- /dev/null +++ b/vscode/l10n/bundle.l10n.ja.json @@ -0,0 +1,17 @@ +{ + "loopfinder: open a folder first.": "loopfinder: 先にフォルダを開いてください。", + "Set up": "準備する", + "loopfinder: no loopfinder/config.json in this workspace.": "loopfinder: このワークスペースには loopfinder/config.json がありません。", + "Which folder?": "どのフォルダで動かしますか?", + "loopfinder: trust this workspace first. Building runs its scripts.": "loopfinder: 先にこのワークスペースを信頼してください。ビルドは中のスクリプトを実際に動かします。", + "loopfinder: init failed.": "loopfinder: 準備に失敗しました。", + "Copy the request": "依頼文をコピー", + "loopfinder: set up. Next, ask your AI agent: \"{0}\" It writes loopfinder/flows.json; then build.": "loopfinder: 準備しました。次に AI エージェントに「{0}」と頼んでください。エージェントが loopfinder/flows.json を書いたら、ビルドします。", + "loopfinder: copied the request for your AI agent": "loopfinder: AI エージェントへの依頼文をコピーしました", + "loopfinder: tracing the scripts and building the graph": "loopfinder: スクリプトを記録して図を組み立てています", + "loopfinder: build failed.": "loopfinder: ビルドに失敗しました。", + "Show output": "出力を見る", + "Build": "ビルド", + "loopfinder: no graph yet. Build it first.": "loopfinder: まだ図がありません。先にビルドしてください。", + "loopfinder: not a single file: {0}": "loopfinder: 1つのファイルではありません: {0}" +} diff --git a/vscode/media/host.js b/vscode/media/host.js index 0392c56..bf8b01c 100644 --- a/vscode/media/host.js +++ b/vscode/media/host.js @@ -1,14 +1,17 @@ // The bridge between the viewer (public/app.js) and the extension. Loaded before app.js, which looks for // window.loopfinderHost: the graph comes by message instead of fetch('flow.json'), a rebuilt graph comes -// the same way, a path click opens the file in the editor, and "auto" theme follows the editor's theme. +// the same way, a path click opens the file in the editor, "auto" theme follows the editor's theme, and the +// editor's display language is the default language. (() => { const vscode = acquireVsCodeApi(); let waiting = null; // load() before the first graph arrived let onGraph = null; // later graphs (after a build) + let lang = null; // the editor's display language, e.g. "ja" or "en-us" window.addEventListener('message', e => { const m = e.data; if (!m || m.type !== 'graph') return; + if (typeof m.lang === 'string') lang = m.lang.split('-')[0]; if (waiting) { const resolve = waiting; waiting = null; resolve(m.graph); } else if (onGraph) onGraph(m.graph); }); @@ -27,6 +30,7 @@ }, onGraph(fn) { onGraph = fn; }, open(path) { vscode.postMessage({ type: 'open', path }); }, + lang: () => lang, theme, onTheme(fn) { new MutationObserver(fn).observe(document.body, { attributes: true, attributeFilter: ['class'] }); }, }; diff --git a/vscode/media/icon.svg b/vscode/media/icon.svg new file mode 100644 index 0000000..c74a76c --- /dev/null +++ b/vscode/media/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/vscode/package.json b/vscode/package.json index 2c06f65..d3de91b 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1,7 +1,7 @@ { "name": "loopfinder", - "displayName": "loopfinder", - "description": "Find the feedback loops in your workspace: trace your scripts with writes blocked, add the human/AI steps, and see every loop, including the ones you never declared.", + "displayName": "%displayName%", + "description": "%description%", "version": "0.1.0", "publisher": "motimotinotch", "license": "MIT", @@ -15,10 +15,6 @@ "engines": { "vscode": "^1.85.0" }, - "categories": [ - "Visualization", - "Other" - ], "keywords": [ "feedback-loop", "data-flow", @@ -46,24 +42,24 @@ "commands": [ { "command": "loopfinder.show", - "title": "Show the loops", + "title": "%command.show%", "category": "loopfinder" }, { "command": "loopfinder.build", - "title": "Build the graph (runs the traced scripts)", + "title": "%command.build%", "category": "loopfinder", "enablement": "isWorkspaceTrusted" }, { "command": "loopfinder.init", - "title": "Set up this workspace", + "title": "%command.init%", "category": "loopfinder", "enablement": "isWorkspaceTrusted" }, { "command": "loopfinder.copyPrompt", - "title": "Copy the request for your AI agent", + "title": "%command.copyPrompt%", "category": "loopfinder" } ], @@ -73,18 +69,18 @@ "loopfinder.allowNetwork": { "type": "boolean", "default": false, - "markdownDescription": "Let traced scripts reach the network. It is always recorded; blocked by default, so the script sees an error." + "markdownDescription": "%config.allowNetwork%" }, "loopfinder.allowSubprocess": { "type": "boolean", "default": false, - "markdownDescription": "Let traced scripts start other programs. Blocked by default." + "markdownDescription": "%config.allowSubprocess%" }, "loopfinder.layoutBudget": { "type": "number", "default": 15, "minimum": 0, - "markdownDescription": "Seconds spent searching each flow's layout during a build." + "markdownDescription": "%config.layoutBudget%" } } } @@ -92,5 +88,11 @@ "scripts": { "prepare-core": "node scripts/prepare.js", "package": "node scripts/prepare.js && npx @vscode/vsce package --no-dependencies" - } + }, + "icon": "icon.png", + "categories": [ + "Visualization", + "Other" + ], + "l10n": "./l10n" } diff --git a/vscode/package.nls.ja.json b/vscode/package.nls.ja.json new file mode 100644 index 0000000..151c223 --- /dev/null +++ b/vscode/package.nls.ja.json @@ -0,0 +1,11 @@ +{ + "displayName": "loopfinder", + "description": "ワークスペースのフィードバックループを見つける。書き込みを止めてスクリプトを記録し、人と AI の段を足して、宣言していないものも含めてすべての輪を数える。", + "command.show": "輪を表示", + "command.build": "図をビルド(記録するスクリプトを実際に動かす)", + "command.init": "このワークスペースを準備", + "command.copyPrompt": "AI エージェントへの依頼文をコピー", + "config.allowNetwork": "記録するスクリプトにネットワークへの接続を許す。接続は常に記録される。既定では止めるので、スクリプトにはエラーが返る。", + "config.allowSubprocess": "記録するスクリプトにほかのプログラムの起動を許す。既定では止める。", + "config.layoutBudget": "ビルドのとき、流れごとに並び順を探す秒数。" +} diff --git a/vscode/package.nls.json b/vscode/package.nls.json new file mode 100644 index 0000000..2c0669f --- /dev/null +++ b/vscode/package.nls.json @@ -0,0 +1,11 @@ +{ + "displayName": "loopfinder", + "description": "Find the feedback loops in your workspace: trace your scripts with writes blocked, add the human/AI steps, and see every loop, including the ones you never declared.", + "command.show": "Show the loops", + "command.build": "Build the graph (runs the traced scripts)", + "command.init": "Set up this workspace", + "command.copyPrompt": "Copy the request for your AI agent", + "config.allowNetwork": "Let traced scripts reach the network. It is always recorded; blocked by default, so the script sees an error.", + "config.allowSubprocess": "Let traced scripts start other programs. Blocked by default.", + "config.layoutBudget": "Seconds spent searching each flow's layout during a build." +} diff --git a/vscode/scripts/icon.js b/vscode/scripts/icon.js new file mode 100644 index 0000000..04066c1 --- /dev/null +++ b/vscode/scripts/icon.js @@ -0,0 +1,33 @@ +// Renders media/icon.svg to icon.png (256px, transparent corners) with headless Chrome or Edge. +// Run by hand when the SVG changes; icon.png is committed. +'use strict'; +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { spawnSync } = require('child_process'); +const { pathToFileURL } = require('url'); + +const SIZE = 256; +const ext = path.join(__dirname, '..'); +const browsers = [ + process.env.CHROME, + 'C:/Program Files/Google/Chrome/Application/chrome.exe', + 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/usr/bin/google-chrome', + '/usr/bin/chromium', +].filter(Boolean); +const browser = browsers.find(b => fs.existsSync(b)); +if (!browser) { console.error('no Chrome/Edge found; set CHROME='); process.exit(1); } + +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'loopfinder-icon-')); +const svg = fs.readFileSync(path.join(ext, 'media', 'icon.svg'), 'utf8').replace(/\s*/, ''); +const page = path.join(tmp, 'icon.html'); +fs.writeFileSync(page, `${svg}`); +const out = path.join(ext, 'icon.png'); +const r = spawnSync(browser, ['--headless', '--disable-gpu', '--hide-scrollbars', '--default-background-color=00000000', + `--window-size=${SIZE},${SIZE}`, `--screenshot=${out}`, `--user-data-dir=${path.join(tmp, 'profile')}`, pathToFileURL(page).href], + { encoding: 'utf8', timeout: 60000 }); +fs.rmSync(tmp, { recursive: true, force: true }); +if (!fs.existsSync(out)) { console.error(r.stderr); process.exit(1); } +console.log(`wrote ${path.relative(process.cwd(), out)}`); diff --git a/vscode/scripts/prepare.js b/vscode/scripts/prepare.js index d94c1a2..f5c9762 100644 --- a/vscode/scripts/prepare.js +++ b/vscode/scripts/prepare.js @@ -12,7 +12,7 @@ fs.rmSync(core, { recursive: true, force: true }); for (const name of ['bin', 'src', 'public', 'trace', 'package.json']) { fs.cpSync(path.join(repo, name), path.join(core, name), { recursive: true, filter: src => !src.includes('__pycache__') }); } -for (const name of ['LICENSE', 'LICENSE-dagre', 'LICENSE-lucide']) { +for (const name of ['LICENSE', 'LICENSE-dagre', 'LICENSE-graphlib', 'LICENSE-lucide']) { fs.copyFileSync(path.join(repo, name), path.join(ext, name)); }