Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions RELEASE_PROCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ dkg update 9.0.0-beta.2 --allow-prerelease --no-verify-tag

## 7) Post-update verification

`build:runtime` is a runtime/package build. Git-based blue-green updates run the Node UI `build:ui` step separately inside the target release slot and require `packages/node-ui/dist-ui/index.html` before activation.

After each update:

```bash
Expand All @@ -136,6 +138,8 @@ cat "$DKG_HOME/releases/active"
cat "$DKG_HOME/.current-commit"
cat "$DKG_HOME/.current-version"
test ! -f "$DKG_HOME/.update-pending.json" && echo "pending state cleared"
SLOT="$(readlink -f "$DKG_HOME/releases/current")"
test -f "$SLOT/packages/node-ui/dist-ui/index.html" && echo "Node UI static bundle ready"
```

## 8) Rollback
Expand Down
4 changes: 4 additions & 0 deletions docs/testing/AUTO_UPDATE_LOCAL_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,16 @@ DKG_HOME="$DKG_HOME" dkg update 9.0.5 --no-verify-tag

## 5) Validate swap and metadata after each update

Git-based blue-green updates keep `build:runtime` runtime-focused, then build the Node UI static bundle in the target slot before swapping.

```bash
readlink "$DKG_HOME/releases/current"
cat "$DKG_HOME/releases/active"
cat "$DKG_HOME/.current-commit"
cat "$DKG_HOME/.current-version"
test ! -f "$DKG_HOME/.update-pending.json" && echo "pending state cleared"
SLOT="$(readlink -f "$DKG_HOME/releases/current")"
test -f "$SLOT/packages/node-ui/dist-ui/index.html" && echo "Node UI static bundle ready"
```

## 6) Rollback test
Expand Down
14 changes: 12 additions & 2 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
DAEMON_EXIT_CODE_RESTART,
} from './daemon.js';
import { migrateToBlueGreen } from './migration.js';
import { ensureRollbackNodeUiBundle } from './rollback-node-ui.js';
import { registerIntegrationCommands } from './integrations/commands.js';

/** Commander action callbacks receive parsed .option() values with loose types. */
Expand Down Expand Up @@ -544,7 +545,10 @@ program

// Keep blue-green slots initialized for both foreground and daemonized start.
if (!process.env.DKG_NO_BLUE_GREEN) {
await migrateToBlueGreen((msg) => console.log(msg), { allowRemoteBootstrap: false });
await migrateToBlueGreen((msg) => console.log(msg), {
allowRemoteBootstrap: false,
repairLiveNodeUi: true,
Comment thread
Jurij89 marked this conversation as resolved.
});
}

if (opts.foreground) {
Expand Down Expand Up @@ -2924,7 +2928,10 @@ program
return;
}

