@agentuity/migrate 3.0.0-alpha.0 → 3.0.0-alpha.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.
Files changed (52) hide show
  1. package/bin/migrate.ts +93 -10
  2. package/dist/detect-v3.d.ts +92 -0
  3. package/dist/detect-v3.d.ts.map +1 -0
  4. package/dist/detect-v3.js +675 -0
  5. package/dist/detect-v3.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrate-v3.d.ts +38 -0
  11. package/dist/migrate-v3.d.ts.map +1 -0
  12. package/dist/migrate-v3.js +448 -0
  13. package/dist/migrate-v3.js.map +1 -0
  14. package/dist/report.d.ts +3 -0
  15. package/dist/report.d.ts.map +1 -1
  16. package/dist/report.js +64 -0
  17. package/dist/report.js.map +1 -1
  18. package/dist/transforms/v3/agents.d.ts +33 -0
  19. package/dist/transforms/v3/agents.d.ts.map +1 -0
  20. package/dist/transforms/v3/agents.js +335 -0
  21. package/dist/transforms/v3/agents.js.map +1 -0
  22. package/dist/transforms/v3/dev-setup.d.ts +27 -0
  23. package/dist/transforms/v3/dev-setup.d.ts.map +1 -0
  24. package/dist/transforms/v3/dev-setup.js +103 -0
  25. package/dist/transforms/v3/dev-setup.js.map +1 -0
  26. package/dist/transforms/v3/entry-point.d.ts +23 -0
  27. package/dist/transforms/v3/entry-point.d.ts.map +1 -0
  28. package/dist/transforms/v3/entry-point.js +67 -0
  29. package/dist/transforms/v3/entry-point.js.map +1 -0
  30. package/dist/transforms/v3/package-json.d.ts +28 -0
  31. package/dist/transforms/v3/package-json.d.ts.map +1 -0
  32. package/dist/transforms/v3/package-json.js +151 -0
  33. package/dist/transforms/v3/package-json.js.map +1 -0
  34. package/dist/transforms/v3/routes.d.ts +37 -0
  35. package/dist/transforms/v3/routes.d.ts.map +1 -0
  36. package/dist/transforms/v3/routes.js +146 -0
  37. package/dist/transforms/v3/routes.js.map +1 -0
  38. package/dist/transforms/v3/services.d.ts +19 -0
  39. package/dist/transforms/v3/services.d.ts.map +1 -0
  40. package/dist/transforms/v3/services.js +61 -0
  41. package/dist/transforms/v3/services.js.map +1 -0
  42. package/package.json +4 -4
  43. package/src/detect-v3.ts +867 -0
  44. package/src/index.ts +13 -0
  45. package/src/migrate-v3.ts +539 -0
  46. package/src/report.ts +86 -0
  47. package/src/transforms/v3/agents.ts +434 -0
  48. package/src/transforms/v3/dev-setup.ts +137 -0
  49. package/src/transforms/v3/entry-point.ts +90 -0
  50. package/src/transforms/v3/package-json.ts +183 -0
  51. package/src/transforms/v3/routes.ts +185 -0
  52. package/src/transforms/v3/services.ts +76 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Transform: package.json for v2 → v3 migration.
