@aifabrix/builder 2.52.1 → 2.53.1
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/lib/app/deploy-config.js +3 -2
- package/lib/channels/add-channel.js +11 -41
- package/lib/channels/approval-guides/index.js +3 -0
- package/lib/channels/approval-guides/microsoft-copilot.js +26 -17
- package/lib/channels/channel-add-existing.js +72 -0
- package/lib/channels/channel-runtime-resolver.js +5 -1
- package/lib/channels/channel-status-display.js +30 -3
- package/lib/channels/microsoft-copilot-entra-sso-form.js +73 -2
- package/lib/channels/microsoft-copilot-entra-sso.js +26 -5
- package/lib/channels/microsoft-copilot-limits.js +22 -3
- package/lib/channels/microsoft-copilot-package.js +59 -14
- package/lib/channels/microsoft-copilot-validate.js +40 -10
- package/lib/cli/attach-default-help-examples.js +102 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-credential-deployment.js +7 -5
- package/lib/cli/setup-utility-validate.js +1 -0
- package/lib/commands/credential-push.js +37 -5
- package/lib/commands/package-score.js +72 -0
- package/lib/commands/role-assistant.js +46 -8
- package/lib/deployment/deployer-poll-run.js +2 -2
- package/lib/deployment/deployer.js +1 -1
- package/lib/deployment/environment.js +2 -2
- package/lib/external-system/deploy.js +11 -21
- package/lib/integration-definition/apply.js +12 -14
- package/lib/integration-definition/enrich-openapi-operations.js +9 -10
- package/lib/lab/package-parity-score.js +417 -0
- package/lib/programmatic/capability-registry.js +1 -0
- package/lib/programmatic/help-content/channel-onboarding.md +35 -0
- package/lib/programmatic/help-content/cip-overview.md +27 -0
- package/lib/programmatic/help-content/connected-system-ui.md +70 -0
- package/lib/programmatic/help-content/dimensions-guide.md +23 -0
- package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
- package/lib/programmatic/help-content/overview.md +25 -0
- package/lib/programmatic/help-content/subscription-guide.md +27 -0
- package/lib/programmatic/help-content/workflow.md +50 -0
- package/lib/programmatic/openapi-component-schemas.js +1 -0
- package/lib/programmatic/openapi-descriptions.js +6 -0
- package/lib/programmatic/openapi-envelope-examples.js +11 -0
- package/lib/programmatic/openapi-operation-bindings.js +5 -0
- package/lib/programmatic/openapi-request-schemas.js +39 -13
- package/lib/programmatic/openapi-result-schemas.js +30 -0
- package/lib/programmatic/openapi-schema-descriptions-core.js +18 -0
- package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
- package/lib/programmatic/persist-supplied-openapi.js +180 -0
- package/lib/programmatic/route-handler-map.js +6 -0
- package/lib/programmatic/run-integration-definition.js +13 -5
- package/lib/role-assistant/evidence-sync.js +55 -7
- package/lib/role-assistant/test-runner-attachments.js +118 -0
- package/lib/role-assistant/test-runner-bid-apply.js +100 -21
- package/lib/role-assistant/test-runner-bid-document.js +1 -1
- package/lib/role-assistant/test-runner-start-payload.js +96 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +11 -7
- package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
- package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
- package/lib/role-assistant/test-runner-workhub.js +68 -105
- package/lib/role-assistant/test-runner.js +5 -63
- package/lib/role-assistant/test-suite.js +33 -40
- package/lib/schema/external-datasource.schema.json +13 -4
- package/lib/utils/compose-generator.js +10 -1
- package/lib/utils/compose-traefik-extra-routes.js +107 -0
- package/lib/utils/docker-build.js +2 -2
- package/lib/utils/help-builder.js +2 -1
- package/lib/utils/promote-dev-docker-image.js +15 -10
- package/package.json +14 -16
- package/templates/README.md +12 -1
- package/templates/applications/dataplane/application.yaml +8 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +2 -1
- package/templates/applications/miso-controller/rbac.yaml +1 -0
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +6 -2
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +8 -5
- package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +74 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +29 -14
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +6 -2
- package/templates/python/docker-compose.hbs +36 -0
- package/templates/typescript/docker-compose.hbs +36 -0
- package/.cursor/plans/Archive/157.1-security-quality-violations-by-type.csv +0 -374
- package/.cursor/process/README.md +0 -9
- package/.cursor/rules/anchor-docs.mdc +0 -15
- package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
- package/.cursor/rules/cli-layout.mdc +0 -81
- package/.cursor/rules/cli-product-neutral.mdc +0 -43
- package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
- package/.cursor/rules/docs-rules.mdc +0 -32
- package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
- package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
- package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
- package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
- package/.cursor/rules/project-rules.mdc +0 -913
- package/.cursor/skills/README.md +0 -15
- package/.dockerignore +0 -52
- package/.eslintignore +0 -22
- package/.markdownlint.json +0 -14
- package/anchor-docs/README.md +0 -10
- package/anchor-docs/_TEMPLATE +0 -24
- package/babel.config.js +0 -6
- package/bin/builder-api-cleanup.js +0 -26
- package/bin/builder-api-nest.js +0 -10
- package/bin/builder-api-spike.js +0 -19
- package/enterprise-knowledge/README.md +0 -50
- package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
- package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
- package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
- package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
- package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
- package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
- package/integration/README.md +0 -81
- package/integration/github/README.md +0 -53
- package/integration/github/application.json +0 -21
- package/integration/github/deploy.js +0 -72
- package/integration/github/env.template +0 -11
- package/integration/github/github-datasource-issue-comments.json +0 -239
- package/integration/github/github-datasource-issues.json +0 -399
- package/integration/github/github-datasource-repository.json +0 -354
- package/integration/github/github-deploy.json +0 -1202
- package/integration/github/github-system.json +0 -82
- package/integration/hubspot-test/README.md +0 -159
- package/integration/hubspot-test/application.json +0 -54
- package/integration/hubspot-test/companies.json +0 -2048
- package/integration/hubspot-test/create-hubspot.js +0 -523
- package/integration/hubspot-test/env.template +0 -4
- package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
- package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
- package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
- package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
- package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
- package/integration/hubspot-test/hubspot-test-system.json +0 -74
- package/integration/hubspot-test/rbac.json +0 -166
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
- package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
- package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
- package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
- package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
- package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
- package/integration/hubspot-test/test-dataplane-down.js +0 -209
- package/integration/hubspot-test/test.js +0 -1588
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
- package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
- package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
- package/integration/roundtrip-test-local/README.md +0 -143
- package/integration/roundtrip-test-local/application.yaml +0 -13
- package/integration/roundtrip-test-local/env.template +0 -15
- package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
- package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
- package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
- package/integration/roundtrip-test-local2/README.md +0 -143
- package/integration/roundtrip-test-local2/application.yaml +0 -13
- package/integration/roundtrip-test-local2/env.template +0 -15
- package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
- package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
- package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
- package/integration/test/wizard.yaml +0 -8
- package/jest.config.coverage.js +0 -37
- package/jest.config.default.js +0 -11
- package/jest.config.integration.fixtures.js +0 -22
- package/jest.config.integration.js +0 -33
- package/jest.config.isolated.js +0 -11
- package/jest.config.manual.js +0 -30
- package/jest.global-live-fabrix-hooks.js +0 -36
- package/jest.isolated-projects.js +0 -283
- package/jest.projects.js +0 -79
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +0 -205
- package/packages/builder-api/README.md +0 -120
- package/packages/builder-api/package.json +0 -30
- package/packages/builder-api/src/app.module.ts +0 -7
- package/packages/builder-api/src/auth/auth.module.ts +0 -5
- package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
- package/packages/builder-api/src/main.ts +0 -16
- package/packages/builder-api/tsconfig.json +0 -15
- package/scripts/check-builder-api-architecture-grep.js +0 -79
- package/scripts/check-datasource-test-run-schema-sync.js +0 -34
- package/scripts/ci-fix.sh +0 -19
- package/scripts/ci-simulate.sh +0 -19
- package/scripts/diagnose-cli.js +0 -150
- package/scripts/docker_image_content_gate.js +0 -47
- package/scripts/docker_image_content_gate.sh +0 -5
- package/scripts/export-builder-api-evidence.js +0 -262
- package/scripts/install-local.js +0 -490
- package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
- package/scripts/lib/builder-api-docker-staging-core.js +0 -183
- package/scripts/lib/docker-image-content-gate-core.js +0 -166
- package/scripts/lib/plan-actor.js +0 -109
- package/scripts/lib/plan-config.js +0 -20
- package/scripts/lib/plan-gates.js +0 -154
- package/scripts/lib/plan-meta.js +0 -104
- package/scripts/lib/plan-parse.js +0 -162
- package/scripts/lib/plan-prevalidate-gates.js +0 -337
- package/scripts/lib/plan-run-core.js +0 -125
- package/scripts/lib/plan-run-report.js +0 -119
- package/scripts/plan-approve.js +0 -24
- package/scripts/plan-archive.js +0 -49
- package/scripts/plan-run.js +0 -13
- package/scripts/plan-set-actor.js +0 -21
- package/scripts/plan-validate.js +0 -80
- package/scripts/pnpm/af.mjs +0 -125
- package/scripts/pnpm/check-quiet.mjs +0 -32
- package/scripts/pnpm/dev-install.mjs +0 -29
- package/scripts/pnpm/dev-reload.mjs +0 -18
- package/scripts/pnpm/help.mjs +0 -55
- package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
- package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
- package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
- package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
- package/scripts/pnpm/lib/spinner.mjs +0 -42
- package/scripts/pnpm-global-remove.js +0 -48
- package/scripts/stage_builder_api_for_docker.js +0 -65
- package/scripts/sync-builder-api-template.js +0 -44
- package/scripts/test-dataplane-bootstrap.js +0 -331
package/scripts/pnpm/help.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const showAll = process.argv.includes('--all');
|
|
3
|
-
|
|
4
|
-
const publicSections = [
|
|
5
|
-
{
|
|
6
|
-
title: 'Start here (80/20)',
|
|
7
|
-
lines: [
|
|
8
|
-
'aifabrix setup → once (any directory)',
|
|
9
|
-
'dev-install → after clone (npm install + install:local)',
|
|
10
|
-
'dev-reload → daily (Docker live reload)',
|
|
11
|
-
'check-quiet → before commit (build:ci → .temp/validation/)',
|
|
12
|
-
'Full guide: QUICK-START.md'
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
title: 'Sometimes',
|
|
17
|
-
lines: [
|
|
18
|
-
'dev → builder-api on host (aifabrix resolve builder-api first)',
|
|
19
|
-
'ui-build → N/A (no UI in this repo)',
|
|
20
|
-
'check → lint + test (fast, not full CI)',
|
|
21
|
-
'pnpm run af ba-logs | ba-down'
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const advancedLines = [
|
|
27
|
-
'--- Advanced (help -- --all) ---',
|
|
28
|
-
'dev-build / dev-promote (tags main + version) / af ba-*',
|
|
29
|
-
'build:ci → lint + schema-sync + flags + test:ci (same as GitHub)',
|
|
30
|
-
'sync-json → publish builder/builder-api → templates/',
|
|
31
|
-
'test:154 → builder-api scoped Jest + arch grep',
|
|
32
|
-
'test:manual → live API tests (aifabrix login first)',
|
|
33
|
-
'test:integration → integration fixtures (TEST_LANGUAGE=python|typescript optional)',
|
|
34
|
-
'test:hubspot-wizard [-- --type negative|positive]',
|
|
35
|
-
'docker-image-gate-builder-api',
|
|
36
|
-
'builder-api:build | builder-api:nest | builder-api:cleanup',
|
|
37
|
-
'install:global / uninstall:global / diagnose:cli',
|
|
38
|
-
'Docs: docs/development/pnpm-commands.md'
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
console.log('AI Fabrix Builder — pnpm commands\n');
|
|
42
|
-
for (const section of publicSections) {
|
|
43
|
-
console.log(section.title);
|
|
44
|
-
for (const line of section.lines) {
|
|
45
|
-
console.log(` ${line}`);
|
|
46
|
-
}
|
|
47
|
-
console.log('');
|
|
48
|
-
}
|
|
49
|
-
if (!showAll) {
|
|
50
|
-
console.log('Run: pnpm run help -- --all');
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
for (const line of advancedLines) {
|
|
54
|
-
console.log(line);
|
|
55
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { resolveAifabrixBin } from './resolve-aifabrix-bin.mjs';
|
|
6
|
-
import { resolveAppUrl } from './resolve-app-url.mjs';
|
|
7
|
-
import { createSpinner } from './spinner.mjs';
|
|
8
|
-
|
|
9
|
-
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
10
|
-
const LOG_FILE = 'dev-reload.log';
|
|
11
|
-
const CAPTURE_PID_FILE = 'dev-reload-capture.pid';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {Object} DevReloadProfile
|
|
15
|
-
* @property {string} appKey
|
|
16
|
-
* @property {string} displayName
|
|
17
|
-
* @property {number} portBase
|
|
18
|
-
* @property {string} [webServerUrlEnvKey]
|
|
19
|
-
* @property {string} [healthPath]
|
|
20
|
-
* @property {boolean} [apiOnly]
|
|
21
|
-
* @property {string} [uiBuildCommand]
|
|
22
|
-
* @property {string} [uiOutputDir]
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {DevReloadProfile} profile
|
|
27
|
-
* @returns {Promise<void>}
|
|
28
|
-
*/
|
|
29
|
-
export async function runDevReload(profile) {
|
|
30
|
-
const logPath = prepareLogFile();
|
|
31
|
-
appendSessionBanner(profile.appKey, logPath);
|
|
32
|
-
|
|
33
|
-
const aifabrix = resolveAifabrixBin();
|
|
34
|
-
const spinner = createSpinner(`Starting ${profile.displayName} (live reload)…`);
|
|
35
|
-
spinner.start();
|
|
36
|
-
|
|
37
|
-
await runToLogFile(aifabrix, ['resolve', profile.appKey], logPath);
|
|
38
|
-
const runExit = await runToLogFile(aifabrix, ['run', profile.appKey, '--reload'], logPath);
|
|
39
|
-
if (runExit !== 0) {
|
|
40
|
-
spinner.stop();
|
|
41
|
-
printRunFailure(logPath);
|
|
42
|
-
process.exit(runExit);
|
|
43
|
-
}
|
|
44
|
-
spinner.stop();
|
|
45
|
-
|
|
46
|
-
startBackgroundLogCapture(profile.appKey, logPath);
|
|
47
|
-
printDevReloadSummary(profile, logPath);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @returns {string}
|
|
52
|
-
*/
|
|
53
|
-
function prepareLogFile() {
|
|
54
|
-
const logDir = path.join(repoRoot, '.temp');
|
|
55
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
56
|
-
const logPath = path.join(logDir, LOG_FILE);
|
|
57
|
-
fs.writeFileSync(logPath, '');
|
|
58
|
-
return logPath;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* @returns {string}
|
|
63
|
-
*/
|
|
64
|
-
function capturePidPath() {
|
|
65
|
-
return path.join(repoRoot, '.temp', CAPTURE_PID_FILE);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function stopPreviousCapture() {
|
|
69
|
-
const pidPath = capturePidPath();
|
|
70
|
-
if (!fs.existsSync(pidPath)) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const pid = Number.parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
74
|
-
if (!pid) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
process.kill(pid, 'SIGTERM');
|
|
79
|
-
} catch {
|
|
80
|
-
// already stopped
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {string} appKey
|
|
86
|
-
* @param {string} logPath
|
|
87
|
-
*/
|
|
88
|
-
function startBackgroundLogCapture(appKey, logPath) {
|
|
89
|
-
stopPreviousCapture();
|
|
90
|
-
|
|
91
|
-
const captureScript = path.join(
|
|
92
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
93
|
-
'dev-reload-log-capture.mjs'
|
|
94
|
-
);
|
|
95
|
-
const child = spawn(process.execPath, [captureScript, appKey, logPath], {
|
|
96
|
-
cwd: repoRoot,
|
|
97
|
-
env: process.env,
|
|
98
|
-
detached: true,
|
|
99
|
-
stdio: 'ignore'
|
|
100
|
-
});
|
|
101
|
-
child.unref();
|
|
102
|
-
fs.writeFileSync(capturePidPath(), String(child.pid));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @param {DevReloadProfile} profile
|
|
107
|
-
* @param {string} logPath
|
|
108
|
-
*/
|
|
109
|
-
function printDevReloadSummary(profile, logPath) {
|
|
110
|
-
const appUrl = resolveAppUrl(profile, { repoRoot }).replace(/\/+$/, '');
|
|
111
|
-
const absLog = path.resolve(logPath);
|
|
112
|
-
const healthUrl = profile.healthPath ? `${appUrl}${profile.healthPath}` : `${appUrl}/`;
|
|
113
|
-
|
|
114
|
-
console.log('');
|
|
115
|
-
console.log('═'.repeat(60));
|
|
116
|
-
console.log(` ${profile.displayName} — live reload (Docker)`);
|
|
117
|
-
console.log('═'.repeat(60));
|
|
118
|
-
console.log('');
|
|
119
|
-
console.log(` App URL: ${appUrl}/`);
|
|
120
|
-
if (profile.healthPath) {
|
|
121
|
-
console.log(` Health: ${healthUrl}`);
|
|
122
|
-
}
|
|
123
|
-
console.log(` Logs: ${absLog}`);
|
|
124
|
-
console.log('');
|
|
125
|
-
console.log(' Backend code on mounted paths reloads automatically.');
|
|
126
|
-
if (!profile.apiOnly && profile.uiBuildCommand && profile.uiOutputDir) {
|
|
127
|
-
console.log('');
|
|
128
|
-
console.log(' UI changes: run');
|
|
129
|
-
console.log(` ${profile.uiBuildCommand}`);
|
|
130
|
-
console.log(` → ${profile.uiOutputDir}`);
|
|
131
|
-
}
|
|
132
|
-
console.log('');
|
|
133
|
-
console.log(' Log capture runs in the background (this command exits).');
|
|
134
|
-
console.log('═'.repeat(60));
|
|
135
|
-
console.log('');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* @param {string} logPath
|
|
140
|
-
*/
|
|
141
|
-
function printRunFailure(logPath) {
|
|
142
|
-
console.error(`\n[dev-reload] Start failed. Logs: ${path.resolve(logPath)}\n`);
|
|
143
|
-
if (!fs.existsSync(logPath)) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
147
|
-
const tail = lines.slice(-15).join('\n');
|
|
148
|
-
if (tail) {
|
|
149
|
-
console.error(tail);
|
|
150
|
-
console.error('');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* @param {string} appKey
|
|
156
|
-
* @param {string} logPath
|
|
157
|
-
*/
|
|
158
|
-
function appendSessionBanner(appKey, logPath) {
|
|
159
|
-
const banner =
|
|
160
|
-
`\n${'='.repeat(72)}\n` +
|
|
161
|
-
`[${new Date().toISOString()}] dev-reload session: ${appKey}\n` +
|
|
162
|
-
`${'='.repeat(72)}\n`;
|
|
163
|
-
fs.appendFileSync(logPath, banner);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @param {string} command
|
|
168
|
-
* @param {string[]} args
|
|
169
|
-
* @param {string} logPath
|
|
170
|
-
* @returns {Promise<number>}
|
|
171
|
-
*/
|
|
172
|
-
function runToLogFile(command, args, logPath) {
|
|
173
|
-
return new Promise((resolve) => {
|
|
174
|
-
const proc = spawn(command, args, {
|
|
175
|
-
cwd: repoRoot,
|
|
176
|
-
env: process.env,
|
|
177
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const append = (chunk) => {
|
|
181
|
-
fs.appendFileSync(logPath, chunk);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
proc.stdout.on('data', append);
|
|
185
|
-
proc.stderr.on('data', append);
|
|
186
|
-
proc.on('close', (code) => resolve(typeof code === 'number' ? code : 1));
|
|
187
|
-
proc.on('error', () => resolve(1));
|
|
188
|
-
});
|
|
189
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Background log capture for dev-reload (detached child).
|
|
4
|
-
* Usage: node dev-reload-log-capture.mjs <appKey> <logPath>
|
|
5
|
-
*/
|
|
6
|
-
import { spawn } from 'node:child_process';
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import readline from 'node:readline';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
import { resolveAifabrixBin } from './resolve-aifabrix-bin.mjs';
|
|
12
|
-
import { resolveLogKeys } from './dev-reload-log-utils.mjs';
|
|
13
|
-
|
|
14
|
-
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
15
|
-
const appKey = process.argv[2];
|
|
16
|
-
const logPath = process.argv[3];
|
|
17
|
-
|
|
18
|
-
if (!appKey || !logPath) {
|
|
19
|
-
console.error('Usage: node dev-reload-log-capture.mjs <appKey> <logPath>');
|
|
20
|
-
process.exit(2);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const aifabrix = resolveAifabrixBin();
|
|
24
|
-
const logKeys = resolveLogKeys(repoRoot, appKey);
|
|
25
|
-
|
|
26
|
-
await Promise.all(
|
|
27
|
-
logKeys.map((key) => followAppLogsToFile(aifabrix, key, logPath, key === appKey))
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {string} aifabrix
|
|
32
|
-
* @param {string} key
|
|
33
|
-
* @param {string} filePath
|
|
34
|
-
* @param {boolean} required
|
|
35
|
-
*/
|
|
36
|
-
function followAppLogsToFile(aifabrix, key, filePath, required) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
const proc = spawn(aifabrix, ['logs', key, '-f', '--tail', '100'], {
|
|
39
|
-
cwd: repoRoot,
|
|
40
|
-
env: process.env,
|
|
41
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const prefix = `[${key}] `;
|
|
45
|
-
const onLine = (line) => {
|
|
46
|
-
fs.appendFileSync(filePath, `${new Date().toISOString()} ${prefix}${line}\n`);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
readline.createInterface({ input: proc.stdout }).on('line', onLine);
|
|
50
|
-
readline.createInterface({ input: proc.stderr }).on('line', onLine);
|
|
51
|
-
|
|
52
|
-
proc.on('close', (code) => {
|
|
53
|
-
if (code !== 0 && required) {
|
|
54
|
-
reject(new Error(`Log capture ended for ${key} (exit ${code ?? 1})`));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
resolve();
|
|
58
|
-
});
|
|
59
|
-
proc.on('error', (err) => {
|
|
60
|
-
if (required) {
|
|
61
|
-
reject(err);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
resolve();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @param {string} repoRoot
|
|
6
|
-
* @param {string} appKey
|
|
7
|
-
* @returns {string[]}
|
|
8
|
-
*/
|
|
9
|
-
export function resolveLogKeys(repoRoot, appKey) {
|
|
10
|
-
return [appKey, ...readCompanionKeys(repoRoot, appKey)];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} repoRoot
|
|
15
|
-
* @param {string} appKey
|
|
16
|
-
* @returns {string[]}
|
|
17
|
-
*/
|
|
18
|
-
function readCompanionKeys(repoRoot, appKey) {
|
|
19
|
-
const yamlPath = path.join(repoRoot, 'builder', appKey, 'application.yaml');
|
|
20
|
-
if (!fs.existsSync(yamlPath)) {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
const text = fs.readFileSync(yamlPath, 'utf8');
|
|
24
|
-
if (!/^\s*worker:\s*true\s*$/m.test(text)) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
const companions = [`${appKey}-worker`];
|
|
28
|
-
if (/^\s*imageRunBeat:/m.test(text)) {
|
|
29
|
-
companions.push(`${appKey}-beat`);
|
|
30
|
-
}
|
|
31
|
-
return companions;
|
|
32
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
8
|
-
|
|
9
|
-
/** application.yaml / platform apps need manifest-aware builder (not variables.yaml-only CLIs). */
|
|
10
|
-
const MIN_AIFABRIX_VERSION = '2.45.0';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {string} version - Semver string.
|
|
14
|
-
* @returns {number[]} Parsed numeric semver segments.
|
|
15
|
-
*/
|
|
16
|
-
function parseSemver(version) {
|
|
17
|
-
return String(version)
|
|
18
|
-
.trim()
|
|
19
|
-
.split('.')
|
|
20
|
-
.map((part) => parseInt(part, 10) || 0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {string} left - Left semver.
|
|
25
|
-
* @param {string} right - Right semver.
|
|
26
|
-
* @returns {number} Compare result (1, 0, -1).
|
|
27
|
-
*/
|
|
28
|
-
function compareSemver(left, right) {
|
|
29
|
-
const a = parseSemver(left);
|
|
30
|
-
const b = parseSemver(right);
|
|
31
|
-
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
32
|
-
const av = a[i] || 0;
|
|
33
|
-
const bv = b[i] || 0;
|
|
34
|
-
if (av > bv) return 1;
|
|
35
|
-
if (av < bv) return -1;
|
|
36
|
-
}
|
|
37
|
-
return 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {string} bin - Candidate executable path.
|
|
42
|
-
* @returns {string|null} CLI version output or null.
|
|
43
|
-
*/
|
|
44
|
-
function readAifabrixVersion(bin) {
|
|
45
|
-
try {
|
|
46
|
-
return execFileSync(bin, ['--version'], { encoding: 'utf8' }).trim();
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @returns {string[]} Candidate aifabrix binary paths.
|
|
54
|
-
*/
|
|
55
|
-
function collectAifabrixCandidates() {
|
|
56
|
-
const candidates = [];
|
|
57
|
-
const seen = new Set();
|
|
58
|
-
|
|
59
|
-
const push = (candidate) => {
|
|
60
|
-
if (!candidate || seen.has(candidate)) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
seen.add(candidate);
|
|
64
|
-
candidates.push(candidate);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
push(path.join(repoRoot, 'bin', 'aifabrix.js'));
|
|
68
|
-
|
|
69
|
-
if (process.env.AIFABRIX_CLI) {
|
|
70
|
-
push(process.env.AIFABRIX_CLI);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const home = os.homedir();
|
|
74
|
-
push(path.join(home, '.npm-global', 'bin', 'aifabrix'));
|
|
75
|
-
push(path.join(home, '.local', 'bin', 'aifabrix'));
|
|
76
|
-
|
|
77
|
-
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
78
|
-
if (!dir) continue;
|
|
79
|
-
push(path.join(dir, 'aifabrix'));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return candidates;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Prefer this repo's bin/aifabrix.js when developing the builder package locally.
|
|
87
|
-
*
|
|
88
|
-
* @returns {string} Resolved aifabrix executable path.
|
|
89
|
-
*/
|
|
90
|
-
export function resolveAifabrixBin() {
|
|
91
|
-
let bestBin = null;
|
|
92
|
-
let bestVersion = null;
|
|
93
|
-
|
|
94
|
-
for (const candidate of collectAifabrixCandidates()) {
|
|
95
|
-
let resolved;
|
|
96
|
-
try {
|
|
97
|
-
if (fs.existsSync(candidate)) {
|
|
98
|
-
resolved = fs.realpathSync(candidate);
|
|
99
|
-
} else {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const version = readAifabrixVersion(resolved);
|
|
107
|
-
if (!version) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (!bestVersion || compareSemver(version, bestVersion) > 0) {
|
|
111
|
-
bestBin = resolved;
|
|
112
|
-
bestVersion = version;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!bestBin || !bestVersion || compareSemver(bestVersion, MIN_AIFABRIX_VERSION) < 0) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
`@aifabrix/builder >= ${MIN_AIFABRIX_VERSION} is required (found ${bestVersion || 'none'}). ` +
|
|
119
|
-
'Install: npm run install:local — or set AIFABRIX_CLI to a newer aifabrix binary.'
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return bestBin;
|
|
124
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {Object} AppUrlProfile
|
|
8
|
-
* @property {string} appKey
|
|
9
|
-
* @property {number} portBase
|
|
10
|
-
* @property {string} [webServerUrlEnvKey]
|
|
11
|
-
* @property {string} [healthPath]
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @returns {string}
|
|
16
|
-
*/
|
|
17
|
-
function readDeveloperIdRaw() {
|
|
18
|
-
const configPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
19
|
-
if (!fs.existsSync(configPath)) {
|
|
20
|
-
return '0';
|
|
21
|
-
}
|
|
22
|
-
const match = fs.readFileSync(configPath, 'utf8').match(/^developer-id:\s*['"]?(\d+)['"]?\s*$/m);
|
|
23
|
-
return match ? match[1] : '0';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @param {string} appKey
|
|
28
|
-
* @param {string} developerIdRaw
|
|
29
|
-
* @returns {string}
|
|
30
|
-
*/
|
|
31
|
-
function buildContainerName(appKey, developerIdRaw) {
|
|
32
|
-
const idNum = Number.parseInt(developerIdRaw, 10) || 0;
|
|
33
|
-
if (idNum === 0) {
|
|
34
|
-
return `aifabrix-${appKey}`;
|
|
35
|
-
}
|
|
36
|
-
return `aifabrix-dev${developerIdRaw}-${appKey}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @param {string} filePath
|
|
41
|
-
* @returns {Record<string, string>}
|
|
42
|
-
*/
|
|
43
|
-
function parseEnvFile(filePath) {
|
|
44
|
-
const values = {};
|
|
45
|
-
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
|
46
|
-
const trimmed = line.trim();
|
|
47
|
-
if (!trimmed || trimmed.startsWith('#')) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const eq = trimmed.indexOf('=');
|
|
51
|
-
if (eq === -1) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const key = trimmed.slice(0, eq).trim();
|
|
55
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
56
|
-
if (
|
|
57
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
58
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
59
|
-
) {
|
|
60
|
-
value = value.slice(1, -1);
|
|
61
|
-
}
|
|
62
|
-
values[key] = value;
|
|
63
|
-
}
|
|
64
|
-
return values;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* @param {string} raw
|
|
69
|
-
* @returns {string|null}
|
|
70
|
-
*/
|
|
71
|
-
function normalizeHttpUrl(raw) {
|
|
72
|
-
const trimmed = raw?.trim();
|
|
73
|
-
if (!trimmed || trimmed.startsWith('kv://')) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const parsed = new URL(trimmed);
|
|
78
|
-
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
return parsed.toString().replace(/\/+$/, '');
|
|
82
|
-
} catch {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @param {string} repoRoot
|
|
89
|
-
* @param {string} appKey
|
|
90
|
-
* @param {string} envKey
|
|
91
|
-
* @returns {string|null}
|
|
92
|
-
*/
|
|
93
|
-
function readBuilderEnvUrl(repoRoot, appKey, envKey) {
|
|
94
|
-
const envPath = path.join(repoRoot, 'builder', appKey, '.env');
|
|
95
|
-
if (!fs.existsSync(envPath)) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
return normalizeHttpUrl(parseEnvFile(envPath)[envKey]);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* @param {string} containerName
|
|
103
|
-
* @param {string} envKey
|
|
104
|
-
* @returns {string|null}
|
|
105
|
-
*/
|
|
106
|
-
function readContainerEnvUrl(containerName, envKey) {
|
|
107
|
-
const result = spawnSync('docker', ['exec', containerName, 'env'], {
|
|
108
|
-
encoding: 'utf8',
|
|
109
|
-
timeout: 8000
|
|
110
|
-
});
|
|
111
|
-
if (result.status !== 0) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
for (const line of result.stdout.split('\n')) {
|
|
115
|
-
if (!line.startsWith(`${envKey}=`)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
return normalizeHttpUrl(line.slice(envKey.length + 1));
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* @param {string} containerName
|
|
125
|
-
* @returns {string|null}
|
|
126
|
-
*/
|
|
127
|
-
function readDockerPublishedUrl(containerName) {
|
|
128
|
-
const result = spawnSync(
|
|
129
|
-
'docker',
|
|
130
|
-
['ps', '--filter', `name=^/${containerName}$`, '--format', '{{.Ports}}'],
|
|
131
|
-
{ encoding: 'utf8', timeout: 8000 }
|
|
132
|
-
);
|
|
133
|
-
if (result.status !== 0) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
const portMatch = result.stdout.match(/:(\d+)->\d+\//);
|
|
137
|
-
if (!portMatch) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
return `http://localhost:${portMatch[1]}`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @param {number} portBase
|
|
145
|
-
* @returns {number}
|
|
146
|
-
*/
|
|
147
|
-
function resolveHostPort(portBase) {
|
|
148
|
-
const devId = Number.parseInt(readDeveloperIdRaw(), 10) || 0;
|
|
149
|
-
return devId === 0 ? portBase : portBase + devId * 100;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* @param {AppUrlProfile} profile
|
|
154
|
-
* @param {{ repoRoot: string }} options
|
|
155
|
-
* @returns {string}
|
|
156
|
-
*/
|
|
157
|
-
export function resolveAppUrl(profile, options) {
|
|
158
|
-
const { repoRoot } = options;
|
|
159
|
-
const envKey = profile.webServerUrlEnvKey;
|
|
160
|
-
const containerName = buildContainerName(profile.appKey, readDeveloperIdRaw());
|
|
161
|
-
|
|
162
|
-
if (envKey) {
|
|
163
|
-
const fromContainer = readContainerEnvUrl(containerName, envKey);
|
|
164
|
-
if (fromContainer) {
|
|
165
|
-
return `${fromContainer}/`;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const fromDockerPort = readDockerPublishedUrl(containerName);
|
|
170
|
-
if (fromDockerPort) {
|
|
171
|
-
return `${fromDockerPort}/`;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (envKey) {
|
|
175
|
-
const fromBuilderEnv = readBuilderEnvUrl(repoRoot, profile.appKey, envKey);
|
|
176
|
-
if (fromBuilderEnv) {
|
|
177
|
-
return `${fromBuilderEnv}/`;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const port = resolveHostPort(profile.portBase);
|
|
182
|
-
return `http://localhost:${port}/`;
|
|
183
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/** Minimal TTY spinner (no extra dependencies). */
|
|
2
|
-
|
|
3
|
-
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {string} label
|
|
7
|
-
* @returns {{ start: () => void, stop: (finalLine?: string) => void }}
|
|
8
|
-
*/
|
|
9
|
-
export function createSpinner(label) {
|
|
10
|
-
let frameIndex = 0;
|
|
11
|
-
/** @type {NodeJS.Timeout | null} */
|
|
12
|
-
let timer = null;
|
|
13
|
-
const enabled = process.stdout.isTTY && process.env.CI !== 'true';
|
|
14
|
-
|
|
15
|
-
function clearLine() {
|
|
16
|
-
if (enabled) {
|
|
17
|
-
process.stdout.write('\r\x1b[K');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
start() {
|
|
23
|
-
if (!enabled) {
|
|
24
|
-
console.log(label);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
timer = setInterval(() => {
|
|
28
|
-
process.stdout.write(`\r${FRAMES[frameIndex++ % FRAMES.length]} ${label}`);
|
|
29
|
-
}, 80);
|
|
30
|
-
},
|
|
31
|
-
stop(finalLine) {
|
|
32
|
-
if (timer) {
|
|
33
|
-
clearInterval(timer);
|
|
34
|
-
timer = null;
|
|
35
|
-
}
|
|
36
|
-
clearLine();
|
|
37
|
-
if (finalLine) {
|
|
38
|
-
console.log(finalLine);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|