await migrateToBlueGreen((msg) => console.log(msg), { allowRemoteBootstrap: true });
await migrateToBlueGreen((msg) => console.log(msg), {
allowRemoteBootstrap: true,
repairLiveNodeUi: false,
});
console.log('Checking for updates and applying...');
try {
const updateStatus = await performUpdateWithStatus(au, (msg) => console.log(msg), {
Expand Down Expand Up @@ -2988,6 +2995,9 @@ program
console.error(`Slot ${target} has no build output. Run "dkg update" first to prepare it.`);
process.exit(1);
}
if (!ensureRollbackNodeUiBundle(targetDir, target)) {
Comment thread
Jurij89 marked this conversation as resolved.
Comment thread
Jurij89 marked this conversation as resolved.
Comment thread
Jurij89 marked this conversation as resolved.
process.exit(1);
}

const pid = await readPid();
if (pid && isProcessRunning(pid)) {
Expand Down
62 changes: 61 additions & 1 deletion packages/cli/src/daemon/auto-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ import {
expectedBundledMarkItDownBuildMetadata,
readCliPackageVersion,
} from '../extraction/markitdown-bundle-metadata.js';
import {
NODE_UI_PACKAGE_NAME_FALLBACKS,
nodeUiPackageJsonPath,
nodeUiPackageNamesFromCliPackageJson,
nodeUiPackageNameFromPackageJson,
nodeUiNpmStaticIndexPaths,
nodeUiStaticBuildCommand,
nodeUiStaticBuildLabel,
nodeUiStaticIndexPath,
} from '../node-ui-static.js';

const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);
Expand Down Expand Up @@ -466,6 +476,22 @@ async function _performNpmUpdateInner(
log(`Auto-update (npm): entry point missing after install. Aborting swap.`);
return "failed";
}
let npmNodeUiPackageNames = NODE_UI_PACKAGE_NAME_FALLBACKS;
try {
npmNodeUiPackageNames = nodeUiPackageNamesFromCliPackageJson(
await readFile(join(npmPkgDir, "package.json"), "utf-8"),
);
} catch {
// Older packages or damaged installs may not expose readable metadata.
}
const npmNodeUiIndexes = nodeUiNpmStaticIndexPaths(targetDir, npmNodeUiPackageNames);
if (!npmNodeUiIndexes.some((indexFile) => existsSync(indexFile))) {
log(
`Auto-update (npm): Node UI static bundle missing after install ` +
`(${npmNodeUiIndexes.join(', ')}). Aborting swap.`,
);
return "failed";
}
let resolvedVersion = readCliPackageVersion(npmPkgDir);
if (!resolvedVersion) {
resolvedVersion = targetVersion;
Expand Down Expand Up @@ -859,8 +885,9 @@ async function cleanGeneratedOutputs(
const cliPkgDir = join(packagesDir, 'cli');
await rm(join(cliPkgDir, 'network'), { recursive: true, force: true });
await rm(join(cliPkgDir, 'project.json'), { force: true });
await rm(dirname(nodeUiStaticIndexPath(targetDir)), { recursive: true, force: true });
log(
`Auto-update: cleared stale dist/ (${removedDist} pkgs) + tsconfig.tsbuildinfo (${removedTsBuildInfo} pkgs) + cli/network/ + cli/project.json before build (incremental caches preserved).`,
`Auto-update: cleared stale dist/ (${removedDist} pkgs) + tsconfig.tsbuildinfo (${removedTsBuildInfo} pkgs) + cli/network/ + cli/project.json + node-ui/dist-ui before build (incremental caches preserved).`,
);
} catch (primaryErr: any) {
log(
Expand Down Expand Up @@ -1329,6 +1356,32 @@ async function _performUpdateInner(
}
}

let nodeUiPackageNames = NODE_UI_PACKAGE_NAME_FALLBACKS;
try {
nodeUiPackageNames = [nodeUiPackageNameFromPackageJson(
await readFile(nodeUiPackageJsonPath(targetDir), 'utf-8'),
)];
} catch {
// Older/broken refs may still have a buildable UI; try known workspace names below.
}
for (let i = 0; i < nodeUiPackageNames.length; i++) {
const nodeUiPackageName = nodeUiPackageNames[i];
try {
await runBuildStep(execAsync, nodeUiStaticBuildCommand(nodeUiPackageName), {
cwd: targetDir,
timeoutMs: timeouts.build,
label: nodeUiStaticBuildLabel(nodeUiPackageName),
log,
});
break;
} catch (err) {
if (i === nodeUiPackageNames.length - 1) throw err;
log(
`Auto-update: ${nodeUiStaticBuildLabel(nodeUiPackageName)} failed; trying ${nodeUiStaticBuildLabel(nodeUiPackageNames[i + 1])}.`,
);
}
}

log("Auto-update: staging MarkItDown binary for the inactive slot...");
try {
await runBuildStep(
Expand Down Expand Up @@ -1358,6 +1411,13 @@ async function _performUpdateInner(
log(`Auto-update: build output missing (${entryFile}). Aborting swap.`);
return "failed";
}
const nodeUiIndexFile = nodeUiStaticIndexPath(targetDir);
Comment thread
Jurij89 marked this conversation as resolved.
if (!existsSync(nodeUiIndexFile)) {
Comment thread
Jurij89 marked this conversation as resolved.
log(
`Auto-update: Node UI static bundle missing (${nodeUiIndexFile}). Aborting swap.`,
);
return "failed";
}
const bundledMarkItDownAsset = currentBundledMarkItDownAssetName();
if (bundledMarkItDownAsset) {
const bundledMarkItDownPath = join(
Expand Down
Loading
Loading