Skip to content

Commit 60dc269

Browse files
feat: align aiox-pro naming, sync, and update flow (#625)
* feat: add pro submodule sync fallback [Story 123.1] * fix: bootstrap pro repo during fallback sync [Story 123.1] * fix: align pro sync workflow with submodule config [Story 123.1] * fix: harden pro sync SHA resolution [Story 123.1] * fix: resolve Pro package from both @aiox-fullstack/pro and @aios-fullstack/pro [Story 122.1] - pro-detector: add npm package resolution with canonical/fallback priority - pro CLI commands: resolve license path from both scopes - pro-setup wizard: loadProModule tries both npm scopes - pro-scaffolder: resolve source dir from both npm scopes - aiox-pro-cli: isProInstalled checks both scopes - i18n: update user-facing messages to active package name - CI: update publish workflow to active package name - tests: update pro-detector tests for new API (21/21 passing) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat: implement aiox pro update command with re-scaffold [Story 122.3] - Add pro-updater.js: detect installed Pro, query npm, check compat, update, re-scaffold - Register 'aiox pro update' subcommand with --check, --dry-run, --force, --include-core, --skip-scaffold - Add 'aiox update --include-pro' to update core + Pro in one command - License validation before update (skip on --check/--dry-run) - Detects package manager (npm/pnpm/yarn/bun) automatically - Re-scaffolds Pro assets (squads, skills, configs) after package update - Formatted CLI output with update summary Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: resolve Pro CI regressions after dual-scope support [Story 122.3] * fix: address Pro review blockers [Story 122.3] * fix: address remaining pro updater review notes [Story 122.3] * fix: address final pro updater review blockers [Story 122.3] * fix: stabilize pro scaffolder import [Story 122.3] * fix: finalize aiox-pro alignment and update flow [Story 123.2] * fix: regenerate install manifest for clean tree [Story 123.2] * fix: address remaining Pro review feedback [Story 123.2] * fix: regenerate install manifest for clean tree [Story 123.2] --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 3c2cde8 commit 60dc269

19 files changed

Lines changed: 1710 additions & 180 deletions

File tree

.aiox-core/cli/commands/pro/index.js

Lines changed: 185 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* aiox pro deactivate Deactivate the current license
1010
* aiox pro features List all pro features
1111
* aiox pro validate Force online revalidation
12-
* aiox pro setup Configure GitHub Packages access (AC-12)
12+
* aiox pro setup Install or verify the AIOX Pro package
1313
*
1414
* @module cli/commands/pro
1515
* @version 1.1.0
@@ -25,31 +25,44 @@ const readline = require('readline');
2525

2626
// BUG-6 fix (INS-1): Dynamic licensePath resolution
2727
// In framework-dev: __dirname = aiox-core/.aiox-core/cli/commands/pro → ../../../../pro/license
28-
// In project-dev: pro is installed via npm as @aiox-fullstack/pro
28+
// In project-dev: pro is installed via npm as @aiox-fullstack/pro or @aios-fullstack/pro
2929
function resolveLicensePath() {
3030
// 1. Try relative path (framework-dev mode)
3131
const relativePath = path.resolve(__dirname, '..', '..', '..', '..', 'pro', 'license');
3232
if (fs.existsSync(relativePath)) {
3333
return relativePath;
3434
}
3535

36-
// 2. Try node_modules/@aiox-fullstack/pro/license (project-dev mode)
37-
try {
38-
const proPkg = require.resolve('@aiox-fullstack/pro/package.json');
39-
const proDir = path.dirname(proPkg);
40-
const npmPath = path.join(proDir, 'license');
41-
if (fs.existsSync(npmPath)) {
42-
return npmPath;
36+
// 2. Try npm packages — canonical then fallback
37+
const npmCandidates = [
38+
'@aiox-fullstack/pro',
39+
'@aios-fullstack/pro',
40+
];
41+
42+
for (const pkgName of npmCandidates) {
43+
try {
44+
const proPkg = require.resolve(`${pkgName}/package.json`);
45+
const proDir = path.dirname(proPkg);
46+
const npmPath = path.join(proDir, 'license');
47+
if (fs.existsSync(npmPath)) {
48+
return npmPath;
49+
}
50+
} catch {
51+
// package not installed
4352
}
44-
} catch {
45-
// @aiox-fullstack/pro not installed via npm
4653
}
4754

48-
// 3. Try project root node_modules (fallback)
55+
// 3. Try project root node_modules (both scopes)
4956
const projectRoot = process.cwd();
50-
const cwdPath = path.join(projectRoot, 'node_modules', '@aiox-fullstack', 'pro', 'license');
51-
if (fs.existsSync(cwdPath)) {
52-
return cwdPath;
57+
const scopePaths = [
58+
path.join(projectRoot, 'node_modules', '@aiox-fullstack', 'pro', 'license'),
59+
path.join(projectRoot, 'node_modules', '@aios-fullstack', 'pro', 'license'),
60+
];
61+
62+
for (const cwdPath of scopePaths) {
63+
if (fs.existsSync(cwdPath)) {
64+
return cwdPath;
65+
}
5366
}
5467

5568
// Return relative path as default (will fail gracefully in loadLicenseModules)
@@ -97,7 +110,8 @@ function loadLicenseModules() {
97110
};
98111
} catch (error) {
99112
console.error('AIOX Pro license module not available.');
100-
console.error('Install AIOX Pro: npm install @aiox-fullstack/pro');
113+
console.error('Install AIOX Pro: aiox pro setup');
114+
console.error('Or via wrapper: npx aiox-pro install');
101115
process.exit(1);
102116
}
103117
}
@@ -217,9 +231,13 @@ async function activateAction(options) {
217231
// Scaffold pro content into project (Story INS-3.1)
218232
// Lazy-load to avoid crashing if pro-scaffolder or js-yaml is unavailable
219233
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..');
220-
const proSourceDir = path.join(projectRoot, 'node_modules', '@aiox-fullstack', 'pro');
234+
// Try canonical then fallback package path
235+
const proSourceDir = [
236+
path.join(projectRoot, 'node_modules', '@aiox-fullstack', 'pro'),
237+
path.join(projectRoot, 'node_modules', '@aios-fullstack', 'pro'),
238+
].find(p => fs.existsSync(p));
221239

222-
if (fs.existsSync(proSourceDir)) {
240+
if (proSourceDir) {
223241
let scaffoldProContent;
224242
try {
225243
({ scaffoldProContent } = require('../../../../packages/installer/src/pro/pro-scaffolder'));
@@ -261,7 +279,7 @@ async function activateAction(options) {
261279
console.log('');
262280
}
263281
} else {
264-
console.log('Note: @aiox-fullstack/pro package not found in node_modules.');
282+
console.log('Note: AIOX Pro package not found in node_modules.');
265283
console.log('Pro content will be scaffolded when the package is installed.');
266284
console.log('');
267285
}
@@ -571,64 +589,108 @@ async function validateAction() {
571589
// ---------------------------------------------------------------------------
572590

573591
/**
574-
* Setup and verify @aiox-fullstack/pro installation.
592+
* Setup and verify AIOX Pro installation.
575593
*
576-
* Since @aiox-fullstack/pro is published on the public npm registry,
577-
* no special token or .npmrc configuration is needed. This command
578-
* installs the package and verifies it's working.
594+
* Tries canonical @aiox-fullstack/pro first, falls back to @aios-fullstack/pro.
579595
*
580596
* @param {object} options - Command options
581597
* @param {boolean} options.verify - Only verify without installing
582598
*/
583599
async function setupAction(options) {
600+
const PRO_PACKAGES = ['@aiox-fullstack/pro', '@aios-fullstack/pro'];
601+
584602
console.log('\nAIOX Pro - Setup\n');
585603

586604
if (options.verify) {
587-
// Verify-only mode
588-
console.log('Verifying @aiox-fullstack/pro installation...\n');
605+
console.log('Verifying AIOX Pro installation...\n');
589606

590607
try {
591608
const { execSync } = require('child_process');
592-
const result = execSync('npm ls @aiox-fullstack/pro --json', {
593-
stdio: 'pipe',
594-
timeout: 15000,
595-
});
596-
const parsed = JSON.parse(result.toString());
597-
const deps = parsed.dependencies || {};
598-
if (deps['@aiox-fullstack/pro']) {
599-
console.log(`✅ @aiox-fullstack/pro@${deps['@aiox-fullstack/pro'].version} is installed`);
600-
} else {
601-
console.log('❌ @aiox-fullstack/pro is not installed');
609+
let found = false;
610+
for (const pkg of PRO_PACKAGES) {
611+
try {
612+
const result = execSync(`npm ls ${pkg} --json`, { stdio: 'pipe', timeout: 15000 });
613+
const parsed = JSON.parse(result.toString());
614+
const deps = parsed.dependencies || {};
615+
if (deps[pkg]) {
616+
console.log(`✅ ${pkg}@${deps[pkg].version} is installed`);
617+
found = true;
618+
break;
619+
}
620+
} catch { /* try next */ }
621+
}
622+
if (!found) {
623+
console.log('❌ AIOX Pro is not installed');
602624
console.log('');
603625
console.log('Install with:');
604-
console.log(' npm install @aiox-fullstack/pro');
626+
console.log(' aiox pro setup');
627+
console.log(' # or npx aiox-pro install');
605628
}
606629
} catch {
607-
console.log('❌ @aiox-fullstack/pro is not installed');
630+
console.log('❌ AIOX Pro is not installed');
608631
console.log('');
609632
console.log('Install with:');
610-
console.log(' npm install @aiox-fullstack/pro');
633+
console.log(' aiox pro setup');
634+
console.log(' # or npx aiox-pro install');
611635
}
612636
return;
613637
}
614638

615-
// Install mode
616-
console.log('@aiox-fullstack/pro is available on the public npm registry.');
639+
// Install mode — try canonical first, fallback second
640+
console.log('AIOX Pro is available on the public npm registry.');
617641
console.log('No special tokens or configuration needed.\n');
618642

619-
console.log('Installing @aiox-fullstack/pro...\n');
643+
const { execSync } = require('child_process');
644+
let installedPackage = null;
620645

621-
try {
622-
const { execSync } = require('child_process');
623-
execSync('npm install @aiox-fullstack/pro', {
624-
stdio: 'inherit',
625-
timeout: 120000,
626-
});
627-
console.log('\n✅ @aiox-fullstack/pro installed successfully!');
628-
} catch (error) {
629-
console.error(`\n❌ Installation failed: ${error.message}`);
646+
function getInstallErrorOutput(error) {
647+
return [
648+
error?.message,
649+
error?.stderr?.toString?.(),
650+
error?.stdout?.toString?.(),
651+
].filter(Boolean).join('\n');
652+
}
653+
654+
function isPackageNotFoundError(error, pkg) {
655+
const output = getInstallErrorOutput(error).toLowerCase();
656+
const packageName = pkg.toLowerCase();
657+
658+
if (!output.includes(packageName)) {
659+
return false;
660+
}
661+
662+
return output.includes('e404')
663+
|| output.includes('npm err! 404')
664+
|| output.includes(' is not in this registry')
665+
|| output.includes(' not found');
666+
}
667+
668+
for (const pkg of PRO_PACKAGES) {
669+
try {
670+
console.log(`Installing ${pkg}...\n`);
671+
execSync(`npm install ${pkg}`, { stdio: 'inherit', timeout: 120000 });
672+
console.log(`\n✅ ${pkg} installed successfully!`);
673+
installedPackage = pkg;
674+
break;
675+
} catch (error) {
676+
if (isPackageNotFoundError(error, pkg)) {
677+
continue;
678+
}
679+
680+
console.error(`\n❌ Failed to install ${pkg}.`);
681+
const details = getInstallErrorOutput(error);
682+
if (details) {
683+
console.error(details);
684+
}
685+
process.exit(1);
686+
}
687+
}
688+
689+
if (!installedPackage) {
690+
console.error('\n❌ Installation failed.');
630691
console.log('\nTry manually:');
631-
console.log(' npm install @aiox-fullstack/pro');
692+
console.log(' aiox pro setup');
693+
console.log(' # or npx aiox-pro install');
632694
process.exit(1);
633695
}
634696

@@ -644,6 +706,66 @@ async function setupAction(options) {
644706
console.log('');
645707
}
646708

709+
// ---------------------------------------------------------------------------
710+
// aiox pro update (Story 122.3)
711+
// ---------------------------------------------------------------------------
712+
713+
async function updateAction(options) {
714+
const proUpdaterPath = path.resolve(__dirname, '..', '..', '..', 'core', 'pro', 'pro-updater');
715+
let updatePro, formatUpdateResult;
716+
717+
try {
718+
({ updatePro, formatUpdateResult } = require(proUpdaterPath));
719+
} catch {
720+
console.error('❌ Pro updater module not found.');
721+
console.error('Please ensure aiox-core is installed correctly.');
722+
process.exit(1);
723+
}
724+
725+
const projectRoot = process.cwd();
726+
727+
// Validate license before updating (unless --check)
728+
if (!options.check && !options.dryRun) {
729+
try {
730+
const { featureGate } = loadLicenseModules();
731+
const state = featureGate.getLicenseState();
732+
if (state !== 'Active' && state !== 'Grace') {
733+
console.error('\n❌ AIOX Pro license is not active.');
734+
console.error('Activate your license first: aiox pro activate --key PRO-XXXX-XXXX-XXXX-XXXX');
735+
process.exit(1);
736+
}
737+
} catch {
738+
// License modules not available — proceed anyway (first update scenario)
739+
}
740+
}
741+
742+
try {
743+
const result = await updatePro(projectRoot, {
744+
check: options.check || false,
745+
dryRun: options.dryRun || false,
746+
force: options.force || false,
747+
includeCoreUpdate: options.includeCore || false,
748+
skipScaffold: options.skipScaffold || false,
749+
onProgress: (phase, message) => {
750+
if (phase === 'detect') console.log(` 🔍 ${message}`);
751+
else if (phase === 'check') console.log(` 📡 ${message}`);
752+
else if (phase === 'core') console.log(` 📦 ${message}`);
753+
else if (phase === 'update') console.log(` ⬆️ ${message}`);
754+
else if (phase === 'scaffold') console.log(` 🔧 ${message}`);
755+
},
756+
});
757+
758+
console.log(formatUpdateResult(result));
759+
760+
if (!result.success) {
761+
process.exit(1);
762+
}
763+
} catch (error) {
764+
console.error(`\n❌ ${error.message}`);
765+
process.exit(1);
766+
}
767+
}
768+
647769
// ---------------------------------------------------------------------------
648770
// Command builder
649771
// ---------------------------------------------------------------------------
@@ -691,10 +813,21 @@ function createProCommand() {
691813
// aiox pro setup (AC-12: Install-gate)
692814
proCmd
693815
.command('setup')
694-
.description('Install and verify @aiox-fullstack/pro')
816+
.description('Install and verify AIOX Pro')
695817
.option('--verify', 'Only verify installation without installing')
696818
.action(setupAction);
697819

820+
// aiox pro update (Story 122.3)
821+
proCmd
822+
.command('update')
823+
.description('Update AIOX Pro to latest version and sync assets')
824+
.option('--check', 'Check for updates without applying')
825+
.option('--dry-run', 'Show update plan without executing')
826+
.option('-f, --force', 'Force reinstall even if up-to-date')
827+
.option('--include-core', 'Also update aiox-core')
828+
.option('--skip-scaffold', 'Skip re-scaffolding assets after update')
829+
.action(updateAction);
830+
698831
return proCmd;
699832
}
700833

0 commit comments

Comments
 (0)