This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
for cmd in node npm tar; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
echo "Missing required tool: $cmd" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if command -v dpkg-deb >/dev/null 2>&1; then
|
||||
echo "dpkg-deb available"
|
||||
else
|
||||
echo "dpkg-deb not available; Linux .deb packaging will be skipped" >&2
|
||||
fi
|
||||
|
||||
node - <<'NODE'
|
||||
const pkg = require('./package.json');
|
||||
console.log(`StepForge ${pkg.version} bootstrap OK`);
|
||||
NODE
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
BUILD_ROOT="${STEPFORGE_BUILD_DIR:-$ROOT_DIR/build}"
|
||||
EXAMPLES_ROOT="${STEPFORGE_EXAMPLES_DIR:-$ROOT_DIR/examples}"
|
||||
ARTIFACT_DIR="$BUILD_ROOT/artifacts"
|
||||
REPORT_FILE="$BUILD_ROOT/build_report.md"
|
||||
MANIFEST_FILE="$BUILD_ROOT/artifacts_manifest.json"
|
||||
|
||||
mkdir -p "$BUILD_ROOT"
|
||||
|
||||
bash "$ROOT_DIR/scripts/bootstrap-offline.sh"
|
||||
node "$ROOT_DIR/scripts/make-sample-guide.js" --root "$EXAMPLES_ROOT"
|
||||
STEPFORGE_PACKAGE_DIR="$ARTIFACT_DIR" bash "$ROOT_DIR/scripts/package-linux.sh" >/dev/null
|
||||
|
||||
BUILD_ROOT="$BUILD_ROOT" \
|
||||
ARTIFACT_DIR="$ARTIFACT_DIR" \
|
||||
EXAMPLES_ROOT="$EXAMPLES_ROOT" \
|
||||
REPORT_FILE="$REPORT_FILE" \
|
||||
MANIFEST_FILE="$MANIFEST_FILE" \
|
||||
ROOT_DIR="$ROOT_DIR" \
|
||||
node - <<'NODE'
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const buildRoot = process.env.BUILD_ROOT;
|
||||
const artifactDir = process.env.ARTIFACT_DIR;
|
||||
const examplesRoot = process.env.EXAMPLES_ROOT;
|
||||
const reportFile = process.env.REPORT_FILE;
|
||||
const manifestFile = process.env.MANIFEST_FILE;
|
||||
const rootDir = process.env.ROOT_DIR;
|
||||
|
||||
function walk(dir, base = dir, out = []) {
|
||||
if (!fs.existsSync(dir)) return out;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const abs = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(abs, base, out);
|
||||
else out.push(path.relative(base, abs));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function sha256(file) {
|
||||
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
||||
}
|
||||
|
||||
const files = [];
|
||||
for (const rel of walk(artifactDir, artifactDir)) {
|
||||
const abs = path.join(artifactDir, rel);
|
||||
files.push({
|
||||
kind: 'artifact',
|
||||
path: path.relative(buildRoot, abs),
|
||||
size: fs.statSync(abs).size,
|
||||
sha256: sha256(abs),
|
||||
});
|
||||
}
|
||||
for (const rel of walk(examplesRoot, examplesRoot)) {
|
||||
if (!rel.startsWith('sample-')) continue;
|
||||
const abs = path.join(examplesRoot, rel);
|
||||
files.push({
|
||||
kind: 'sample',
|
||||
path: path.relative(buildRoot, abs),
|
||||
size: fs.statSync(abs).size,
|
||||
sha256: sha256(abs),
|
||||
});
|
||||
}
|
||||
|
||||
const pkg = require(path.join(rootDir, 'package.json'));
|
||||
const report = `# StepForge Build Report
|
||||
|
||||
Version: ${pkg.version}
|
||||
Generated: ${new Date().toISOString()}
|
||||
|
||||
## Outputs
|
||||
|
||||
- Portable tarball: ${files.find((f) => f.path.endsWith('.tar.gz'))?.path || 'not generated'}
|
||||
- Debian package: ${files.find((f) => f.path.endsWith('.deb'))?.path || 'not generated'}
|
||||
- Sample guide archive: ${files.find((f) => f.path.endsWith('sample-guide.sfgz'))?.path || 'not generated'}
|
||||
|
||||
## Notes
|
||||
|
||||
- The desktop shell is Electron.
|
||||
- Core storage, exports, and archive handling are local-only.
|
||||
- Sample exports and package artifacts are written by the offline build scripts.
|
||||
`;
|
||||
|
||||
fs.writeFileSync(reportFile, report);
|
||||
fs.writeFileSync(manifestFile, JSON.stringify({
|
||||
format: 'stepforge-artifacts-manifest',
|
||||
version: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
packageVersion: pkg.version,
|
||||
files,
|
||||
}, null, 2) + '\n');
|
||||
NODE
|
||||
|
||||
echo "Build artifacts written to $BUILD_ROOT"
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { GuideStore } = require('../core/store');
|
||||
const raster = require('../core/raster');
|
||||
const { encodePng } = require('../core/png');
|
||||
const { buildRenderAst } = require('../core/renderast');
|
||||
const { exportGuideArchive } = require('../core/archive');
|
||||
const { runExport } = require('../exporters');
|
||||
const { writeJsonSync, slugify } = require('../core/util');
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '..');
|
||||
const DEFAULT_ROOT = path.join(ROOT_DIR, 'examples');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { root: DEFAULT_ROOT };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--root' && argv[i + 1]) out.root = path.resolve(argv[++i]);
|
||||
else if (arg === '--help' || arg === '-h') out.help = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function cleanDir(dir) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function drawChrome(img, { accent, title, subtitle, sidebarLabel, bodyLabel }) {
|
||||
const W = img.width;
|
||||
const H = img.height;
|
||||
raster.fillRect(img, 0, 0, W, H, [245, 247, 250, 255]);
|
||||
raster.fillRect(img, 0, 0, W, 68, accent);
|
||||
raster.fillRect(img, 28, 94, 270, H - 138, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 326, 94, W - 354, H - 138, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 48, 118, 212, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 48, 152, 212, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 48, 186, 212, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 362, 148, 220, 152, [230, 237, 245, 255]);
|
||||
raster.fillRect(img, 608, 148, 276, 40, [235, 241, 248, 255]);
|
||||
raster.fillRect(img, 608, 202, 276, 40, [235, 241, 248, 255]);
|
||||
raster.fillRect(img, 608, 256, 276, 40, [235, 241, 248, 255]);
|
||||
raster.drawText(img, 28, 20, title, 26, [255, 255, 255, 255]);
|
||||
raster.drawText(img, 28, 44, subtitle, 12, [214, 226, 240, 255]);
|
||||
raster.drawText(img, 48, 102, sidebarLabel, 12, [78, 90, 105, 255]);
|
||||
raster.drawText(img, 356, 102, bodyLabel, 12, [78, 90, 105, 255]);
|
||||
}
|
||||
|
||||
function makeShotOne() {
|
||||
const img = raster.createImage(1280, 760, [245, 247, 250, 255]);
|
||||
drawChrome(img, {
|
||||
accent: [0, 104, 255, 255],
|
||||
title: 'Reset password',
|
||||
subtitle: 'Users > Security > Reset',
|
||||
sidebarLabel: 'Users',
|
||||
bodyLabel: 'Admin Portal',
|
||||
});
|
||||
raster.fillRect(img, 392, 156, 176, 36, [0, 104, 255, 255]);
|
||||
raster.drawTextCentered(img, 480, 175, 'Open Users', 16, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 644, 160, 160, 20, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 644, 196, 240, 20, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 644, 232, 220, 20, [255, 255, 255, 255]);
|
||||
raster.drawText(img, 360, 336, '1. Open the Users list and confirm the target account is visible.', 12, [48, 59, 71, 255]);
|
||||
raster.drawText(img, 360, 360, 'The highlight shows the next action target.', 12, [96, 108, 121, 255]);
|
||||
return img;
|
||||
}
|
||||
|
||||
function makeShotTwo() {
|
||||
const img = raster.createImage(1280, 760, [245, 247, 250, 255]);
|
||||
drawChrome(img, {
|
||||
accent: [20, 115, 90, 255],
|
||||
title: 'Security settings',
|
||||
subtitle: '2-factor authentication and resets',
|
||||
sidebarLabel: 'Security',
|
||||
bodyLabel: 'Account settings',
|
||||
});
|
||||
raster.fillRect(img, 366, 160, 252, 56, [20, 115, 90, 255]);
|
||||
raster.drawTextCentered(img, 492, 180, 'Enable 2FA', 18, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 648, 160, 250, 22, [233, 238, 244, 255]);
|
||||
raster.fillRect(img, 648, 196, 250, 22, [233, 238, 244, 255]);
|
||||
raster.fillRect(img, 648, 232, 250, 22, [233, 238, 244, 255]);
|
||||
raster.drawText(img, 360, 336, '2. Enable the reset policy and save the change.', 12, [48, 59, 71, 255]);
|
||||
raster.drawText(img, 360, 360, 'The annotation number points at the primary action.', 12, [96, 108, 121, 255]);
|
||||
return img;
|
||||
}
|
||||
|
||||
function makeShotThree() {
|
||||
const img = raster.createImage(1280, 760, [245, 247, 250, 255]);
|
||||
drawChrome(img, {
|
||||
accent: [36, 50, 78, 255],
|
||||
title: 'Confirmation',
|
||||
subtitle: 'Review before closing the workflow',
|
||||
sidebarLabel: 'Review',
|
||||
bodyLabel: 'Change summary',
|
||||
});
|
||||
raster.fillRect(img, 366, 150, 472, 210, [255, 255, 255, 255]);
|
||||
raster.fillRect(img, 396, 182, 120, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 396, 220, 316, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 396, 256, 356, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 396, 292, 270, 18, [232, 237, 243, 255]);
|
||||
raster.fillRect(img, 778, 298, 36, 36, [36, 50, 78, 255]);
|
||||
raster.drawText(img, 396, 406, '3. Confirm the summary, then close the dialog.', 12, [48, 59, 71, 255]);
|
||||
raster.drawText(img, 396, 430, 'A blur redacts the account number in the sample export.', 12, [96, 108, 121, 255]);
|
||||
return img;
|
||||
}
|
||||
|
||||
function createGuide(store) {
|
||||
const guide = store.createGuide({
|
||||
title: 'Reset a password in Admin Portal',
|
||||
descriptionHtml: '<p>Offline sample guide showing capture, annotations, rich text, and exports.</p>',
|
||||
placeholders: {
|
||||
Product: 'Admin Portal',
|
||||
Author: 'StepForge',
|
||||
Department: 'Support',
|
||||
},
|
||||
flags: {
|
||||
focusedViewDefault: true,
|
||||
hideSkippedStepsInExports: true,
|
||||
},
|
||||
});
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: 'Open [[Product]] users',
|
||||
descriptionHtml: '<p>Open the users list and select the target account.</p>',
|
||||
annotations: [
|
||||
{ type: 'rect', x: 0.275, y: 0.18, w: 0.19, h: 0.18, style: { stroke: '#0068ff', strokeWidth: 6, fill: 'transparent' } },
|
||||
{ type: 'number', value: 1, x: 0.30, y: 0.08, w: 0.08, h: 0.12, style: { stroke: '#0068ff' } },
|
||||
],
|
||||
textBlocks: [
|
||||
{ position: 'after-description', level: 'info', title: 'Tip', descriptionHtml: '<p>Use the search box to avoid scrolling.</p>' },
|
||||
],
|
||||
image: makeShotOne(),
|
||||
},
|
||||
{
|
||||
title: 'Enable the reset policy',
|
||||
descriptionHtml: '<p>Make sure the policy is active before continuing.</p>',
|
||||
annotations: [
|
||||
{ type: 'arrow', x: 0.47, y: 0.24, w: 0.23, h: -0.04, style: { stroke: '#14a375', strokeWidth: 5 } },
|
||||
{ type: 'tooltip', x: 0.53, y: 0.13, w: 0.17, h: 0.08, text: 'Primary action', style: { fill: '#111827', textColor: '#ffffff', stroke: '#111827', tail: 'bottom' } },
|
||||
{ type: 'number', value: 2, x: 0.31, y: 0.08, w: 0.08, h: 0.12, style: { stroke: '#14a375' } },
|
||||
],
|
||||
codeBlocks: [
|
||||
{ id: 'cmd', language: 'bash', code: 'stepforge --capture --window --delay 300' },
|
||||
],
|
||||
image: makeShotTwo(),
|
||||
},
|
||||
{
|
||||
title: 'Review the confirmation',
|
||||
descriptionHtml: '<p>Confirm the summary and close the modal.</p>',
|
||||
annotations: [
|
||||
{ type: 'blur', x: 0.49, y: 0.32, w: 0.21, h: 0.08, radius: 12, style: { stroke: '#9ca3af', strokeWidth: 2 } },
|
||||
{ type: 'highlight', x: 0.47, y: 0.24, w: 0.28, h: 0.20, style: { fill: '#ffeeb0', stroke: '#f0a500', strokeWidth: 2 } },
|
||||
{ type: 'number', value: 3, x: 0.31, y: 0.08, w: 0.08, h: 0.12, style: { stroke: '#36a' } },
|
||||
],
|
||||
tableBlocks: [
|
||||
{ id: 't1', rows: [['Field', 'Value'], ['Title', 'Admin Portal'], ['Owner', 'Support']] },
|
||||
],
|
||||
image: makeShotThree(),
|
||||
},
|
||||
];
|
||||
|
||||
steps.forEach((entry, index) => {
|
||||
const buf = encodePng(entry.image);
|
||||
store.addStep(guide.guideId, {
|
||||
title: entry.title,
|
||||
descriptionHtml: entry.descriptionHtml,
|
||||
annotations: entry.annotations,
|
||||
textBlocks: entry.textBlocks || [],
|
||||
codeBlocks: entry.codeBlocks || [],
|
||||
tableBlocks: entry.tableBlocks || [],
|
||||
focusedView: { enabled: true, zoom: 1.1, panX: 0.5, panY: 0.5 },
|
||||
}, buf, { width: entry.image.width, height: entry.image.height }, { position: index });
|
||||
});
|
||||
|
||||
const substep = store.addStep(guide.guideId, {
|
||||
kind: 'empty',
|
||||
parentStepId: store.getGuide(guide.guideId).stepsOrder[1],
|
||||
title: 'Confirm permission prompt',
|
||||
descriptionHtml: '<p>Only administrators can complete this step.</p>',
|
||||
textBlocks: [{ position: 'after-description', level: 'warn', title: 'Access', descriptionHtml: '<p>Admin rights required.</p>' }],
|
||||
}, null, null, { position: 2 });
|
||||
|
||||
store.addStep(guide.guideId, {
|
||||
kind: 'empty',
|
||||
title: 'Legacy note',
|
||||
hidden: true,
|
||||
descriptionHtml: '<p>This hidden step exercises filtering in exports.</p>',
|
||||
}, null, null, { position: 4 });
|
||||
|
||||
store.addStep(guide.guideId, {
|
||||
kind: 'empty',
|
||||
title: 'Deprecated flow',
|
||||
skipped: true,
|
||||
descriptionHtml: '<p>This skipped step remains in the library but is excluded from exports.</p>',
|
||||
}, null, null, { position: 5 });
|
||||
|
||||
return { guideId: guide.guideId, substepId: substep.stepId };
|
||||
}
|
||||
|
||||
function exportOutputs(store, guideId, root, manifest) {
|
||||
const ast = buildRenderAst(store, guideId);
|
||||
const formats = ['json', 'markdown', 'html-simple', 'html-rich', 'pdf', 'gif', 'image-bundle', 'docx', 'pptx'];
|
||||
const outputs = {};
|
||||
for (const format of formats) {
|
||||
const outDir = path.join(root, 'sample-exports', format);
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
const result = runExport(format, ast, outDir, {});
|
||||
outputs[format] = path.relative(root, result.file || outDir);
|
||||
}
|
||||
const archiveFile = path.join(root, 'sample-guide.sfgz');
|
||||
exportGuideArchive(store, guideId, archiveFile);
|
||||
manifest.archive = path.relative(root, archiveFile);
|
||||
manifest.exports = outputs;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
if (args.help) {
|
||||
console.log('Usage: node scripts/make-sample-guide.js [--root <dir>]');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const root = args.root;
|
||||
const dataDir = path.join(root, 'sample-data');
|
||||
const exportsDir = path.join(root, 'sample-exports');
|
||||
cleanDir(root);
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
fs.mkdirSync(exportsDir, { recursive: true });
|
||||
|
||||
const store = new GuideStore(dataDir);
|
||||
const { guideId, substepId } = createGuide(store);
|
||||
const manifest = {
|
||||
format: 'stepforge-sample-manifest',
|
||||
version: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
guideId,
|
||||
title: store.getGuide(guideId).title,
|
||||
dataDir: path.relative(root, dataDir),
|
||||
note: 'The sample guide is generated entirely offline from local assets.',
|
||||
};
|
||||
exportOutputs(store, guideId, root, manifest);
|
||||
manifest.substepId = substepId;
|
||||
manifest.slug = slugify(manifest.title);
|
||||
writeJsonSync(path.join(root, 'sample-manifest.json'), manifest);
|
||||
console.log(`Sample guide written to ${root}`);
|
||||
}
|
||||
|
||||
if (require.main === module) main();
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VERSION="$(node -p "require('${ROOT_DIR}/package.json').version" 2>/dev/null || echo 0.0.0)"
|
||||
OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
WORK_DIR="$(mktemp -d "${OUT_DIR%/}/.pkg.XXXXXX")"
|
||||
APP_DIR="$WORK_DIR/opt/stepforge"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$APP_DIR" "$WORK_DIR/usr/bin" "$WORK_DIR/DEBIAN"
|
||||
|
||||
copy_item() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
if [[ -e "$ROOT_DIR/$src" ]]; then
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
cp -a "$ROOT_DIR/$src" "$dest"
|
||||
fi
|
||||
}
|
||||
|
||||
# Application payload: only the files needed to run the app.
|
||||
copy_item app "$APP_DIR/app"
|
||||
copy_item core "$APP_DIR/core"
|
||||
copy_item exporters "$APP_DIR/exporters"
|
||||
copy_item scripts "$APP_DIR/scripts"
|
||||
copy_item README.md "$APP_DIR/README.md"
|
||||
copy_item ARCHITECTURE.md "$APP_DIR/ARCHITECTURE.md"
|
||||
copy_item CHANGELOG.md "$APP_DIR/CHANGELOG.md"
|
||||
copy_item CODE_OF_CONDUCT.md "$APP_DIR/CODE_OF_CONDUCT.md"
|
||||
copy_item CONTRIBUTING.md "$APP_DIR/CONTRIBUTING.md"
|
||||
copy_item LICENSE "$APP_DIR/LICENSE"
|
||||
copy_item SECURITY.md "$APP_DIR/SECURITY.md"
|
||||
copy_item package.json "$APP_DIR/package.json"
|
||||
copy_item package-lock.json "$APP_DIR/package-lock.json"
|
||||
copy_item prompt.md "$APP_DIR/prompt.md"
|
||||
copy_item examples "$APP_DIR/examples"
|
||||
copy_item build/agent_audit.md "$APP_DIR/build/agent_audit.md"
|
||||
|
||||
if [[ -d "$ROOT_DIR/node_modules" ]]; then
|
||||
cp -a "$ROOT_DIR/node_modules" "$APP_DIR/node_modules"
|
||||
fi
|
||||
|
||||
cat > "$WORK_DIR/usr/bin/stepforge" <<'EOF'
|
||||
#!/usr/bin/env sh
|
||||
APP_DIR=/opt/stepforge
|
||||
cd "$APP_DIR" || exit 1
|
||||
exec "$APP_DIR/node_modules/.bin/electron" "$APP_DIR" "$@"
|
||||
EOF
|
||||
chmod 0755 "$WORK_DIR/usr/bin/stepforge"
|
||||
|
||||
cat > "$WORK_DIR/DEBIAN/control" <<EOF
|
||||
Package: stepforge
|
||||
Version: $VERSION
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Maintainer: StepForge <noreply@example.com>
|
||||
Description: Offline desktop guide capture and export tool
|
||||
A fully offline desktop app for step-by-step documentation, built for local
|
||||
capture, annotation, and export workflows.
|
||||
EOF
|
||||
|
||||
DEB_FILE="$OUT_DIR/stepforge_${VERSION}_amd64.deb"
|
||||
TAR_FILE="$OUT_DIR/stepforge_${VERSION}_linux-x64.tar.gz"
|
||||
|
||||
if command -v dpkg-deb >/dev/null 2>&1; then
|
||||
dpkg-deb --build "$WORK_DIR" "$DEB_FILE" >/dev/null
|
||||
else
|
||||
echo "dpkg-deb is not installed; skipping .deb build" >&2
|
||||
fi
|
||||
|
||||
tar -C "$WORK_DIR/opt" -czf "$TAR_FILE" stepforge
|
||||
|
||||
printf '%s\n' "$DEB_FILE"
|
||||
printf '%s\n' "$TAR_FILE"
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const { spawn } = require('node:child_process');
|
||||
|
||||
const electronPath = require('electron');
|
||||
const env = { ...process.env };
|
||||
delete env.ELECTRON_RUN_AS_NODE;
|
||||
|
||||
const child = spawn(electronPath, ['.'], {
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
windowsHide: false,
|
||||
});
|
||||
|
||||
let closed = false;
|
||||
child.on('close', (code, signal) => {
|
||||
closed = true;
|
||||
if (code === null) {
|
||||
process.exit(signal ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
process.exit(code);
|
||||
});
|
||||
|
||||
for (const signal of ['SIGINT', 'SIGTERM', 'SIGUSR2']) {
|
||||
process.on(signal, () => {
|
||||
if (!closed) child.kill(signal);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
bash tests/run_test.sh
|
||||
bash scripts/build-release.sh
|
||||
Reference in New Issue
Block a user