Files
nocobase/tests/scripts/v3-workflow-routing.test.mjs
YANG QIA 164abe70e5 chore: move NocoBase Studio back to its private repository (#10936)
* chore: move NocoBase Studio back to its private repository

Remove packages/apps/studio and packages/tools/studio-cli, which are now
maintained in nocobase/studio, together with everything in the monorepo
that only existed for them: the fixed changeset group and their pending
and prerelease changeset entries, the Studio release image workflow and
its call from the beta release, the Studio test shards of the release
workflows, the Studio OpenAPI and database-matrix targets, and the
script tests covering the image and the CLI brand.

* docs(app-installer): describe --app with a generic published application

Replace the NocoBase Studio examples in the installer's help, flag
description, usage error and README, and in the deployment Skill, with
a generic scoped package, and use a neutral fixture name in its tests.

* chore: rerun checks on the updated CI
2026-10-16 09:40:01 +08:00

205 lines
6.8 KiB
JavaScript

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
const workflow = (name) =>
readFileSync(
new URL(`../../.github/workflows/v3-${name}.yml`, import.meta.url),
'utf8',
);
const triggerBranches = (source, event) => {
const match = source.match(
new RegExp(`^ ${event}:\\n branches:\\n((?: - .+\\n)+)`, 'mu'),
);
assert.ok(match, `${event} must declare branch filters`);
return match[1]
.trimEnd()
.split('\n')
.map((line) => line.replace(/^ - /u, '').replace(/^['"]|['"]$/gu, ''));
};
const matchesSingleSegmentGlob = (pattern, branch) => {
const expression = pattern
.split('*')
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'))
.join('[^/]*');
return new RegExp(`^${expression}$`, 'u').test(branch);
};
test('stacked v3 targets receive PR checks without widening direct-branch triggers', () => {
const changesets = workflow('changeset-check');
const guard = workflow('guard-main');
assert.deepEqual(triggerBranches(changesets, 'pull_request'), [
'v3-main',
'v3-develop',
'*/v3-*',
]);
assert.deepEqual(triggerBranches(guard, 'pull_request'), ['v3-main']);
const workflowPattern = '*/v3-*';
const botPattern = /^[^/]+\/v3-[^/]+$/u;
for (const branch of [
'feat/v3-app-installer',
'fix/v3-stacked-pr-routing',
'docs/v3-release-notes',
'feat/app-installer',
'v3-develop',
'agent/PM-42',
'feat/v3-routing/extra',
]) {
assert.equal(
matchesSingleSegmentGlob(workflowPattern, branch),
botPattern.test(branch),
branch,
);
}
});
test('OSS releases write only the migrated repository and v3 branches', () => {
for (const name of [
'release-beta',
'release-stable',
'merge-beta-to-stable',
]) {
const source = workflow(name);
assert.match(source, /repositories: nocobase\n/u);
assert.doesNotMatch(
source,
/repositories: nocobase3\n|github\.com\/nocobase\/nocobase3\.git/u,
);
assert.doesNotMatch(
source,
/--base (?:main|develop)\b|origin\/(?:main|develop)\b|git push origin (?:main|develop)\b/u,
);
assert.match(source, /RELEASE_RESULT_FEISHU_WEBHOOK_URL/u);
assert.doesNotMatch(source, /gh pr merge[^\n]*--merge/u);
assert.match(source, /gh pr merge[^\n]*--squash/u);
}
});
test('OSS releases publish through public npm with the existing token', () => {
for (const name of ['release-beta', 'release-stable']) {
const source = workflow(name);
assert.match(source, /registry-url: https:\/\/registry\.npmjs\.org/u);
assert.match(source, /NODE_AUTH_TOKEN: \$\{\{ secrets\.NPM_TOKEN \}\}/u);
assert.doesNotMatch(source, /npm\.nocobase\.ai|PRO_NPM_TOKEN/u);
}
const promotion = workflow('merge-beta-to-stable');
assert.match(promotion, /REGISTRY_URL: https:\/\/registry\.npmjs\.org/u);
assert.doesNotMatch(promotion, /npm\.nocobase\.ai/u);
});
test('the release smoke registry keeps scope isolation over public npm', () => {
const source = readFileSync(
new URL('../../.github/verdaccio/config.yaml', import.meta.url),
'utf8',
);
assert.match(source, /nocobase:\n url: https:\/\/registry\.npmjs\.org\//u);
assert.match(source, /'@nocobase\/\*':[\s\S]*?proxy: nocobase/u);
assert.doesNotMatch(source, /npm\.nocobase\.ai/u);
});
test('v3 GitHub Releases never take the shared Latest marker', () => {
const source = workflow('github-release');
assert.match(
source,
/release-beta\/\*\) FLAGS=\(--prerelease --latest=false\)/u,
);
assert.match(source, /release\/\*\)\s+FLAGS=\(--latest=false\)/u);
});
test('site deployment uses the existing publish triggers without a migration gate', () => {
for (const name of ['docs', 'ui-library']) {
const source = workflow(name);
assert.doesNotMatch(source, /V3_ASSET_DEPLOY_ENABLED/u);
assert.match(
source,
/github\.event_name == 'push' && github\.ref == 'refs\/heads\/v3-develop'/u,
);
assert.match(
source,
/github\.event_name == 'workflow_dispatch' && inputs\.publish/u,
);
assert.deepEqual(triggerBranches(source, 'pull_request'), [
'v3-develop',
'v3-main',
'*/v3-*',
]);
assert.deepEqual(triggerBranches(source, 'push'), ['v3-develop']);
assert.match(source, /publish:[\s\S]*?default: false/u);
}
const docs = workflow('docs');
for (const name of ['OSS_BUCKET', 'OSS_REGION', 'CDN_DOMAIN']) {
assert.ok(docs.includes(`secrets.V3_DOCS_ALI_${name}`));
assert.ok(!docs.includes(`secrets.DOCS_ALI_${name}`));
}
for (const name of ['docs', 'ui-library']) {
for (const key of ['ID', 'SECRET']) {
assert.ok(
workflow(name).includes(`secrets.DOCS_ALI_OSS_ACCESS_KEY_${key}`),
);
}
}
});
test('independent Pro releases keep their own repository and branches', () => {
for (const name of [
'pro-release-beta',
'pro-release-stable',
'pro-promote-to-stable',
]) {
const source = workflow(name);
assert.match(source, /repositories: nocobase3-pro/u);
assert.doesNotMatch(source, /--base v3-|git push origin v3-/u);
assert.match(source, /secrets\.PRO_NPM_REGISTRY/u);
assert.match(source, /Commercial packages must not use public npm/u);
assert.doesNotMatch(source, /npm\.nocobase\.ai/u);
}
});
test('installer smoke covers the Default archive', () => {
const smoke = workflow('app-installer-smoke');
assert.match(
smoke,
/pnpm --filter @nocobase\/app-template-default build --tar/u,
);
assert.match(
smoke,
/smoke-app-installer\.mjs --archive packages\/templates\/app-template-default\/storage\/exports\/dist\.tar\.gz/u,
);
assert.doesNotMatch(smoke, /app-template-hub|--source|schedule:/u);
for (const name of ['release-beta', 'release-stable']) {
assert.match(workflow(name), /template: \[default, examples\]/u);
assert.doesNotMatch(workflow(name), /template: \[[^\n]*\bhub\b/u);
}
});
test('Pro publishing passes the private registry and token independently', () => {
for (const name of ['pro-release-beta', 'pro-release-stable']) {
const source = workflow(name);
assert.match(
source,
/registry-url: \$\{\{ secrets\.PRO_NPM_REGISTRY \}\}/u,
);
assert.match(source, /--registry "\$PRO_NPM_REGISTRY"/u);
assert.match(
source,
/NODE_AUTH_TOKEN: \$\{\{ secrets\.PRO_NPM_TOKEN \}\}/u,
);
assert.doesNotMatch(
source,
/registry-url: https:\/\/registry\.npmjs\.org|--registry https:\/\/registry\.npmjs\.org/u,
);
}
});
test('Pro workflows retain the branch arguments supported by their source-owned sync script', () => {
assert.match(workflow('pro-release-beta'), /ARGS=\(--branch develop\)/u);
for (const name of ['pro-release-stable', 'pro-promote-to-stable']) {
assert.match(workflow(name), /ARGS=\(--branch main\)/u);
}
});