@easysociable/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -69
- package/dist/easysociable.js +301 -2829
- package/dist/easysociable.js.map +1 -1
- package/package.json +17 -12
- package/skills/ONBOARDING.md +3 -1
- package/skills/easysociable-content/INSTALL.md +60 -0
- package/skills/easysociable-content/SKILL.md +107 -96
- package/skills/easysociable-content/fixtures/golden-run-en/draft.md +13 -0
- package/skills/easysociable-content/fixtures/golden-run-en/idea.md +26 -0
- package/skills/easysociable-content/fixtures/golden-run-en/pack-tiktok.json +11 -0
- package/skills/easysociable-content/platforms/claude.md +1 -2
- package/skills/easysociable-content/platforms/codex.md +1 -2
- package/skills/easysociable-content/platforms/hermes.md +1 -2
- package/skills/easysociable-content/platforms/openclaw.md +1 -2
- package/skills/easysociable-content/references/CLI.md +25 -12
- package/skills/easysociable-content/references/CONTRACT_SYNC.md +42 -0
- package/skills/easysociable-content/references/MCP.md +7 -12
- package/skills/easysociable-content/references/PAGE_PACK.md +128 -5
- package/skills/easysociable-content/references/PLATFORM_GUIDE.md +36 -0
- package/skills/easysociable-content/references/RUN_LAYOUT.md +73 -0
- package/skills/easysociable-content/references/TROUBLESHOOTING.md +11 -9
- package/skills/easysociable-content/references/VAULT.md +32 -12
- package/skills/easysociable-content/references/VOICE.md +16 -0
- package/skills/easysociable-content/scripts/contracts.mjs +183 -0
- package/skills/easysociable-content/scripts/export-contracts.mjs +76 -0
- package/skills/easysociable-content/scripts/fixture.test.mjs +24 -0
- package/skills/easysociable-content/scripts/package-skill.mjs +49 -0
- package/skills/easysociable-content/scripts/setup.mjs +127 -83
- package/skills/easysociable-content/scripts/setup.test.mjs +49 -38
- package/skills/easysociable-content/scripts/starter.mjs +50 -48
- package/skills/easysociable-content/scripts/validate-export.mjs +42 -0
- package/skills/easysociable-content/scripts/validate-pack.mjs +48 -0
- package/skills/easysociable-content/scripts/validate-run.mjs +100 -0
- package/skills/easysociable-content/scripts/validate.test.mjs +101 -0
- package/skills/easysociable-content/scripts/workbench.mjs +135 -0
- package/skills/easysociable-content/starter/CLAUDE.md +756 -0
- package/skills/easysociable-content/starter/accounts/README.md +9 -0
- package/skills/easysociable-content/starter/accounts/_index.md +31 -0
- package/skills/easysociable-content/starter/accounts/overrides/README.md +3 -0
- package/skills/easysociable-content/starter/discovery/README.md +18 -0
- package/skills/easysociable-content/starter/discovery/_index.md +27 -0
- package/skills/easysociable-content/starter/discovery/_template.md +41 -0
- package/skills/easysociable-content/starter/engine.json +19 -0
- package/skills/easysociable-content/starter/exports/README.md +28 -0
- package/skills/easysociable-content/starter/inbox/README.md +9 -0
- package/skills/easysociable-content/starter/inbox/_index.md +24 -0
- package/skills/easysociable-content/starter/inbox/entries/README.md +3 -0
- package/skills/easysociable-content/starter/platforms/README.md +19 -0
- package/skills/easysociable-content/starter/platforms/ig.md +69 -0
- package/skills/easysociable-content/starter/platforms/instagram.md +5 -0
- package/skills/easysociable-content/starter/platforms/linkedin.md +72 -0
- package/skills/easysociable-content/starter/platforms/threads.md +69 -0
- package/skills/easysociable-content/starter/platforms/tiktok.md +71 -0
- package/skills/easysociable-content/starter/platforms/x.md +78 -0
- package/skills/easysociable-content/starter/platforms/xiaohongshu.md +19 -0
- package/skills/easysociable-content/starter/profiles/_index.md +13 -0
- package/skills/easysociable-content/starter/profiles/default/README.md +11 -0
- package/skills/easysociable-content/starter/profiles/default/audience.md +38 -0
- package/skills/easysociable-content/starter/profiles/default/pillars.md +33 -0
- package/skills/easysociable-content/starter/profiles/default/voice.md +24 -0
- package/skills/easysociable-content/starter/published/_index.md +20 -0
- package/skills/easysociable-content/starter/raw/README.md +4 -0
- package/skills/easysociable-content/starter/raw/_index.md +12 -0
- package/skills/easysociable-content/starter/runs/README.md +22 -0
- package/skills/easysociable-content/starter/runs/_index.md +41 -0
- package/skills/easysociable-content/starter/runs/_template/brief.md +66 -0
- package/skills/easysociable-content/starter/runs/_template/draft.md +11 -0
- package/skills/easysociable-content/starter/runs/_template/editor.md +18 -0
- package/skills/easysociable-content/starter/runs/_template/feedback.md +15 -0
- package/skills/easysociable-content/starter/runs/_template/idea.md +27 -0
- package/skills/easysociable-content/starter/runs/_template/pack.md +47 -0
- package/skills/easysociable-content/starter/runs/_template/packet.md +25 -0
- package/skills/easysociable-content/starter/runs/_template/rubric.md +19 -0
- package/skills/easysociable-content/starter/swipe/_index.md +27 -0
- package/skills/easysociable-content/starter/swipe/_template.md +34 -0
- package/skills/easysociable-content/starter/topics/README.md +25 -0
- package/skills/easysociable-content/starter/topics/WATCHLIST.md +65 -0
- package/skills/easysociable-content/starter/topics/_index.md +44 -0
- package/skills/easysociable-content/starter/topics/_template-item.md +90 -0
- package/skills/easysociable-content/starter/topics/items/README.md +4 -0
- package/skills/easysociable-content/starter/topics/scoring.md +201 -0
- package/skills/easysociable-content/starter/topics/watchlists/README.md +10 -0
- package/skills/easysociable-content/starter/topics/watchlists/_shared.md +14 -0
- package/skills/easysociable-content/starter/topics/watchlists/default.md +6 -0
- package/skills/easysociable-content/starter/wiki/README.md +13 -0
- package/skills/easysociable-content/starter/wiki/atoms/_index.md +23 -0
- package/skills/easysociable-content/starter/wiki/atoms/_template.md +31 -0
- package/skills/easysociable-content/starter/wiki/claims/_index.md +26 -0
- package/skills/easysociable-content/starter/wiki/claims/_template.md +38 -0
- package/skills/easysociable-content/starter/workbench/README.md +17 -0
- package/skills/easysociable-content/workbench/build.py +1074 -0
- package/skills/easysociable-content/workbench/serve.py +53 -0
- package/skills/easysociable-content/workbench/template.html +2119 -0
- package/skills/easysociable-content/workflows/ANALYSIS_AND_COVERAGE.md +14 -5
- package/skills/easysociable-content/workflows/CONTENT_EXPORT.md +16 -11
- package/skills/easysociable-content/workflows/GETTING_STARTED.md +40 -0
- package/skills/easysociable-content/workflows/MCP_FIRST.md +3 -8
- package/skills/easysociable-content/workflows/OUT_OF_SCOPE.md +18 -0
- package/skills/easysociable-content/workflows/PACK_AUTHORING.md +42 -0
- package/skills/easysociable-content/workflows/SWIPE_CAPTURE.md +18 -0
- package/skills/easysociable-content/workflows/TOPIC_DRAFT.md +28 -4
- package/skills/easysociable-content/workflows/WORKING_DRAFT.md +17 -8
- package/skills/easysociable-slideshow/SKILL.md +20 -13
- package/skills/easysociable-slideshow/references/VAULT.md +13 -11
- package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
- package/skills/easysociable-content/workflows/DEVICE_TASKS.md +0 -5
- package/skills/easysociable-content/workflows/EXPERIMENTS.md +0 -6
- package/skills/easysociable-content/workflows/PUBLICATION.md +0 -5
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { join, resolve } from 'node:path'
|
|
5
|
+
import { validateContentPack, validateRunLayout } from './contracts.mjs'
|
|
6
|
+
import { validateExportDirectory } from './export-contracts.mjs'
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const options = { run: '', exportDir: '', requirePack: false, requireExport: false, requirePageTemplateId: false, json: false }
|
|
10
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
11
|
+
const arg = argv[i]
|
|
12
|
+
if (arg === '--run') options.run = argv[++i] ?? ''
|
|
13
|
+
else if (arg === '--export') options.exportDir = argv[++i] ?? ''
|
|
14
|
+
else if (arg === '--require-pack') options.requirePack = true
|
|
15
|
+
else if (arg === '--require-export') options.requireExport = true
|
|
16
|
+
else if (arg === '--require-page-template-id') options.requirePageTemplateId = true
|
|
17
|
+
else if (arg === '--json') options.json = true
|
|
18
|
+
else if (arg === '--help' || arg === '-h') options.help = true
|
|
19
|
+
else throw new Error(`Unknown argument: ${arg}`)
|
|
20
|
+
}
|
|
21
|
+
return options
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function output(value, json) {
|
|
25
|
+
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : `${value.message ?? value}\n`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const options = parseArgs(process.argv.slice(2))
|
|
30
|
+
if (options.help) {
|
|
31
|
+
output({ usage: 'node validate-run.mjs --run <run-dir> [--require-pack] [--require-export] [--export <export-dir>] [--json]' }, false)
|
|
32
|
+
process.exit(0)
|
|
33
|
+
}
|
|
34
|
+
if (!options.run) {
|
|
35
|
+
output({ code: 'RUN_REQUIRED', message: 'Pass --run <run-dir>.', valid: false }, options.json)
|
|
36
|
+
process.exitCode = 2
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const runDir = resolve(options.run)
|
|
40
|
+
const layout = validateRunLayout(runDir, { requirePack: options.requirePack })
|
|
41
|
+
const packs = []
|
|
42
|
+
for (const name of layout.files.packs) {
|
|
43
|
+
const platform = name.replace(/^pack-/, '').replace(/\.json$/i, '')
|
|
44
|
+
try {
|
|
45
|
+
const pack = JSON.parse(readFileSync(join(runDir, name), 'utf8'))
|
|
46
|
+
packs.push({ file: name, pages: Array.isArray(pack.pages) ? pack.pages.length : 0, ...validateContentPack(pack, {
|
|
47
|
+
requirePageTemplateId: options.requirePageTemplateId,
|
|
48
|
+
platform,
|
|
49
|
+
}) })
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
packs.push({ file: name, pages: 0, valid: false, errors: [`invalid JSON (${error instanceof Error ? error.message : String(error)})`], warnings: [] })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const packErrors = packs.flatMap((item) => item.errors.map((e) => `${item.file}: ${e}`))
|
|
56
|
+
let exportReport
|
|
57
|
+
const needsExport = options.requireExport || layout.frontmatter.status === 'exported'
|
|
58
|
+
if (needsExport) {
|
|
59
|
+
let exportDir = options.exportDir
|
|
60
|
+
if (!exportDir) {
|
|
61
|
+
const engineRoot = resolve(runDir, '..', '..')
|
|
62
|
+
const engineConfig = join(engineRoot, 'engine.json')
|
|
63
|
+
if (existsSync(engineConfig)) {
|
|
64
|
+
try {
|
|
65
|
+
const engine = JSON.parse(readFileSync(engineConfig, 'utf8'))
|
|
66
|
+
const base = join(engineRoot, engine.paths.exports, layout.frontmatter.slug)
|
|
67
|
+
const nested = layout.frontmatter.platform ? join(base, layout.frontmatter.platform) : ''
|
|
68
|
+
exportDir = nested && existsSync(nested) ? nested : base
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
exportReport = { valid: false, errors: [`engine.json: invalid JSON (${error instanceof Error ? error.message : String(error)})`], warnings: [] }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else exportReport = { valid: false, errors: ['engine.json: required to derive export path'], warnings: [] }
|
|
75
|
+
}
|
|
76
|
+
if (!exportReport) {
|
|
77
|
+
const expectedPages = packs.find(item => item.pages > 0)?.pages ?? 1
|
|
78
|
+
exportReport = validateExportDirectory(resolve(exportDir), { expectedPages })
|
|
79
|
+
exportReport.export = resolve(exportDir)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const exportErrors = exportReport?.errors?.map(error => `export: ${error}`) ?? []
|
|
83
|
+
const valid = layout.valid && packErrors.length === 0 && exportErrors.length === 0
|
|
84
|
+
output({
|
|
85
|
+
valid,
|
|
86
|
+
action: 'validated',
|
|
87
|
+
run: runDir,
|
|
88
|
+
layout,
|
|
89
|
+
packs,
|
|
90
|
+
export: exportReport,
|
|
91
|
+
errors: [...layout.errors, ...packErrors, ...exportErrors],
|
|
92
|
+
warnings: [...layout.warnings, ...packs.flatMap((item) => item.warnings.map((w) => `${item.file}: ${w}`)), ...(exportReport?.warnings ?? [])],
|
|
93
|
+
}, options.json)
|
|
94
|
+
if (!valid) process.exitCode = 2
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
output({ code: 'RUN_VALIDATE_FAILED', message: error instanceof Error ? error.message : String(error), valid: false }, true)
|
|
99
|
+
process.exitCode = 1
|
|
100
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { validateContentPack } from './contracts.mjs'
|
|
7
|
+
import { validateExportDirectory } from './export-contracts.mjs'
|
|
8
|
+
|
|
9
|
+
const root = new URL('.', import.meta.url).pathname
|
|
10
|
+
const validatePack = join(root, 'validate-pack.mjs')
|
|
11
|
+
const validateRun = join(root, 'validate-run.mjs')
|
|
12
|
+
const fixture = mkdtempSync(join(tmpdir(), 'easysociable-validate-'))
|
|
13
|
+
|
|
14
|
+
function run(script, args) {
|
|
15
|
+
return spawnSync(process.execPath, [script, ...args, '--json'], { encoding: 'utf8' })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ideaMd({ platform = 'tiktok', status = 'pack-ready' } = {}) {
|
|
19
|
+
return `---
|
|
20
|
+
run_id: RUN-demo
|
|
21
|
+
status: ${status}
|
|
22
|
+
created: 2026-08-28
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Idea
|
|
26
|
+
|
|
27
|
+
- **one_liner:** Demo
|
|
28
|
+
- **primary_platform:** ${platform}
|
|
29
|
+
- **platforms:** ${platform}
|
|
30
|
+
- **swipe_id:** none
|
|
31
|
+
- **atoms:** none
|
|
32
|
+
- **claim_id:** none
|
|
33
|
+
`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const tiktokPack = {
|
|
37
|
+
schemaVersion: 'content-pack.v1',
|
|
38
|
+
platform: 'tiktok',
|
|
39
|
+
title: 'Demo',
|
|
40
|
+
pages: [
|
|
41
|
+
{ role: 'hook', pageTemplateId: 'tpl_hook', slots: { text_0: 'a' } },
|
|
42
|
+
{ role: 'value', pageTemplateId: 'tpl_value', slots: { text_0: 'b' } },
|
|
43
|
+
{ role: 'value', pageTemplateId: 'tpl_value', slots: { text_0: 'c' } },
|
|
44
|
+
{ role: 'cta', pageTemplateId: 'tpl_cta', slots: { text_0: 'd' } },
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const ok = validateContentPack(tiktokPack)
|
|
50
|
+
assert.equal(ok.valid, true)
|
|
51
|
+
|
|
52
|
+
const short = validateContentPack({ ...tiktokPack, pages: tiktokPack.pages.slice(0, 3) })
|
|
53
|
+
assert.equal(short.valid, false)
|
|
54
|
+
|
|
55
|
+
const forbidden = validateContentPack({ ...tiktokPack, deck_template_id: 'x' })
|
|
56
|
+
assert.equal(forbidden.valid, false)
|
|
57
|
+
|
|
58
|
+
const badSlot = validateContentPack({ ...tiktokPack, pages: [{ ...tiktokPack.pages[0], slots: { text_0: [] } }, ...tiktokPack.pages.slice(1)] })
|
|
59
|
+
assert.equal(badSlot.valid, false)
|
|
60
|
+
|
|
61
|
+
const missingTemplate = validateContentPack({ ...tiktokPack, pages: [{ role: 'hook', slots: {} }, ...tiktokPack.pages.slice(1)] })
|
|
62
|
+
assert.equal(missingTemplate.valid, false)
|
|
63
|
+
|
|
64
|
+
const packFile = join(fixture, 'pack-tiktok.json')
|
|
65
|
+
writeFileSync(packFile, JSON.stringify(tiktokPack), 'utf8')
|
|
66
|
+
const packCli = run(validatePack, ['--file', packFile, '--platform', 'tiktok'])
|
|
67
|
+
assert.equal(packCli.status, 0)
|
|
68
|
+
|
|
69
|
+
const runDir = join(fixture, 'demo-run')
|
|
70
|
+
mkdirSync(runDir)
|
|
71
|
+
writeFileSync(join(runDir, 'idea.md'), ideaMd(), 'utf8')
|
|
72
|
+
writeFileSync(join(runDir, 'draft.md'), '# Draft\n', 'utf8')
|
|
73
|
+
writeFileSync(join(runDir, 'pack-tiktok.json'), JSON.stringify(tiktokPack), 'utf8')
|
|
74
|
+
|
|
75
|
+
const runCli = run(validateRun, ['--run', runDir, '--require-pack'])
|
|
76
|
+
assert.equal(runCli.status, 0)
|
|
77
|
+
const runReport = JSON.parse(runCli.stdout)
|
|
78
|
+
assert.equal(runReport.valid, true)
|
|
79
|
+
|
|
80
|
+
rmSync(join(runDir, 'pack-tiktok.json'))
|
|
81
|
+
const missingPack = run(validateRun, ['--run', runDir, '--require-pack'])
|
|
82
|
+
assert.equal(missingPack.status, 2)
|
|
83
|
+
|
|
84
|
+
const exportDir = join(fixture, 'exported')
|
|
85
|
+
mkdirSync(exportDir)
|
|
86
|
+
writeFileSync(join(exportDir, '01.png'), Buffer.from('89504e470d0a1a0a', 'hex'))
|
|
87
|
+
writeFileSync(join(exportDir, 'manifest.json'), JSON.stringify({ schema: 'template-export.v1', artifacts: [{ pageIndex: 0 }] }))
|
|
88
|
+
assert.equal(validateExportDirectory(exportDir, { expectedPages: 1 }).valid, true)
|
|
89
|
+
|
|
90
|
+
const exportedRun = join(fixture, 'exported-run')
|
|
91
|
+
mkdirSync(exportedRun)
|
|
92
|
+
writeFileSync(join(exportedRun, 'idea.md'), ideaMd({ platform: 'x', status: 'exported' }), 'utf8')
|
|
93
|
+
writeFileSync(join(exportedRun, 'draft.md'), '# Draft\n', 'utf8')
|
|
94
|
+
const exportedCli = run(validateRun, ['--run', exportedRun, '--export', exportDir, '--require-export'])
|
|
95
|
+
assert.equal(exportedCli.status, 0, exportedCli.stdout)
|
|
96
|
+
|
|
97
|
+
console.log('Content Skill validate smoke passed.')
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
rmSync(fixture, { recursive: true, force: true })
|
|
101
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
4
|
+
import { existsSync, mkdirSync, copyFileSync } from 'node:fs'
|
|
5
|
+
import net from 'node:net'
|
|
6
|
+
import { pathToFileURL } from 'node:url'
|
|
7
|
+
import { join, resolve } from 'node:path'
|
|
8
|
+
import { WORKBENCH_FILES, WORKBENCH_SRC } from './starter.mjs'
|
|
9
|
+
|
|
10
|
+
export const WORKBENCH_PORT = 8765
|
|
11
|
+
export const WORKBENCH_URL = `http://127.0.0.1:${WORKBENCH_PORT}/workbench/`
|
|
12
|
+
|
|
13
|
+
function output(value, json) {
|
|
14
|
+
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : `${value.message ?? value}\n`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function python3Bin() {
|
|
18
|
+
const result = spawnSync('python3', ['--version'], { encoding: 'utf8' })
|
|
19
|
+
if (result.status === 0) return 'python3'
|
|
20
|
+
return ''
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function portInUse(port = WORKBENCH_PORT) {
|
|
24
|
+
return new Promise((resolvePort) => {
|
|
25
|
+
const socket = net.connect({ port, host: '127.0.0.1' }, () => {
|
|
26
|
+
socket.end()
|
|
27
|
+
resolvePort(true)
|
|
28
|
+
})
|
|
29
|
+
socket.on('error', () => resolvePort(false))
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ensureWorkbench(engine) {
|
|
34
|
+
const destDir = join(engine, 'workbench')
|
|
35
|
+
mkdirSync(destDir, { recursive: true })
|
|
36
|
+
const copied = []
|
|
37
|
+
const skipped = []
|
|
38
|
+
for (const name of WORKBENCH_FILES) {
|
|
39
|
+
const dest = join(destDir, name)
|
|
40
|
+
if (existsSync(dest)) {
|
|
41
|
+
skipped.push(name)
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
copyFileSync(join(WORKBENCH_SRC, name), dest)
|
|
45
|
+
copied.push(name)
|
|
46
|
+
}
|
|
47
|
+
return { destDir, copied, skipped }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildWorkbench(engine) {
|
|
51
|
+
const python = python3Bin()
|
|
52
|
+
if (!python) {
|
|
53
|
+
return { ok: false, code: 'PYTHON3_REQUIRED', message: 'python3 is required for the workbench. Install Python 3 and retry.' }
|
|
54
|
+
}
|
|
55
|
+
const script = join(engine, 'workbench', 'build.py')
|
|
56
|
+
if (!existsSync(script)) {
|
|
57
|
+
return { ok: false, code: 'WORKBENCH_MISSING', message: 'workbench/build.py is missing. Re-run setup --apply.' }
|
|
58
|
+
}
|
|
59
|
+
const result = spawnSync(python, [script], { encoding: 'utf8', cwd: engine })
|
|
60
|
+
if (result.status !== 0) {
|
|
61
|
+
return { ok: false, code: 'WORKBENCH_BUILD_FAILED', message: result.stderr.trim() || result.stdout.trim() || 'workbench build failed' }
|
|
62
|
+
}
|
|
63
|
+
return { ok: true, message: result.stdout.trim() }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function startWorkbench(engine, { noOpen = false } = {}) {
|
|
67
|
+
const python = python3Bin()
|
|
68
|
+
if (!python) {
|
|
69
|
+
return { ok: false, code: 'PYTHON3_REQUIRED', message: 'python3 is required for the workbench. Install Python 3 and retry.' }
|
|
70
|
+
}
|
|
71
|
+
ensureWorkbench(engine)
|
|
72
|
+
const built = buildWorkbench(engine)
|
|
73
|
+
if (!built.ok) return built
|
|
74
|
+
const url = WORKBENCH_URL
|
|
75
|
+
if (await portInUse()) {
|
|
76
|
+
if (!noOpen && process.env.EASYSOCIABLE_NO_OPEN !== 'true') openUrl(url)
|
|
77
|
+
return { ok: true, alreadyRunning: true, url, build: built.message }
|
|
78
|
+
}
|
|
79
|
+
const args = [join(engine, 'workbench', 'serve.py')]
|
|
80
|
+
if (noOpen) args.push('--no-open')
|
|
81
|
+
const child = spawn(python, args, { cwd: engine, detached: true, stdio: 'ignore' })
|
|
82
|
+
child.unref()
|
|
83
|
+
return { ok: true, alreadyRunning: false, url, pid: child.pid, build: built.message }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function openUrl(url) {
|
|
87
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open'
|
|
88
|
+
const args = process.platform === 'win32' ? ['/c', 'start', url] : [url]
|
|
89
|
+
spawn(command, args, { stdio: 'ignore', detached: true }).unref()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseArgs(argv) {
|
|
93
|
+
const options = { engine: '', command: '', noOpen: false, json: false }
|
|
94
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
95
|
+
const arg = argv[i]
|
|
96
|
+
if (arg === '--engine') options.engine = argv[++i] ?? ''
|
|
97
|
+
else if (arg === '--no-open') options.noOpen = true
|
|
98
|
+
else if (arg === '--json') options.json = true
|
|
99
|
+
else if (arg === '--help' || arg === '-h') options.help = true
|
|
100
|
+
else if (!arg.startsWith('--') && !options.command) options.command = arg
|
|
101
|
+
else throw new Error(`Unknown argument: ${arg}`)
|
|
102
|
+
}
|
|
103
|
+
return options
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function main() {
|
|
107
|
+
const options = parseArgs(process.argv.slice(2))
|
|
108
|
+
if (options.help) {
|
|
109
|
+
output({ usage: 'node workbench.mjs build|start --engine <Content Engine> [--no-open] [--json]' }, false)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
if (!options.engine) throw new Error('--engine is required')
|
|
113
|
+
const engine = resolve(options.engine)
|
|
114
|
+
if (options.command === 'build') {
|
|
115
|
+
ensureWorkbench(engine)
|
|
116
|
+
const built = buildWorkbench(engine)
|
|
117
|
+
output(built, options.json)
|
|
118
|
+
if (!built.ok) process.exitCode = 2
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if (options.command === 'start' || options.command === '') {
|
|
122
|
+
const started = await startWorkbench(engine, { noOpen: options.noOpen })
|
|
123
|
+
output(started, options.json)
|
|
124
|
+
if (!started.ok) process.exitCode = 2
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
throw new Error('Command must be build or start')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
131
|
+
main().catch((error) => {
|
|
132
|
+
output({ code: 'WORKBENCH_FAILED', message: error instanceof Error ? error.message : String(error) }, true)
|
|
133
|
+
process.exitCode = 1
|
|
134
|
+
})
|
|
135
|
+
}
|