pinokiod 7.3.1 → 7.3.4
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/kernel/api/github/index.js +444 -0
- package/kernel/api/index.js +199 -11
- package/kernel/api/process/index.js +124 -44
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/api/uri/index.js +51 -0
- package/kernel/bin/git.js +9 -10
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/bin/zip.js +9 -1
- package/kernel/connect/providers/github/README.md +5 -4
- package/kernel/environment.js +195 -92
- package/kernel/git.js +126 -19
- package/kernel/gitconfig_template +7 -0
- package/kernel/gpu/amd.js +72 -0
- package/kernel/gpu/apple.js +8 -0
- package/kernel/gpu/common.js +12 -0
- package/kernel/gpu/intel.js +47 -0
- package/kernel/gpu/nvidia.js +8 -0
- package/kernel/index.js +11 -1
- package/kernel/managed_skills.js +871 -0
- package/kernel/plugin.js +6 -58
- package/kernel/plugin_sources.js +316 -0
- package/kernel/resource_usage/gpu.js +349 -0
- package/kernel/resource_usage/index.js +322 -0
- package/kernel/resource_usage/macos_footprint.js +197 -0
- package/kernel/resource_usage/preferences.js +92 -0
- package/kernel/resource_usage/process_tree.js +303 -0
- package/kernel/scripts/git/create +4 -4
- package/kernel/scripts/git/fork +7 -8
- package/kernel/shell.js +23 -2
- package/kernel/shells.js +41 -0
- package/kernel/sysinfo.js +62 -9
- package/kernel/util.js +60 -0
- package/package.json +1 -1
- package/server/index.js +984 -156
- package/server/lib/app_log_report.js +543 -0
- package/server/lib/content_validation.js +55 -33
- package/server/lib/launcher_instruction_bootstrap.js +4 -96
- package/server/lib/terminal_session_helpers.js +0 -3
- package/server/public/common.js +77 -31
- package/server/public/create-launcher.js +4 -32
- package/server/public/logs.js +1428 -0
- package/server/public/nav.js +7 -0
- package/server/public/plugin-detail.js +93 -10
- package/server/public/privacy_filter_worker.js +391 -0
- package/server/public/style.css +1104 -154
- package/server/public/task-launcher.js +8 -29
- package/server/public/universal-launcher.css +8 -6
- package/server/public/universal-launcher.js +3 -27
- package/server/routes/apps.js +195 -1
- package/server/views/app.ejs +3041 -717
- package/server/views/autolaunch.ejs +917 -0
- package/server/views/bootstrap.ejs +7 -1
- package/server/views/d.ejs +408 -65
- package/server/views/editor.ejs +85 -19
- package/server/views/index.ejs +661 -111
- package/server/views/init/index.ejs +1 -1
- package/server/views/install.ejs +1 -1
- package/server/views/logs.ejs +164 -86
- package/server/views/net.ejs +7 -1
- package/server/views/partials/d_terminal_column.ejs +2 -2
- package/server/views/partials/d_terminal_options.ejs +0 -8
- package/server/views/partials/fs_status.ejs +47 -0
- package/server/views/partials/home_action_modal.ejs +86 -0
- package/server/views/partials/home_run_menu.ejs +87 -0
- package/server/views/partials/main_sidebar.ejs +2 -0
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/plugin_detail.ejs +19 -4
- package/server/views/plugins.ejs +201 -3
- package/server/views/pre.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/shell.ejs +40 -18
- package/server/views/skills.ejs +506 -0
- package/server/views/terminal.ejs +45 -19
- package/spec/INSTRUCTION_SYNC.md +20 -10
- package/system/plugin/antigravity-cli/antigravity.png +0 -0
- package/system/plugin/antigravity-cli/common.js +155 -0
- package/system/plugin/antigravity-cli/install.js +272 -0
- package/system/plugin/antigravity-cli/pinokio.js +13 -0
- package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
- package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
- package/system/plugin/claude/claude.png +0 -0
- package/system/plugin/claude/pinokio.js +47 -0
- package/system/plugin/claude-auto/claude.png +0 -0
- package/system/plugin/claude-auto/pinokio.js +58 -0
- package/system/plugin/claude-desktop/icon.jpeg +0 -0
- package/system/plugin/claude-desktop/pinokio.js +23 -0
- package/system/plugin/codex/openai.webp +0 -0
- package/system/plugin/codex/pinokio.js +42 -0
- package/system/plugin/codex-auto/openai.webp +0 -0
- package/system/plugin/codex-auto/pinokio.js +49 -0
- package/system/plugin/codex-desktop/icon.png +0 -0
- package/system/plugin/codex-desktop/pinokio.js +23 -0
- package/system/plugin/crush/crush.png +0 -0
- package/system/plugin/crush/pinokio.js +15 -0
- package/system/plugin/cursor/cursor.jpeg +0 -0
- package/system/plugin/cursor/pinokio.js +23 -0
- package/system/plugin/qwen/pinokio.js +34 -0
- package/system/plugin/qwen/qwen.png +0 -0
- package/system/plugin/vscode/pinokio.js +20 -0
- package/system/plugin/vscode/vscode.png +0 -0
- package/system/plugin/windsurf/pinokio.js +23 -0
- package/system/plugin/windsurf/windsurf.png +0 -0
- package/test/antigravity-cli-plugin.test.js +185 -0
- package/test/app-api.test.js +239 -0
- package/test/app-log-report.test.js +67 -0
- package/test/environment-cache-preflight.test.js +98 -0
- package/test/git-bin.test.js +59 -0
- package/test/git-defaults.test.js +150 -0
- package/test/github-api.test.js +158 -0
- package/test/github-connection.test.js +117 -0
- package/test/huggingface-bin.test.js +25 -0
- package/test/managed-skills.test.js +351 -0
- package/test/plugin-action-functions.test.js +337 -0
- package/test/plugin-dev-iframe.test.js +17 -0
- package/test/plugin-sources.test.js +203 -0
- package/test/privacy-filter-worker-heuristics.test.js +69 -0
- package/test/process-wait.test.js +169 -0
- package/test/script-api.test.js +97 -0
- package/test/shell-api.test.js +134 -0
- package/test/shell-run-template.test.js +209 -0
- package/test/storage-api.test.js +137 -0
- package/test/uri-api.test.js +100 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const test = require("node:test")
|
|
2
|
+
const assert = require("node:assert/strict")
|
|
3
|
+
const fs = require("node:fs/promises")
|
|
4
|
+
const fssync = require("node:fs")
|
|
5
|
+
const os = require("node:os")
|
|
6
|
+
const path = require("node:path")
|
|
7
|
+
const Environment = require("../kernel/environment")
|
|
8
|
+
|
|
9
|
+
const CACHE_KEYS = ["TMP", "TEMP", "TMPDIR", "PIP_TMPDIR", "UV_CACHE_DIR", "PIP_CACHE_DIR"]
|
|
10
|
+
|
|
11
|
+
const createKernel = (homedir) => ({
|
|
12
|
+
homedir,
|
|
13
|
+
kv: {
|
|
14
|
+
get: async () => null
|
|
15
|
+
},
|
|
16
|
+
exists: (...args) => new Promise((resolve) => {
|
|
17
|
+
fssync.access(path.resolve(homedir, ...args), fssync.constants.F_OK, (error) => {
|
|
18
|
+
resolve(!error)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const withTempHome = async (fn) => {
|
|
24
|
+
const homedir = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-cache-preflight-"))
|
|
25
|
+
try {
|
|
26
|
+
await fs.writeFile(path.join(homedir, "ENVIRONMENT"), "OTHER=value\n")
|
|
27
|
+
await fn(homedir)
|
|
28
|
+
} finally {
|
|
29
|
+
await fs.rm(homedir, { recursive: true, force: true })
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("ensurePinokioCacheDirs writes managed cache env defaults and probes directories", async () => {
|
|
34
|
+
await withTempHome(async (homedir) => {
|
|
35
|
+
const kernel = createKernel(homedir)
|
|
36
|
+
const result = await Environment.ensurePinokioCacheDirs(kernel, { throwOnFailure: true })
|
|
37
|
+
|
|
38
|
+
assert.equal(result.errors.length, 0)
|
|
39
|
+
assert.equal(kernel.cacheDirErrors.length, 0)
|
|
40
|
+
assert.equal(kernel.cacheDirPreflight.length, CACHE_KEYS.length)
|
|
41
|
+
|
|
42
|
+
const env = await fs.readFile(path.join(homedir, "ENVIRONMENT"), "utf8")
|
|
43
|
+
for (const key of CACHE_KEYS) {
|
|
44
|
+
assert.match(env, new RegExp(`^${key}=\\.\\/cache\\/${key}$`, "m"))
|
|
45
|
+
const stats = await fs.stat(path.join(homedir, "cache", key))
|
|
46
|
+
assert.equal(stats.isDirectory(), true)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("ensurePinokioCacheDirs repairs managed cache targets that are not writable directories", async () => {
|
|
52
|
+
await withTempHome(async (homedir) => {
|
|
53
|
+
await fs.mkdir(path.join(homedir, "cache"), { recursive: true })
|
|
54
|
+
await fs.writeFile(path.join(homedir, "cache", "TMP"), "not a directory")
|
|
55
|
+
|
|
56
|
+
const kernel = createKernel(homedir)
|
|
57
|
+
const result = await Environment.ensurePinokioCacheDirs(kernel, { throwOnFailure: true })
|
|
58
|
+
const tmpResult = result.results.find((item) => item.key === "TMP")
|
|
59
|
+
|
|
60
|
+
assert.equal(result.errors.length, 0)
|
|
61
|
+
assert.equal(tmpResult.repaired, true)
|
|
62
|
+
const stats = await fs.stat(path.join(homedir, "cache", "TMP"))
|
|
63
|
+
assert.equal(stats.isDirectory(), true)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("requirements ignores malformed pre metadata without breaking launcher checks", async () => {
|
|
68
|
+
await withTempHome(async (homedir) => {
|
|
69
|
+
const appDir = path.join(homedir, "api", "demo")
|
|
70
|
+
await fs.mkdir(appDir, { recursive: true })
|
|
71
|
+
await fs.writeFile(path.join(appDir, "ENVIRONMENT"), "EXISTING=from-app\n")
|
|
72
|
+
const kernel = createKernel(homedir)
|
|
73
|
+
|
|
74
|
+
const missingArray = await Environment.requirements({ pre: "bad" }, appDir, kernel)
|
|
75
|
+
assert.deepEqual(missingArray, {
|
|
76
|
+
items: [],
|
|
77
|
+
requires_instantiation: false
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const checked = await Environment.requirements({
|
|
81
|
+
pre: [
|
|
82
|
+
null,
|
|
83
|
+
"bad",
|
|
84
|
+
{ env: 5 },
|
|
85
|
+
{ env: "EXISTING", host: "::bad host::" },
|
|
86
|
+
{ key: "MISSING", default: "fallback" }
|
|
87
|
+
]
|
|
88
|
+
}, appDir, kernel)
|
|
89
|
+
|
|
90
|
+
assert.equal(checked.items.length, 2)
|
|
91
|
+
assert.equal(checked.items[0].env, "EXISTING")
|
|
92
|
+
assert.equal(checked.items[0].host, "")
|
|
93
|
+
assert.equal(checked.items[0].val, "from-app")
|
|
94
|
+
assert.equal(checked.items[1].env, "MISSING")
|
|
95
|
+
assert.equal(checked.items[1].val, "fallback")
|
|
96
|
+
assert.equal(checked.requires_instantiation, true)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const path = require('node:path')
|
|
3
|
+
const test = require('node:test')
|
|
4
|
+
|
|
5
|
+
const GitBin = require('../kernel/bin/git')
|
|
6
|
+
|
|
7
|
+
function createGitBin(platform, packages) {
|
|
8
|
+
const git = new GitBin()
|
|
9
|
+
git.kernel = {
|
|
10
|
+
platform,
|
|
11
|
+
homedir: '/tmp/pinokio',
|
|
12
|
+
path: (...parts) => path.join('/tmp/pinokio', ...parts),
|
|
13
|
+
bin: {
|
|
14
|
+
installed: {
|
|
15
|
+
conda: new Set(packages),
|
|
16
|
+
conda_versions: { git: '2.51.0' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
return git
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('Git bin installs Git Credential Manager with the Git bundle', () => {
|
|
24
|
+
assert.equal(
|
|
25
|
+
createGitBin('darwin', []).cmd(),
|
|
26
|
+
'git=2.51.0 git-lfs git-credential-manager=2.7.3 gh=2.82.1'
|
|
27
|
+
)
|
|
28
|
+
assert.equal(
|
|
29
|
+
createGitBin('linux', []).cmd(),
|
|
30
|
+
'git=2.51.0 git-lfs git-credential-manager=2.7.3 gh=2.82.1'
|
|
31
|
+
)
|
|
32
|
+
assert.equal(
|
|
33
|
+
createGitBin('win32', []).cmd(),
|
|
34
|
+
'git=2.51.0 git-lfs git-credential-manager=2.7.3 gh=2.82.1 m2-base'
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('Git bin readiness requires Git Credential Manager and GitHub CLI', async () => {
|
|
39
|
+
assert.equal(
|
|
40
|
+
await createGitBin('darwin', ['git', 'git-lfs']).installed(),
|
|
41
|
+
false
|
|
42
|
+
)
|
|
43
|
+
assert.equal(
|
|
44
|
+
await createGitBin('darwin', ['git', 'git-lfs', 'git-credential-manager']).installed(),
|
|
45
|
+
false
|
|
46
|
+
)
|
|
47
|
+
assert.equal(
|
|
48
|
+
await createGitBin('darwin', ['git', 'git-lfs', 'git-credential-manager', 'gh']).installed(),
|
|
49
|
+
true
|
|
50
|
+
)
|
|
51
|
+
assert.equal(
|
|
52
|
+
await createGitBin('win32', ['git', 'git-lfs', 'git-credential-manager', 'gh']).installed(),
|
|
53
|
+
false
|
|
54
|
+
)
|
|
55
|
+
assert.equal(
|
|
56
|
+
await createGitBin('win32', ['git', 'git-lfs', 'git-credential-manager', 'gh', 'm2-base']).installed(),
|
|
57
|
+
true
|
|
58
|
+
)
|
|
59
|
+
})
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const { execFile } = require('node:child_process')
|
|
3
|
+
const fs = require('node:fs/promises')
|
|
4
|
+
const os = require('node:os')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const test = require('node:test')
|
|
7
|
+
const { promisify } = require('node:util')
|
|
8
|
+
const ini = require('ini')
|
|
9
|
+
|
|
10
|
+
const KernelGit = require('../kernel/git')
|
|
11
|
+
const execFileAsync = promisify(execFile)
|
|
12
|
+
|
|
13
|
+
async function withTempHome(fn) {
|
|
14
|
+
const homedir = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-git-defaults-'))
|
|
15
|
+
try {
|
|
16
|
+
await fn(homedir)
|
|
17
|
+
} finally {
|
|
18
|
+
await fs.rm(homedir, { recursive: true, force: true })
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createKernel(homedir) {
|
|
23
|
+
return {
|
|
24
|
+
homedir,
|
|
25
|
+
path: (...parts) => path.resolve(homedir, ...parts),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function githubCredentialSection(config) {
|
|
30
|
+
return config['credential "https://github'] && config['credential "https://github']['com"']
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('Git defaults configure Git Credential Manager for new Pinokio gitconfig files', async () => {
|
|
34
|
+
await withTempHome(async (homedir) => {
|
|
35
|
+
const git = new KernelGit(createKernel(homedir))
|
|
36
|
+
|
|
37
|
+
await git.ensureDefaults()
|
|
38
|
+
|
|
39
|
+
const config = ini.parse(await fs.readFile(path.join(homedir, 'gitconfig'), 'utf8'))
|
|
40
|
+
assert.equal(config.credential.helper, 'manager')
|
|
41
|
+
assert.equal(config.credential.gitHubAuthModes, 'oauth')
|
|
42
|
+
assert.equal(config.credential.namespace, 'pinokio')
|
|
43
|
+
assert.equal(githubCredentialSection(config).helper, 'manager')
|
|
44
|
+
assert.equal(githubCredentialSection(config).provider, 'github')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('Git defaults migrate the legacy gh GitHub helper to Git Credential Manager', async () => {
|
|
49
|
+
await withTempHome(async (homedir) => {
|
|
50
|
+
await fs.writeFile(path.join(homedir, 'gitconfig'), [
|
|
51
|
+
'[credential "https://github.com"]',
|
|
52
|
+
' helper = !gh auth git-credential',
|
|
53
|
+
'[user]',
|
|
54
|
+
' name = custom',
|
|
55
|
+
' email = custom@example.test',
|
|
56
|
+
'',
|
|
57
|
+
].join('\n'))
|
|
58
|
+
const git = new KernelGit(createKernel(homedir))
|
|
59
|
+
|
|
60
|
+
await git.ensureDefaults()
|
|
61
|
+
|
|
62
|
+
const config = ini.parse(await fs.readFile(path.join(homedir, 'gitconfig'), 'utf8'))
|
|
63
|
+
assert.equal(config.credential.helper, 'manager')
|
|
64
|
+
assert.equal(config.credential.gitHubAuthModes, 'oauth')
|
|
65
|
+
assert.equal(config.credential.namespace, 'pinokio')
|
|
66
|
+
assert.equal(githubCredentialSection(config).helper, 'manager')
|
|
67
|
+
assert.equal(githubCredentialSection(config).provider, 'github')
|
|
68
|
+
assert.equal(config.user.name, 'custom')
|
|
69
|
+
assert.equal(config.user.email, 'custom@example.test')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('Git defaults reset credential sections to built-in defaults before Git reads config', async () => {
|
|
74
|
+
await withTempHome(async (homedir) => {
|
|
75
|
+
const staleHelpers = [
|
|
76
|
+
'!custom-helper auth git-credential',
|
|
77
|
+
'!gh auth git-credential',
|
|
78
|
+
'!gh.exe auth git-credential',
|
|
79
|
+
`!'${path.win32.join('Z:\\', 'Any Folder', 'bin', 'gh.exe')}' auth git-credential`,
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
for (const [index, helper] of staleHelpers.entries()) {
|
|
83
|
+
const caseHome = path.join(homedir, `case-${index}`)
|
|
84
|
+
await fs.mkdir(caseHome)
|
|
85
|
+
const gitconfigPath = path.join(caseHome, 'gitconfig')
|
|
86
|
+
await fs.writeFile(gitconfigPath, [
|
|
87
|
+
'[credential "https://github.com"]',
|
|
88
|
+
` helper = ${helper}`,
|
|
89
|
+
' provider = github',
|
|
90
|
+
'[credential "https://gist.github.com"]',
|
|
91
|
+
` helper = ${helper}`,
|
|
92
|
+
'[credential "https://enterprise.example"]',
|
|
93
|
+
` helper = ${helper}`,
|
|
94
|
+
'[credential]',
|
|
95
|
+
` helper = ${helper}`,
|
|
96
|
+
' gitHubAuthModes = device',
|
|
97
|
+
' namespace = stale',
|
|
98
|
+
'[user]',
|
|
99
|
+
' name = custom',
|
|
100
|
+
' email = custom@example.test',
|
|
101
|
+
'',
|
|
102
|
+
].join('\n'))
|
|
103
|
+
const git = new KernelGit(createKernel(caseHome))
|
|
104
|
+
|
|
105
|
+
await git.ensureDefaults()
|
|
106
|
+
|
|
107
|
+
const content = await fs.readFile(gitconfigPath, 'utf8')
|
|
108
|
+
assert.doesNotMatch(content, /git-credential/i)
|
|
109
|
+
const { stdout } = await execFileAsync('git', ['config', '--file', gitconfigPath, '--list'])
|
|
110
|
+
assert.match(stdout, /credential\.helper=manager/)
|
|
111
|
+
assert.match(stdout, /credential\.githubauthmodes=oauth/)
|
|
112
|
+
assert.match(stdout, /credential\.namespace=pinokio/)
|
|
113
|
+
assert.match(stdout, /credential\.https:\/\/github\.com\.helper=manager/)
|
|
114
|
+
assert.doesNotMatch(stdout, /credential\.https:\/\/gist\.github\.com/)
|
|
115
|
+
assert.doesNotMatch(stdout, /credential\.https:\/\/enterprise\.example/)
|
|
116
|
+
const config = ini.parse(content)
|
|
117
|
+
assert.equal(config.user.name, 'custom')
|
|
118
|
+
assert.equal(config.user.email, 'custom@example.test')
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('Git isomorphic auth callback returns GCM credentials for GitHub URLs', async () => {
|
|
124
|
+
const git = new KernelGit(createKernel('/tmp/pinokio'))
|
|
125
|
+
let requestedUrl = ''
|
|
126
|
+
git.getCredentialForUrl = async (url) => {
|
|
127
|
+
requestedUrl = url
|
|
128
|
+
return {
|
|
129
|
+
username: 'octocat',
|
|
130
|
+
password: 'secret-token'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const auth = await git.getIsomorphicGitAuth('https://github.com/octocat/private.git')
|
|
135
|
+
|
|
136
|
+
assert.equal(requestedUrl, 'https://github.com/octocat/private.git')
|
|
137
|
+
assert.deepEqual(auth, {
|
|
138
|
+
username: 'octocat',
|
|
139
|
+
password: 'secret-token'
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('Git isomorphic auth callback stays anonymous when GCM has no credential', async () => {
|
|
144
|
+
const git = new KernelGit(createKernel('/tmp/pinokio'))
|
|
145
|
+
git.getCredentialForUrl = async () => null
|
|
146
|
+
|
|
147
|
+
const auth = await git.getIsomorphicGitAuth('https://github.com/octocat/public.git')
|
|
148
|
+
|
|
149
|
+
assert.equal(auth, undefined)
|
|
150
|
+
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fs = require('node:fs/promises')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
|
|
6
|
+
const Github = require('../kernel/api/github')
|
|
7
|
+
|
|
8
|
+
test('GitHub API parses credentials without exposing token handling to shell commands', () => {
|
|
9
|
+
const credential = Github.parseCredentialOutput([
|
|
10
|
+
'protocol=https',
|
|
11
|
+
'host=github.com',
|
|
12
|
+
'username=octocat',
|
|
13
|
+
'password=secret-token',
|
|
14
|
+
'',
|
|
15
|
+
].join('\n'))
|
|
16
|
+
|
|
17
|
+
assert.equal(credential.username, 'octocat')
|
|
18
|
+
assert.equal(credential.password, 'secret-token')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('GitHub API parses common GitHub remote URL formats', () => {
|
|
22
|
+
assert.deepEqual(
|
|
23
|
+
Github.parseGithubRemote('https://github.com/octocat/hello-world.git'),
|
|
24
|
+
{ owner: 'octocat', repo: 'hello-world', fullName: 'octocat/hello-world' }
|
|
25
|
+
)
|
|
26
|
+
assert.deepEqual(
|
|
27
|
+
Github.parseGithubRemote('git@github.com:octocat/hello-world.git'),
|
|
28
|
+
{ owner: 'octocat', repo: 'hello-world', fullName: 'octocat/hello-world' }
|
|
29
|
+
)
|
|
30
|
+
assert.deepEqual(
|
|
31
|
+
Github.parseGithubRemote('ssh://git@github.com/octocat/hello-world.git'),
|
|
32
|
+
{ owner: 'octocat', repo: 'hello-world', fullName: 'octocat/hello-world' }
|
|
33
|
+
)
|
|
34
|
+
assert.equal(Github.parseGithubRemote('https://example.com/octocat/hello-world.git'), null)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('GitHub API parses repository names for user and organization creation', () => {
|
|
38
|
+
assert.deepEqual(
|
|
39
|
+
Github.parseRepoName('', '/tmp/hello-world'),
|
|
40
|
+
{ owner: null, repo: 'hello-world' }
|
|
41
|
+
)
|
|
42
|
+
assert.deepEqual(
|
|
43
|
+
Github.parseRepoName('octo-org/hello-world.git', '/tmp/ignored'),
|
|
44
|
+
{ owner: 'octo-org', repo: 'hello-world' }
|
|
45
|
+
)
|
|
46
|
+
assert.throws(() => Github.parseRepoName('bad owner/repo', '/tmp/ignored'), /Invalid GitHub owner/)
|
|
47
|
+
assert.throws(() => Github.parseRepoName('owner/bad repo', '/tmp/ignored'), /Invalid GitHub repository name/)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('GitHub API builds create repository requests for user and organization targets', () => {
|
|
51
|
+
assert.deepEqual(
|
|
52
|
+
Github.buildCreateRepoRequest({
|
|
53
|
+
owner: null,
|
|
54
|
+
repo: 'hello-world',
|
|
55
|
+
authenticatedUser: { login: 'octocat' },
|
|
56
|
+
visibility: 'private'
|
|
57
|
+
}),
|
|
58
|
+
{ path: '/user/repos', body: { name: 'hello-world', private: true } }
|
|
59
|
+
)
|
|
60
|
+
assert.deepEqual(
|
|
61
|
+
Github.buildCreateRepoRequest({
|
|
62
|
+
owner: 'octo-org',
|
|
63
|
+
repo: 'hello-world',
|
|
64
|
+
authenticatedUser: { login: 'octocat' },
|
|
65
|
+
visibility: 'public'
|
|
66
|
+
}),
|
|
67
|
+
{ path: '/orgs/octo-org/repos', body: { name: 'hello-world', private: false } }
|
|
68
|
+
)
|
|
69
|
+
assert.deepEqual(
|
|
70
|
+
Github.buildCreateRepoRequest({
|
|
71
|
+
owner: 'octo-org',
|
|
72
|
+
repo: 'hello-world',
|
|
73
|
+
authenticatedUser: { login: 'octocat' },
|
|
74
|
+
visibility: 'internal'
|
|
75
|
+
}),
|
|
76
|
+
{ path: '/orgs/octo-org/repos', body: { name: 'hello-world', visibility: 'internal' } }
|
|
77
|
+
)
|
|
78
|
+
assert.throws(
|
|
79
|
+
() => Github.buildCreateRepoRequest({
|
|
80
|
+
owner: null,
|
|
81
|
+
repo: 'hello-world',
|
|
82
|
+
authenticatedUser: { login: 'octocat' },
|
|
83
|
+
visibility: 'internal'
|
|
84
|
+
}),
|
|
85
|
+
/Internal repositories require an organization owner/
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('GitHub create and fork scripts no longer invoke gh repo commands', async () => {
|
|
90
|
+
const root = path.resolve(__dirname, '..')
|
|
91
|
+
const createScript = await fs.readFile(path.join(root, 'kernel/scripts/git/create'), 'utf8')
|
|
92
|
+
const forkScript = await fs.readFile(path.join(root, 'kernel/scripts/git/fork'), 'utf8')
|
|
93
|
+
|
|
94
|
+
assert.match(createScript, /"method": "github\.create"/)
|
|
95
|
+
assert.match(forkScript, /"method": "github\.fork"/)
|
|
96
|
+
assert.doesNotMatch(createScript, /gh repo/)
|
|
97
|
+
assert.doesNotMatch(forkScript, /gh repo/)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('GitHub create refuses to push when origin points somewhere else', async () => {
|
|
101
|
+
const api = new Github()
|
|
102
|
+
let requests = 0
|
|
103
|
+
const commands = []
|
|
104
|
+
|
|
105
|
+
api.getCredential = async () => ({ password: 'secret-token' })
|
|
106
|
+
api.getAuthenticatedUser = async () => ({ login: 'octocat' })
|
|
107
|
+
api.remoteUrl = async () => 'https://github.com/octocat/old-repo.git'
|
|
108
|
+
api.githubRequest = async () => {
|
|
109
|
+
requests += 1
|
|
110
|
+
return {}
|
|
111
|
+
}
|
|
112
|
+
api.runGit = async (args) => {
|
|
113
|
+
commands.push(args)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await assert.rejects(
|
|
117
|
+
() => api.create({
|
|
118
|
+
params: {
|
|
119
|
+
cwd: '/tmp/repo',
|
|
120
|
+
name: 'octocat/new-repo',
|
|
121
|
+
visibility: 'public'
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
/origin already points at https:\/\/github\.com\/octocat\/old-repo\.git/
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
assert.equal(requests, 0)
|
|
128
|
+
assert.deepEqual(commands, [])
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('GitHub fork does not rename or repoint origin', async () => {
|
|
132
|
+
const api = new Github()
|
|
133
|
+
const commands = []
|
|
134
|
+
|
|
135
|
+
api.remoteUrl = async (_cwd, name) => {
|
|
136
|
+
if (name === 'origin') return 'https://github.com/source-org/project.git'
|
|
137
|
+
return ''
|
|
138
|
+
}
|
|
139
|
+
api.runGit = async (args) => {
|
|
140
|
+
commands.push(args)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await api.configureForkRemote({
|
|
144
|
+
cwd: '/tmp/repo',
|
|
145
|
+
kernel: {},
|
|
146
|
+
source: { owner: 'source-org', repo: 'project', fullName: 'source-org/project' },
|
|
147
|
+
forkRepo: {
|
|
148
|
+
owner: { login: 'octocat' },
|
|
149
|
+
name: 'project',
|
|
150
|
+
full_name: 'octocat/project',
|
|
151
|
+
clone_url: 'https://github.com/octocat/project.git'
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(commands, [
|
|
156
|
+
['remote', 'add', 'octocat', 'https://github.com/octocat/project.git']
|
|
157
|
+
])
|
|
158
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fs = require('node:fs/promises')
|
|
3
|
+
const os = require('node:os')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
const test = require('node:test')
|
|
6
|
+
|
|
7
|
+
const Server = require('../server')
|
|
8
|
+
|
|
9
|
+
test('GitHub connection ignores legacy gh hosts when no GCM account exists', async () => {
|
|
10
|
+
const connection = await Server.prototype.get_github_connection.call({
|
|
11
|
+
get_legacy_github_hosts: async () => [
|
|
12
|
+
'github.com:',
|
|
13
|
+
' git_protocol: https',
|
|
14
|
+
' users:',
|
|
15
|
+
' cocktailpeanut:',
|
|
16
|
+
' user: cocktailpeanut',
|
|
17
|
+
'',
|
|
18
|
+
].join('\n'),
|
|
19
|
+
github_gcm: async () => '',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
assert.equal(connection.connected, false)
|
|
23
|
+
assert.equal(connection.provider, null)
|
|
24
|
+
assert.equal(connection.display, '')
|
|
25
|
+
assert.match(connection.legacyHosts, /cocktailpeanut/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('GitHub connection reports GCM accounts as connected', async () => {
|
|
29
|
+
const connection = await Server.prototype.get_github_connection.call({
|
|
30
|
+
get_legacy_github_hosts: async () => '',
|
|
31
|
+
github_gcm: async () => 'octocat\n',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
assert.equal(connection.connected, true)
|
|
35
|
+
assert.equal(connection.provider, 'gcm')
|
|
36
|
+
assert.equal(connection.display, 'github.com: octocat')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('GitHub connection coalesces only concurrent GCM checks', async () => {
|
|
40
|
+
let calls = 0
|
|
41
|
+
const context = {
|
|
42
|
+
get_legacy_github_hosts: async () => '',
|
|
43
|
+
github_gcm: async () => {
|
|
44
|
+
calls += 1
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
46
|
+
return 'octocat\n'
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const [first, second] = await Promise.all([
|
|
51
|
+
Server.prototype.get_github_connection.call(context),
|
|
52
|
+
Server.prototype.get_github_connection.call(context),
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
assert.equal(calls, 1)
|
|
56
|
+
assert.equal(first.connected, true)
|
|
57
|
+
assert.deepEqual(second, first)
|
|
58
|
+
|
|
59
|
+
await Server.prototype.get_github_connection.call(context)
|
|
60
|
+
assert.equal(calls, 2)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('GitHub login only emits completion marker after credential verification', () => {
|
|
64
|
+
const { doneMarker, message } = Server.prototype.github_login_params.call({
|
|
65
|
+
kernel: { platform: 'darwin' }
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
assert.equal(doneMarker, 'PINOKIO_GITHUB_LOGIN_DONE')
|
|
69
|
+
assert.match(message, /git credential-manager github login --web --force && printf 'protocol=https\\nhost=github\.com\\n\\n' \| GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never git credential fill >\/dev\/null && \(GCM_DONE=INOKIO_GITHUB_LOGIN_DONE; printf 'P%s\\n' "\$GCM_DONE"\)/)
|
|
70
|
+
assert.doesNotMatch(message, /git credential-manager github login --web --force\s*;/)
|
|
71
|
+
assert.doesNotMatch(message, /PINOKIO_GITHUB_LOGIN_DONE/)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('GitHub login uses success-only credential verification on Windows', () => {
|
|
75
|
+
const { message } = Server.prototype.github_login_params.call({
|
|
76
|
+
kernel: { platform: 'win32' }
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
assert.match(message, /git credential-manager github login --web --force && cmd \/C "set GIT_TERMINAL_PROMPT=0&& set GCM_INTERACTIVE=never&& \(echo protocol=https& echo host=github\.com& echo\.\) \| git credential fill >NUL" && echo P\^INOKIO_GITHUB_LOGIN_DONE/)
|
|
80
|
+
assert.doesNotMatch(message, /PINOKIO_GITHUB_LOGIN_DONE/)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('GitHub logout clears legacy gh auth even when GCM accounts exist', async () => {
|
|
84
|
+
let clearedLegacyAuth = false
|
|
85
|
+
const context = {
|
|
86
|
+
kernel: { platform: 'darwin' },
|
|
87
|
+
github_logout_command: Server.prototype.github_logout_command,
|
|
88
|
+
clear_legacy_github_auth: async () => {
|
|
89
|
+
clearedLegacyAuth = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await Server.prototype.github_logout_params.call(context, {
|
|
94
|
+
accounts: ['octocat'],
|
|
95
|
+
legacyHosts: 'github.com:\n user: octocat\n'
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
assert.equal(clearedLegacyAuth, true)
|
|
99
|
+
assert.equal(result.hadLegacyAuth, true)
|
|
100
|
+
assert.equal(result.message, 'git credential-manager github logout octocat')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('GitHub legacy auth cleanup removes old gh hosts file', async () => {
|
|
104
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-gh-auth-'))
|
|
105
|
+
const hostsFile = path.join(dir, 'config/gh/hosts.yml')
|
|
106
|
+
await fs.mkdir(path.dirname(hostsFile), { recursive: true })
|
|
107
|
+
await fs.writeFile(hostsFile, 'github.com:\n user: octocat\n')
|
|
108
|
+
|
|
109
|
+
await Server.prototype.clear_legacy_github_auth.call({
|
|
110
|
+
kernel: {
|
|
111
|
+
path: (relativePath) => path.join(dir, relativePath)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
await assert.rejects(() => fs.stat(hostsFile), { code: 'ENOENT' })
|
|
116
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
117
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const test = require('node:test')
|
|
3
|
+
|
|
4
|
+
const Huggingface = require('../kernel/bin/huggingface')
|
|
5
|
+
|
|
6
|
+
function createHuggingface(version) {
|
|
7
|
+
const huggingface = new Huggingface()
|
|
8
|
+
huggingface.kernel = {
|
|
9
|
+
bin: {
|
|
10
|
+
installed: {
|
|
11
|
+
conda: new Set(version ? ['huggingface_hub'] : []),
|
|
12
|
+
conda_versions: version ? { huggingface_hub: version } : {},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
return huggingface
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test('Huggingface.installed accepts exactly huggingface_hub 1.0.1', async () => {
|
|
20
|
+
assert.equal(await createHuggingface('1.0.1').installed(), true)
|
|
21
|
+
assert.equal(await createHuggingface('1.0.1-pyhd8ed1ab_0').installed(), true)
|
|
22
|
+
assert.equal(await createHuggingface('1.0.2').installed(), false)
|
|
23
|
+
assert.equal(await createHuggingface('0.35.3').installed(), false)
|
|
24
|
+
assert.equal(await createHuggingface(null).installed(), false)
|
|
25
|
+
})
|