3
+ *
4
+ * - Removes @agentuity/runtime
5
+ * - Adds hono and @agentuity/hono
6
+ * - Adds individual service packages based on detected usage
7
+ * - Bumps all @agentuity/* packages to ^3.0.0
8
+ * - Updates start script if it references old entry point
9
+ */
10
+
11
+ import { SERVICE_PACKAGE_MAP, type V3OutdatedPackage } from '../../detect-v3';
12
+
13
+ export interface V3PackageJsonResult {
14
+ /** Transformed package.json content, or null if no changes */
15
+ content: string | null;
16
+ /** Description of each change */
17
+ changes: string[];
18
+ }
19
+
20
+ /**
21
+ * Transform package.json for v3 migration.
22
+ */
23
+ export function transformPackageJsonV3(
24
+ source: string,
25
+ outdatedPackages: V3OutdatedPackage[],
26
+ servicesUsed: string[],
27
+ options?: {
28
+ /** Whether the project had @agentuity/runtime */
29
+ removeRuntime?: boolean;
30
+ /** Whether to remove @agentuity/react */
31
+ removeReact?: boolean;
32
+ /** Dev scripts to add (from dev-setup transform) */
33
+ devScripts?: Record<string, string>;
34
+ }
35
+ ): V3PackageJsonResult {
36
+ const changes: string[] = [];
37
+ let pkg: Record<string, unknown>;
38
+
39
+ try {
40
+ pkg = JSON.parse(source);
41
+ } catch {
42
+ return { content: null, changes: ['Failed to parse package.json'] };
43
+ }
44
+
45
+ const deps = (pkg.dependencies ?? {}) as Record<string, string>;
46
+ const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;
47
+
48
+ // ── 1. Remove @agentuity/runtime ──────────────────────────────────────
49
+ if (options?.removeRuntime && deps['@agentuity/runtime']) {
50
+ delete deps['@agentuity/runtime'];
51
+ changes.push('Removed @agentuity/runtime from dependencies');
52
+ }
53
+ if (options?.removeRuntime && devDeps['@agentuity/runtime']) {
54
+ delete devDeps['@agentuity/runtime'];
55
+ changes.push('Removed @agentuity/runtime from devDependencies');
56
+ }
57
+
58
+ // ── 2. Add hono ───────────────────────────────────────────────────────
59
+ if (!deps['hono']) {
60
+ deps['hono'] = '^4.0.0';
61
+ changes.push('Added hono@^4.0.0 to dependencies');
62
+ }
63
+
64
+ // ── 3. Add @agentuity/hono ────────────────────────────────────────────
65
+ if (!deps['@agentuity/hono']) {
66
+ deps['@agentuity/hono'] = '^3.0.0';
67
+ changes.push('Added @agentuity/hono@^3.0.0 to dependencies');
68
+ }
69
+
70
+ // ── 4. Add individual service packages based on usage ─────────────────
71
+ for (const service of servicesUsed) {
72
+ if (service === 'logger') {
73
+ // Logger comes from telemetry
74
+ if (!deps['@agentuity/telemetry']) {
75
+ deps['@agentuity/telemetry'] = '^3.0.0';
76
+ changes.push('Added @agentuity/telemetry@^3.0.0 (for logger)');
77
+ }
78
+ continue;
79
+ }
80
+
81
+ const mapping = SERVICE_PACKAGE_MAP[service];
82
+ if (!mapping) continue;
83
+
84
+ if (!deps[mapping.pkg]) {
85
+ deps[mapping.pkg] = '^3.0.0';
86
+ changes.push(`Added ${mapping.pkg}@^3.0.0 (${service} service detected in use)`);
87
+ }
88
+ }
89
+
90
+ // ── 5. Bump existing @agentuity/* packages to ^3.0.0 ──────────────────
91
+ for (const outdated of outdatedPackages) {
92
+ const section = outdated.section === 'dependencies' ? deps : devDeps;
93
+ if (section[outdated.name]) {
94
+ section[outdated.name] = '^3.0.0';
95
+ changes.push(`Updated ${outdated.name} ${outdated.currentVersion} → ^3.0.0`);
96
+ }
97
+ }
98
+
99
+ // ── 6. Remove @agentuity/react if requested ───────────────────────────
100
+ if (options?.removeReact) {
101
+ if (deps['@agentuity/react']) {
102
+ delete deps['@agentuity/react'];
103
+ changes.push('Removed @agentuity/react from dependencies (deprecated in v3)');
104
+ }
105
+ if (devDeps['@agentuity/react']) {
106
+ delete devDeps['@agentuity/react'];
107
+ changes.push('Removed @agentuity/react from devDependencies (deprecated in v3)');
108
+ }
109
+ }
110
+
111
+ // ── 7. Update scripts for new entry point ─────────────────────────
112
+ const scripts = (pkg.scripts ?? {}) as Record<string, string>;
113
+
114
+ // Replace app.ts references in existing scripts
115
+ if (scripts.start && scripts.start.includes('app.ts')) {
116
+ const oldStart = scripts.start;
117
+ scripts.start = scripts.start.replace('app.ts', 'src/index.ts');
118
+ changes.push(`Updated start script: "${oldStart}" → "${scripts.start}"`);
119
+ }
120
+ if (scripts.dev && scripts.dev.includes('app.ts')) {
121
+ const oldDev = scripts.dev;
122
+ scripts.dev = scripts.dev.replace('app.ts', 'src/index.ts');
123
+ changes.push(`Updated dev script: "${oldDev}" → "${scripts.dev}"`);
124
+ }
125
+
126
+ // Ensure a start script exists that points to the new entry point.
127
+ // The buildpack's generic detector uses this to determine how to run the app.
128
+ if (
129
+ !scripts.start ||
130
+ scripts.start.includes('.agentuity') ||
131
+ scripts.start.includes('agentuity')
132
+ ) {
133
+ const oldStart = scripts.start;
134
+ scripts.start = 'bun src/index.ts';
135
+ if (oldStart) {
136
+ changes.push(`Replaced start script: "${oldStart}" → "${scripts.start}"`);
137
+ } else {
138
+ changes.push(`Added start script: "${scripts.start}"`);
139
+ }
140
+ }
141
+
142
+ // Apply dev scripts from dev-setup transform
143
+ if (options?.devScripts) {
144
+ for (const [name, value] of Object.entries(options.devScripts)) {
145
+ const old = scripts[name];
146
+ scripts[name] = value;
147
+ if (old) {
148
+ changes.push(`Updated ${name} script: "${old}" → "${value}"`);
149
+ } else {
150
+ changes.push(`Added ${name} script: "${value}"`);
151
+ }
152
+ }
153
+ }
154
+
155
+ // Write back
156
+ if (Object.keys(deps).length > 0) pkg.dependencies = deps;
157
+ if (Object.keys(devDeps).length > 0) pkg.devDependencies = devDeps;
158
+ if (Object.keys(scripts).length > 0) pkg.scripts = scripts;
159
+
160
+ if (changes.length === 0) {
161
+ return { content: null, changes: [] };
162
+ }
163
+
164
+ // Preserve the original indentation style
165
+ const indent = detectIndent(source);
166
+ return {
167
+ content: JSON.stringify(pkg, null, indent) + '\n',
168
+ changes,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Detect indentation (tab vs spaces) from package.json source.
174
+ */
175
+ function detectIndent(source: string): string | number {
176
+ const match = source.match(/^(\s+)"/m);
177
+ if (match) {
178
+ const whitespace = match[1] ?? '\t';
179
+ if (whitespace.includes('\t')) return '\t';
180
+ return whitespace.length;
181
+ }
182
+ return '\t'; // default to tabs (project convention)
183
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Transform: rewrite service access in route files.
3
+ *
4
+ * Replaces c.var.kv, c.var.vector, etc. with direct imports from the
5
+ * shared services module.
6
+ *
7
+ * Uses string-level surgery rather than AST round-trip to preserve
8
+ * formatting and comments.
9
+ */
10
+
11
+ import ts from 'typescript';
12
+ import type { ServiceUsage } from '../../detect-v3';
13
+ import { relative, dirname } from 'node:path';
14
+
15
+ export interface RouteServiceTransformResult {
16
+ /** The transformed source, or null if no changes */
17
+ source: string | null;
18
+ /** What was changed */
19
+ changes: string[];
20
+ }
21
+
22
+ /**
23
+ * Rewrite c.var.* service access patterns to direct imports.
24
+ *
25
+ * @param source - File source text
26
+ * @param usage - Detected service usage info
27
+ * @param servicesRelativePath - Relative import path to services module (e.g., '../services')
28
+ */
29
+ /**
30
+ * Remove all imports from @agentuity/runtime.
31
+ * In v3, this package is a deprecation stub — nothing should be imported from it.
32
+ */
33
+ export function removeRuntimeImports(source: string): { source: string; removed: boolean } {
34
+ const pattern =
35
+ /import\s+(?:type\s+)?\{[^}]*\}\s*from\s*['"]@agentuity\/runtime['"]\s*;?\s*\n?/g;
36
+ const replaced = source.replace(pattern, '');
37
+ return { source: replaced, removed: replaced !== source };
38
+ }
39
+
40
+ export function transformRouteServices(
41
+ source: string,
42
+ usage: ServiceUsage,
43
+ servicesRelativePath: string
44
+ ): RouteServiceTransformResult {
45
+ let output = source;
46
+ const changes: string[] = [];
47
+ const servicesAdded = new Set<string>();
48
+
49
+ // Remove @agentuity/runtime imports (Env type, sse, stream, websocket, etc.)
50
+ const runtimeCleanup = removeRuntimeImports(output);
51
+ if (runtimeCleanup.removed) {
52
+ output = runtimeCleanup.source;
53
+ changes.push('Removed @agentuity/runtime imports');
54
+ }
55
+
56
+ if (usage.accessPattern === 'c.var') {
57
+ // Replace c.var.serviceName patterns
58
+ // We need to handle various Hono context variable names: c, ctx, context
59
+ const contextNames = ['c', 'ctx', 'context'];
60
+
61
+ for (const service of usage.services) {
62
+ for (const ctxName of contextNames) {
63
+ // Match: c.var.kv c.var.vector etc.
64
+ const pattern = new RegExp(`\\b${ctxName}\\.var\\.${service}\\b`, 'g');
65
+ if (pattern.test(output)) {
66
+ output = output.replace(pattern, service);
67
+ servicesAdded.add(service);
68
+ changes.push(`Replaced ${ctxName}.var.${service} → ${service}`);
69
+ }
70
+ }
71
+ }
72
+ } else if (usage.accessPattern === 'ctx') {
73
+ // Replace ctx.serviceName patterns (agent context)
74
+ const contextNames = ['ctx', 'context', 'c'];
75
+
76
+ for (const service of usage.services) {
77
+ for (const ctxName of contextNames) {
78
+ const pattern = new RegExp(`\\b${ctxName}\\.${service}\\b`, 'g');
79
+ if (pattern.test(output)) {
80
+ output = output.replace(pattern, service);
81
+ servicesAdded.add(service);
82
+ changes.push(`Replaced ${ctxName}.${service} → ${service}`);
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ // Clean up self-referencing declarations: `const kv = kv;` or `const stream = await stream.create(...)`
89
+ // These happen when the original code had `const kv = c.var.kv;`
90
+ if (servicesAdded.size > 0) {
91
+ for (const service of servicesAdded) {
92
+ // Pattern: const/let/var service = service; (entire line)
93
+ const selfRefPattern = new RegExp(
94
+ `^\\s*(?:const|let|var)\\s+${service}\\s*=\\s*${service}\\s*;?\\s*$`,
95
+ 'gm'
96
+ );
97
+ if (selfRefPattern.test(output)) {
98
+ output = output.replace(selfRefPattern, '');
99
+ changes.push(`Removed self-referencing declaration: const ${service} = ${service}`);
100
+ }
101
+ }
102
+ }
103
+
104
+ // Add import for services if any were rewritten
105
+ if (servicesAdded.size > 0) {
106
+ const importList = [...servicesAdded].sort().join(', ');
107
+ const importLine = `import { ${importList} } from '${servicesRelativePath}';`;
108
+
109
+ // Check if there's already an import from the services module
110
+ const existingServicesImport = new RegExp(
111
+ `import\\s*\\{[^}]*\\}\\s*from\\s*['"]${escapeRegex(servicesRelativePath)}['"]`
112
+ );
113
+
114
+ if (existingServicesImport.test(output)) {
115
+ // Merge with existing import
116
+ output = output.replace(existingServicesImport, (match) => {
117
+ // Extract existing imports
118
+ const existingMatch = match.match(/\{([^}]*)\}/);
119
+ if (!existingMatch) return match;
120
+ const existing = existingMatch[1]!
121
+ .split(',')
122
+ .map((s) => s.trim())
123
+ .filter(Boolean);
124
+ const merged = [...new Set([...existing, ...servicesAdded])].sort();
125
+ return `import { ${merged.join(', ')} } from '${servicesRelativePath}'`;
126
+ });
127
+ } else {
128
+ // Insert new import after existing imports
129
+ output = insertAfterImports(output, importLine);
130
+ }
131
+
132
+ changes.push(`Added import for: ${importList}`);
133
+ }
134
+
135
+ if (changes.length === 0) {
136
+ return { source: null, changes: [] };
137
+ }
138
+
139
+ return { source: output, changes };
140
+ }
141
+
142
+ /**
143
+ * Compute the relative import path from a source file to src/services.ts.
144
+ */
145
+ export function computeServicesRelativePath(projectDir: string, sourceFilePath: string): string {
146
+ const servicesPath = 'src/services';
147
+ const sourceDir = dirname(relative(projectDir, sourceFilePath));
148
+
149
+ let rel = relative(sourceDir, servicesPath);
150
+ // Ensure it starts with ./ or ../
151
+ if (!rel.startsWith('.')) {
152
+ rel = './' + rel;
153
+ }
154
+ // Remove .ts extension if present
155
+ rel = rel.replace(/\.ts$/, '');
156
+
157
+ return rel;
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Helpers
162
+ // ---------------------------------------------------------------------------
163
+
164
+ function insertAfterImports(source: string, importLine: string): string {
165
+ const sf = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.ESNext, true);
166
+
167
+ let lastImportEnd = -1;
168
+ for (const stmt of sf.statements) {
169
+ if (ts.isImportDeclaration(stmt)) {
170
+ lastImportEnd = stmt.getEnd();
171
+ }
172
+ }
173
+
174
+ if (lastImportEnd >= 0) {
175
+ return (
176
+ source.substring(0, lastImportEnd) + '\n' + importLine + source.substring(lastImportEnd)
177
+ );
178
+ }
179
+
180
+ return importLine + '\n' + source;
181
+ }
182
+
183
+ function escapeRegex(str: string): string {
184
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
185
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Transform: generate src/services.ts
3
+ *
4
+ * Creates a shared services module that exports singleton client instances
5
+ * for all Agentuity services detected in use across the project.
6
+ *
7
+ * Only includes services that are actually used — no unnecessary dependencies.
8
+ */
9
+
10
+ import { SERVICE_PACKAGE_MAP } from '../../detect-v3';
11
+
12
+ export interface ServicesFileResult {
13
+ /** Generated source for src/services.ts, or null if no services used */
14
+ source: string | null;
15
+ /** What was included */
16
+ changes: string[];
17
+ }
18
+
19
+ /**
20
+ * Generate src/services.ts based on detected service usage.
21
+ */
22
+ export function generateServicesFile(servicesUsed: string[]): ServicesFileResult {
23
+ if (servicesUsed.length === 0) {
24
+ return { source: null, changes: [] };
25
+ }
26
+
27
+ const changes: string[] = [];
28
+ const imports: string[] = [];
29
+ const exports: string[] = [];
30
+
31
+ // Header comment
32
+ const header = [
33
+ '/**',
34
+ ' * Shared Agentuity service clients.',
35
+ ' *',
36
+ ' * Auto-generated by @agentuity/migrate (v2 → v3 migration).',
37
+ ' * Each client auto-configures from AGENTUITY_* environment variables.',
38
+ ' *',
39
+ ' * Usage:',
40
+ " * import { kv, vector } from './services';",
41
+ " * const data = await kv.get('namespace', 'key');",
42
+ ' */',
43
+ '',
44
+ ];
45
+
46
+ // Logger is special — comes from @agentuity/telemetry
47
+ const hasLogger = servicesUsed.includes('logger');
48
+ const serviceClients = servicesUsed.filter((s) => s !== 'logger');
49
+
50
+ // Generate import + export for each service client
51
+ for (const service of serviceClients.sort()) {
52
+ const mapping = SERVICE_PACKAGE_MAP[service];
53
+ if (!mapping) continue;
54
+
55
+ imports.push(`import { ${mapping.client} } from '${mapping.pkg}';`);
56
+ exports.push(`export const ${service} = new ${mapping.client}();`);
57
+ changes.push(`Added ${service} client from ${mapping.pkg}`);
58
+ }
59
+
60
+ // Logger from telemetry
61
+ if (hasLogger) {
62
+ imports.push("import { register } from '@agentuity/telemetry';");
63
+ exports.push('');
64
+ exports.push('/** Shared logger instance from telemetry */');
65
+ exports.push('const telemetry = register();');
66
+ exports.push('export const logger = telemetry.logger;');
67
+ changes.push('Added logger from @agentuity/telemetry');
68
+ }
69
+
70
+ const lines = [...header, ...imports, '', ...exports, ''];
71
+
72
+ return {
73
+ source: lines.join('\n'),
74
+ changes,
75
+ };
76
+ }