@stacksjs/buddy 0.70.44 → 0.70.53

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.
@@ -0,0 +1,20 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /**
3
+ * `buddy cd <name>` — resolve the absolute path of a Stacks project by name.
4
+ *
5
+ * A child process can't mutate its parent shell's cwd, so this command
6
+ * prints the resolved path to stdout rather than calling `chdir()`. Users
7
+ * who want true `cd` behavior add a shell wrapper to their rc:
8
+ *
9
+ * bcd() { eval "$(buddy cd "$1" --eval)"; }
10
+ *
11
+ * Then `bcd my-project` actually changes the shell's directory.
12
+ *
13
+ * The underlying scan (`findStacksProjects`) excludes `~/Documents`,
14
+ * `~/Library`, `~/Pictures`, `~/.Trash` by default. Users with projects
15
+ * outside `~/` can point the scan at a specific root via `--root <dir>`
16
+ * or the `STACKS_PROJECTS_ROOT` env var.
17
+ *
18
+ * stacksjs/stacks#527.
19
+ */
20
+ export declare function cd(buddy: CLI): void;
@@ -1,2 +1,105 @@
1
1
  import type { CLI } from '@stacksjs/types';
2
+ /**
3
+ * Resolve (and decrypt) the deploy-target's environment file into a flat
4
+ * key/value map, so its values can be shipped to the server as each site's
5
+ * systemd `.env` content.
6
+ *
7
+ * ts-cloud's `buildSiteDeployScript` treats `site.env` as the COMPLETE
8
+ * content of the deployed `.env` — it doesn't read or merge in anything
9
+ * from the packaged release tarball (ts-cloud is a generic deploy tool; it
10
+ * has no idea `.env.production`/dotenvx encryption exist, that's entirely a
11
+ * Stacks convention). Left unaddressed, every Hetzner site deploys with
12
+ * ONLY whatever's in that site's own `env` override (often nothing at all)
13
+ * — confirmed against a real deploy (stacksjs/status#1 Phase 9): the `main`
14
+ * site (no `env` override) came up logging "loaded 0 variables from .env",
15
+ * and `api` (which only declares `{ HOST, APP_ENV }` to force the loopback
16
+ * bind) came up with just those 2 keys and none of its real production
17
+ * config, failing config validation on the still-`encrypted:...` APP_ENV
18
+ * ciphertext it never had a chance to decrypt (no DOTENV_PRIVATE_KEY_* in
19
+ * that 2-key set).
20
+ *
21
+ * Returns `{}` (not an error) when the file doesn't exist or fails to
22
+ * parse — an app with no `.env.production` yet shouldn't block deploying
23
+ * with whatever `site.env` overrides it does have.
24
+ */
25
+ export declare function resolveDeployEnvValues(environment: 'production' | 'staging' | 'development'): Promise<Record<string, string>>;
26
+ /**
27
+ * Merge the deploy-target's resolved env values underneath each site's own
28
+ * explicit `env` overrides, stripping a general `PORT` when the site
29
+ * declares its own `port` (the generated systemd unit already sets
30
+ * `Environment=PORT=${site.port}` — see buildSiteDeployScript in ts-cloud —
31
+ * so a leftover PORT in the shipped `.env` would otherwise silently win
32
+ * over it once the app's own dotenv loading applies file values on top of
33
+ * the process env).
34
+ */
35
+ export declare function mergeSiteDeployEnv(sites: Record<string, any>, resolvedDeployEnv: Record<string, string>): Record<string, any>;
36
+ /**
37
+ * Make the site model environment-aware. For a non-production environment that
38
+ * declares a `domainPrefix` (staging → `staging`, development → `dev`), every
39
+ * site's public domain becomes `<prefix>.<domain>`, and URL values that point at
40
+ * those hosts (APP_URL, OAuth redirect URLs, redirect targets, …) are rewritten
41
+ * to match — so one config drives prod + staging + dev from their own branches
42
+ * without duplicating site blocks. Only `//<host>` URL occurrences are rewritten;
43
+ * bare `user@host` (e.g. mail identities) is left alone. Production is untouched.
44
+ */
45
+ export declare function applyEnvironmentToSites(sites: Record<string, any>, environment: string, config: any): Record<string, any>;
46
+ /**
47
+ * Loopback-bound server-app sites (e.g. the `api` site: env.HOST=127.0.0.1,
48
+ * reached only through `buddy serve`'s same-origin /api proxy on :3000 —
49
+ * stacksjs/stacks#1950) must NOT have their port opened to the internet.
50
+ * ts-cloud's Hetzner provisioning opens EVERY numeric `site.port` to
51
+ * 0.0.0.0/0 + ::/0 (collectUpstreamPorts → buildHetznerFirewallRules), which
52
+ * would leave only the process bind between the public internet and the full
53
+ * bun-router API. Hand the provision step a copy of the config with those
54
+ * ports stripped — the unmodified config still drives deployAllComputeSites,
55
+ * so the systemd unit (ExecStart, Environment=PORT) is unaffected.
56
+ *
57
+ * Domain-less sites only: a loopback site WITH a domain feeds the rpx
58
+ * gateway's route table (which proxies to 127.0.0.1:port on-box), so its
59
+ * port declaration is left alone.
60
+ */
61
+ export declare function scrubLoopbackSitePortsForFirewall(tsCloudConfig: any): any;
62
+ /**
63
+ * Everything is MERGE-based so a shared mail server keeps every other tenant's
64
+ * domains, keys, users, and forward rules untouched. Best-effort — a hiccup is
65
+ * logged, never fails the release. Returns what the DNS step needs (mail host +
66
+ * DKIM public key), or null when there is nothing to reconcile / it failed.
67
+ */
68
+ export declare function provisionMailTenant(ip: string, logger: typeof log): Promise<MailTenantResult | null>;
69
+ /**
70
+ * Publish the mail DNS for a hosted domain via Porkbun (idempotent delete+create
71
+ * per record): MX → the mail host, SPF authorizing the box IP, the domain's DKIM
72
+ * public key at `mail._domainkey`, and a DMARC policy. Best-effort — logged, not
73
+ * thrown. No-op without Porkbun credentials (the records are printed to add by
74
+ * hand). MX targets the shared mail host (`mail.stacksjs.com`), so no per-domain
75
+ * mail A record or extra TLS SAN is needed.
76
+ */
77
+ export declare function reconcileMailDns(res: MailTenantResult, ip: string, logger: typeof log): Promise<void>;
2
78
  export declare function deploy(buddy: CLI): void;
79
+ /**
80
+ * Use console.log for clean output without timestamps
81
+ * @defaultValue
82
+ * ```ts
83
+ * {
84
+ * info: (...args: any[]) => unknown,
85
+ * success: (...args: any[]) => unknown,
86
+ * warn: (...args: any[]) => unknown,
87
+ * error: (...args: any[]) => unknown,
88
+ * debug: (...args: any[]) => unknown
89
+ * }
90
+ * ```
91
+ */
92
+ declare const log: {
93
+ info: (...args: any[]) => unknown;
94
+ success: (...args: any[]) => unknown;
95
+ warn: (...args: any[]) => unknown;
96
+ error: (...args: any[]) => unknown;
97
+ debug: (...args: any[]) => unknown
98
+ };
99
+ /** What a mail-tenant reconcile resolved + provisioned, for the DNS step. */
100
+ export declare interface MailTenantResult {
101
+ domain: string
102
+ mailHost: string
103
+ dkimPubB64?: string
104
+ created: Array<{ address: string, password: string }>
105
+ }
@@ -1,3 +1,10 @@
1
1
  import type { CLI, DevOptions } from '@stacksjs/types';
2
2
  export declare function dev(buddy: CLI): void;
3
- export declare function startDevelopmentServer(options: DevOptions, startTime?: number): Promise<void>;
3
+ export declare function startDevelopmentServer(_options: DevOptions, _startTime?: number): Promise<void>;
4
+ declare type DevelopmentRpx = typeof import('@stacksjs/rpx');
5
+ type RpxProxySpec = {
6
+ id: string
7
+ from: string
8
+ to: string
9
+ pathRewrites?: Array<{ from: string, to: string, stripPrefix?: boolean }>
10
+ }
@@ -0,0 +1,172 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /**
3
+ * Parse a migration filename like `0000000045-create-posts-table.sql` or
4
+ * `0000000085-alter-posts-author_id.sql` to extract the table name it
5
+ * acts on. Returns `null` for filenames that don't match the
6
+ * recognised `create-<table>-table` / `alter-<table>-` shapes — those
7
+ * pass through the gate unchanged.
8
+ *
9
+ * Index migrations (`create-<table>_<col>_unique-index-in-<table>.sql`)
10
+ * use the trailing `-in-<table>.sql` segment as the source of truth
11
+ * since the leading segment includes the index name. The other
12
+ * forms read the table from the segment immediately after the verb.
13
+ */
14
+ export declare function migrationTable(filename: string): string | null;
15
+ /**
16
+ * Returns the feature that owns the given migration filename, or `null`
17
+ * if the migration isn't claimed by any feature (in which case it
18
+ * always runs). Used by the migration runner's gating pass.
19
+ */
20
+ export declare function migrationFeature(filename: string): FeatureName | null;
21
+ /**
22
+ * Returns the subset of a feature's manifest paths that currently exist
23
+ * on disk under `root` (defaults to `projectPath()`). Used by both the
24
+ * uninstall delete and the doctor orphan check.
25
+ */
26
+ export declare function featurePathsPresent(feature: FeatureName, root?: string): string[];
27
+ /**
28
+ * Recursively delete every file/dir listed in the feature's manifest under
29
+ * `root` (defaults to `projectPath()`). Missing entries are skipped
30
+ * silently — the operation is safe to re-run. Returns the list of paths
31
+ * actually removed so the caller can print a useful summary.
32
+ */
33
+ export declare function deleteFeatureFiles(feature: FeatureName, root?: string): Promise<string[]>;
34
+ /**
35
+ * Copy every path listed in the feature's manifest from the framework
36
+ * defaults tree (`storage/framework/defaults/<path>`) into the project
37
+ * root (`<project>/<path>`). Missing source entries are skipped — not
38
+ * every feature owns every manifest slot (e.g. `cms` doesn't ship a
39
+ * `resources/components/Dashboard/Commerce/` dir, so that entry is
40
+ * absent from its source). Existing target paths are also skipped by
41
+ * default so re-running install on an existing project is idempotent.
42
+ *
43
+ * Returns the list of paths actually copied. See stacksjs/stacks#1854.
44
+ */
45
+ export declare function copyFeatureFiles(feature: FeatureName, options?: CopyFeatureFilesOptions): Promise<{ copied: string[], skipped: string[] }>;
46
+ /**
47
+ * Flip the top-level `enabled` field in `config/<feature>.ts` to the
48
+ * desired value. Returns:
49
+ *
50
+ * - 'created' — file did not exist and we scaffolded it (install only)
51
+ * - 'flipped' — file existed; `enabled` was on the opposite value
52
+ * - 'unchanged' — file existed; `enabled` already matched
53
+ * - 'missing' — file did not exist and `createIfMissing` was false
54
+ * (uninstall path: nothing to do, feature is already off)
55
+ *
56
+ * Pass `options.root` to target a project directory other than the
57
+ * caller's working tree — `./buddy new --minimal` uses this to disable
58
+ * features in the freshly-cloned project path before the user has cd'd
59
+ * into it.
60
+ */
61
+ export declare function setFeatureEnabled(feature: FeatureName, enabled: boolean, options: { createIfMissing: boolean, root?: string }): Promise<SetFeatureEnabledOutcome>;
62
+ /**
63
+ * Disable every feature in `FEATURE_NAMES` at once — flips
64
+ * `config/<feature>.ts` enabled flags to `false` and removes the
65
+ * stamped scaffolding under the project root.
66
+ *
67
+ * Used by `./buddy new --minimal` to turn the kitchen-sink template
68
+ * `@stacksjs/gitit` clones into a bare-bones starter. Individual
69
+ * `<feature>:install` commands re-enable + re-stamp on demand.
70
+ *
71
+ * Pass `root` to target a project directory other than `projectPath()`;
72
+ * the create command uses this because `./buddy new` runs from the
73
+ * user's cwd while the freshly-cloned project lives at `<cwd>/<name>`.
74
+ *
75
+ * Safe to re-run — both halves of each per-feature step are idempotent.
76
+ */
77
+ export declare function uninstallAllFeatures(options?: { root?: string }): Promise<UninstallAllFeaturesResult[]>;
78
+ export declare function features(buddy: CLI): void;
79
+ /**
80
+ * Feature install / uninstall commands.
81
+ *
82
+ * Each framework feature bundle (dashboard, commerce, cms, marketing,
83
+ * monitoring, realtime, queue) lives in its own `config/<feature>.ts`
84
+ * file. Running `./buddy <feature>:install` flips that file's top-level
85
+ * `enabled` to `true` (scaffolding the file from a starter template if it's
86
+ * missing). `./buddy <feature>:uninstall` flips the flag back to `false`
87
+ * AND removes the feature's stamped action/model/view files from the
88
+ * project (pass `--keep-files` to preserve them); the config file itself
89
+ * is preserved so any custom driver/credential settings survive a future
90
+ * reinstall.
91
+ *
92
+ * The framework loaders (`orm/index.ts` eager-load, `defaults/bootstrap.ts`
93
+ * route registration, action prefetch) consult `feature(name)` at boot and
94
+ * skip anything whose flag is off — so an app with only `auth` activated
95
+ * never pays the cost of importing 70+ Commerce models or registering
96
+ * hundreds of dashboard routes it doesn't use.
97
+ *
98
+ * Auth is intentionally not in this list: it has its own scaffolding
99
+ * pipeline (`buddy auth:setup`) which handles migrations + personal-access
100
+ * client setup beyond a simple `enabled` flip.
101
+ *
102
+ * Mirrors Laravel's `php artisan passport:install` / `horizon:install`
103
+ * pattern: features are inert dead code on disk until installed.
104
+ */
105
+ export declare const FEATURE_NAMES: readonly ['dashboard', 'commerce', 'cms', 'marketing', 'monitoring', 'realtime', 'queue'];
106
+ /**
107
+ * Per-feature stamped file/directory manifest. Paths are relative to the
108
+ * project root and mirror the layout that `./buddy new` lays down. Entries
109
+ * ending in `/` are directory trees (recursive remove on uninstall); bare
110
+ * paths are single files.
111
+ *
112
+ * Manifests intentionally overlap where features share scaffolding —
113
+ * `dashboard` claims the umbrella `app/Actions/Dashboard/` even though
114
+ * `app/Actions/Dashboard/Content/` is also claimed by `cms`. Both the
115
+ * uninstall delete and the doctor orphan check are idempotent
116
+ * (already-gone paths are skipped silently), so the overlap is safe.
117
+ *
118
+ * Adding a new file to one of these directories does **not** require a
119
+ * manifest update — directory entries are recursive. Only add an entry
120
+ * when a feature introduces a new top-level path the framework didn't
121
+ * already claim.
122
+ */
123
+ export declare const FEATURE_FILES: {
124
+ cms: readonly ['app/Actions/Cms/', 'app/Actions/Dashboard/Content/', 'app/Models/Content/', 'app/Models/Tag.ts', 'app/Models/Comment.ts', 'resources/views/dashboard/content/'];
125
+ commerce: readonly ['app/Actions/Commerce/', 'app/Actions/Dashboard/Commerce/', 'app/Models/commerce/', 'resources/components/Dashboard/Commerce/', 'resources/views/dashboard/commerce/'];
126
+ dashboard: readonly ['app/Actions/Dashboard/', 'resources/components/Dashboard/', 'resources/views/dashboard/', 'routes/dashboard.ts', 'routes/dashboard-api.ts'];
127
+ marketing: readonly ['app/Actions/Dashboard/Marketing/', 'app/Models/Campaign.ts', 'app/Models/CampaignSend.ts', 'app/Models/EmailList.ts', 'app/Models/EmailListSubscriber.ts', 'app/Models/SocialPost.ts', 'resources/components/Marketing/', 'resources/views/dashboard/marketing/'];
128
+ monitoring: readonly ['app/Actions/Monitoring/', 'app/Actions/TestErrorAction.ts', 'app/Models/Error.ts', 'functions/monitoring/', 'resources/views/dashboard/monitoring/', 'resources/views/dashboard/errors/'];
129
+ realtime: readonly ['app/Actions/Realtime/', 'app/Actions/Dashboard/Realtime/', 'app/Models/realtime/', 'app/Broadcasts/', 'functions/realtime/', 'resources/views/dashboard/realtime/'];
130
+ queue: readonly ['app/Actions/Queue/', 'app/Actions/Dashboard/Jobs/', 'app/Jobs/', 'app/Models/Job.ts', 'app/Models/FailedJob.ts', 'functions/jobs.ts', 'resources/views/dashboard/queue/', 'resources/views/dashboard/jobs/']
131
+ };
132
+ /**
133
+ * Per-feature database table ownership (stacksjs/stacks#1854).
134
+ *
135
+ * Stacks generates SQL migrations from model files, so each feature's
136
+ * tables map 1:1 with the models in its `FEATURE_FILES.app/Models/...`
137
+ * entries. Listed here explicitly rather than derived at runtime so
138
+ * additions are visible in a single grep-able place and the migration
139
+ * gate doesn't depend on filesystem scanning at boot.
140
+ *
141
+ * The migration runner consults this when `config.<feature>.enabled =
142
+ * false`: matching `*-create-<table>-table.sql` (and `*-alter-<table>-*.sql`)
143
+ * files get hidden for the duration of the run, so a project that
144
+ * never installed CMS doesn't materialize `posts`, `pages`,
145
+ * `comments`, etc. on `./buddy migrate`.
146
+ *
147
+ * Tables on this list are scoped to a single feature. Tables shared
148
+ * across features (none today, but `categories` could end up here)
149
+ * should stay out of the manifest until that's resolved — the runner
150
+ * defaults to "run unless owned by a disabled feature".
151
+ */
152
+ export declare const FEATURE_TABLES: {
153
+ cms: readonly ['posts', 'pages', 'comments', 'tags', 'authors', 'categories'];
154
+ commerce: readonly ['products', 'product_variants', 'product_units', 'manufacturers', 'orders', 'order_items', 'carts', 'cart_items', 'payments', 'payment_methods', 'payment_products', 'payment_transactions', 'customers', 'subscribers', 'subscriber_emails', 'subscriptions', 'gift_cards', 'coupons', 'transactions', 'reviews', 'drivers', 'delivery_routes', 'digital_deliveries', 'shipping_methods', 'shipping_rates', 'shipping_zones', 'license_keys', 'loyalty_points', 'loyalty_rewards', 'print_devices', 'receipts', 'tax_rates', 'waitlist_products', 'waitlist_restaurants'];
155
+ dashboard: readonly ['boards', 'board_columns', 'cards', 'card_labels', 'card_assignees', 'card_comments', 'labels', 'ci_run_states', 'ci_runner_samples', 'ci_runner_alert_states', 'requests', 'logs'];
156
+ marketing: never[];
157
+ monitoring: readonly ['errors'];
158
+ realtime: readonly ['websockets'];
159
+ queue: readonly ['jobs', 'failed_jobs']
160
+ };
161
+ export declare interface CopyFeatureFilesOptions {
162
+ force?: boolean
163
+ source?: string
164
+ target?: string
165
+ }
166
+ export declare interface UninstallAllFeaturesResult {
167
+ feature: FeatureName
168
+ configOutcome: SetFeatureEnabledOutcome
169
+ filesRemoved: string[]
170
+ }
171
+ export type FeatureName = (typeof FEATURE_NAMES)[number];
172
+ export type SetFeatureEnabledOutcome = 'created' | 'flipped' | 'unchanged' | 'missing';
@@ -1,6 +1,7 @@
1
1
  export * from './about';
2
2
  export * from './auth';
3
3
  export * from './build';
4
+ export * from './cd';
4
5
  export * from './changelog';
5
6
  export * from './clean';
6
7
  export * from './cloud';
@@ -16,6 +17,7 @@ export * from './doctor';
16
17
  export * from './domains';
17
18
  export * from './email';
18
19
  export * from './env';
20
+ export * from './features';
19
21
  export * from './fresh';
20
22
  export * from './generate';
21
23
  export * from './http';
@@ -26,6 +28,7 @@ export * from './list';
26
28
  export * from './mail';
27
29
  export * from './make';
28
30
  export * from './migrate';
31
+ export * from './migrate-project';
29
32
  export * from './outdated';
30
33
  export * from './phone';
31
34
  export * from './ports';
@@ -39,6 +42,7 @@ export * from './saas';
39
42
  export * from './schedule';
40
43
  export * from './search';
41
44
  export * from './seed';
45
+ export * from './serve';
42
46
  export * from './setup';
43
47
  export * from './sms';
44
48
  export * from './telemetry';
@@ -0,0 +1,2 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ export declare function migrateProject(buddy: CLI): void;
@@ -0,0 +1,25 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ /**` requests (and any non-GET/HEAD verb) are
3
+ * reverse-proxied to the API process — mirroring the dev views server — so
4
+ * scaffolded `fetch('/api/...')` calls behave identically in production
5
+ * (stacksjs/stacks#1950). The API runs as a separate process
6
+ * (core/actions/src/serve/api.ts), deployed as a second systemd service via
7
+ * the `api` site in config/cloud.ts. Override `API_URL` when the API lives
8
+ * on another host, or `PORT_API` when only the port differs.
9
+ *
10
+ * This is the entry the Hetzner deploy runs as a systemd service
11
+ * (`bun storage/framework/core/buddy/src/cli.ts serve`).
12
+ */
13
+ export declare function serve(buddy: CLI): void;
14
+ /**
15
+ * `buddy serve:api` — boot the production API server (bun-router routes).
16
+ *
17
+ * The twin of `buddy serve`: where that serves the STX frontend, this runs the
18
+ * loopback API the frontend proxies `/api` + non-GET requests to. The entry
19
+ * (`@stacksjs/actions/serve/api`) is resolved through the module graph, so it
20
+ * works whether the framework is vendored at `storage/framework/core` OR only
21
+ * installed under `node_modules/@stacksjs/actions`. This keeps deployments from
22
+ * having to hardcode a `storage/framework/core/...` path in their `start`
23
+ * command — `./buddy serve:api` resolves the framework wherever it lives.
24
+ */
25
+ export declare function serveApi(buddy: CLI): void;
@@ -0,0 +1,12 @@
1
+ import type { Driver, MigrationReport, MigrateProjectRequest } from './types';
2
+ export type { MigrateProjectRequest, MigrationReport, ReportEntry } from './types';
3
+ export declare function runMigrator(req: MigrateProjectRequest): Promise<MigrationReport>;
4
+ /**
5
+ * Render a migration report as a Markdown checklist suitable for
6
+ * writing to `MIGRATION_REPORT.md` in the target project.
7
+ */
8
+ export declare function renderReport(report: MigrationReport): string;
9
+ export declare const DRIVERS: {
10
+ laravel: unknown;
11
+ rails: unknown
12
+ };
@@ -0,0 +1,2 @@
1
+ import type { Driver } from '../types';
2
+ export declare const laravelDriver: Driver;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Parse a Laravel migration file. Returns `null` when no
3
+ * `Schema::create(...)` block is found (e.g. drop migrations,
4
+ * data-only migrations, alter-table migrations — those are deferred
5
+ * to a separate emitter once the create paths land).
6
+ */
7
+ export declare function parseLaravelMigration(source: string): ParsedMigration | null;
8
+ /**
9
+ * Lift Laravel's `YYYY_MM_DD_HHMMSS_create_<table>_table.php` filename
10
+ * into the `0000000NNN-create-<table>-table.sql` form the Stacks
11
+ * migration runner expects. The numeric prefix preserves ordering
12
+ * across the existing Stacks migrations directory — callers should
13
+ * pass a `sequence` that starts above whatever's already there.
14
+ */
15
+ export declare function laravelFilenameToStacks(filename: string, sequence: number, table: string): string;
16
+ /**
17
+ * Laravel migration → Stacks SQL translator.
18
+ *
19
+ * Laravel migrations are PHP classes whose `up()` calls
20
+ * `Schema::create(table, fn (Blueprint $t) => { ... })`. We don't
21
+ * parse arbitrary PHP — we recognise the common Blueprint DSL by
22
+ * regex and emit a CREATE TABLE statement that the Stacks runner can
23
+ * consume.
24
+ *
25
+ * Strategy:
26
+ * 1. Find the `Schema::create('table', function (...) { BODY })` block.
27
+ * 2. Tokenise BODY line-by-line, each line a `$table->method(args)->modifier()...;` chain.
28
+ * 3. Map the leading method to a SQLite column type.
29
+ * 4. Translate modifiers (`->nullable()`, `->unique()`, `->default(...)`, `->index()`)
30
+ * into column constraints (or post-table CREATE INDEX statements).
31
+ *
32
+ * Anything unrecognised gets logged in the report as `// SKIPPED:` and
33
+ * the user can hand-port it.
34
+ */
35
+ export declare interface ParsedMigration {
36
+ table: string
37
+ sql: string
38
+ skipped: string[]
39
+ }
40
+ declare interface Column {
41
+ name: string
42
+ type: string
43
+ nullable: boolean
44
+ unique: boolean
45
+ primaryKey: boolean
46
+ autoIncrement: boolean
47
+ defaultValue: string | null
48
+ index: boolean
49
+ }
50
+ declare type ParsedLine = | { kind: 'columns', columns: Column[], indexes?: { name: string, columns: string[], unique: boolean }[] }
51
+ | { kind: 'index', index: { name: string, columns: string[], unique: boolean } }
52
+ | { kind: 'skip' }
@@ -0,0 +1,34 @@
1
+ export declare function parseLaravelModel(source: string): ParsedModel | null;
2
+ /**
3
+ * Laravel Eloquent → Stacks `defineModel({})` translator.
4
+ *
5
+ * Eloquent models are PHP classes that lean on convention plus a few
6
+ * `protected $foo = ...` properties to describe schema and behaviour.
7
+ * We pull the bits we need by regex:
8
+ *
9
+ * - class name → model `name`
10
+ * - protected $table → `table`
11
+ * - protected $fillable → which attributes are `fillable: true`
12
+ * - protected $casts → which attributes get a typed
13
+ * validation rule
14
+ * - hasMany/hasOne/belongsTo → relationship hints in the report
15
+ *
16
+ * Anything else (scopes, accessors, mutators, observers) gets dropped
17
+ * with a note in the migration report. The user can re-add them by
18
+ * hand from the Stacks docs.
19
+ */
20
+ export declare interface ParsedModel {
21
+ className: string
22
+ table: string
23
+ fillable: string[]
24
+ hidden: string[]
25
+ casts: Record<string, string>
26
+ relationships: ParsedRelationship[]
27
+ tsSource: string
28
+ notes: string[]
29
+ }
30
+ export declare interface ParsedRelationship {
31
+ name: string
32
+ kind: 'belongsTo' | 'hasMany' | 'hasOne' | 'belongsToMany'
33
+ target: string
34
+ }
@@ -0,0 +1,2 @@
1
+ import type { Driver } from '../types';
2
+ export declare const railsDriver: Driver;
@@ -0,0 +1,44 @@
1
+ export declare interface MigrateProjectRequest {
2
+ source: string
3
+ target: string
4
+ from: SourceFramework
5
+ dryRun?: boolean
6
+ }
7
+ /**
8
+ * A single output produced (or attempted) by a driver. Drivers append
9
+ * one `ReportEntry` per source file they touch so the report reads
10
+ * like a checklist of what was translated.
11
+ */
12
+ export declare interface ReportEntry {
13
+ source: string
14
+ target: string
15
+ status: 'translated' | 'copied' | 'skipped' | 'failed'
16
+ note?: string
17
+ }
18
+ export declare interface MigrationReport {
19
+ source: string
20
+ target: string
21
+ from: SourceFramework
22
+ startedAt: string
23
+ finishedAt: string
24
+ entries: ReportEntry[]
25
+ }
26
+ export declare interface Driver {
27
+ readonly name: SourceFramework
28
+ migrate: (req: MigrateProjectRequest) => Promise<ReportEntry[]>
29
+ }
30
+ /**
31
+ * Shared types for the `./buddy migrate:project` migrators (Laravel,
32
+ * Rails, …). Each driver consumes a source-project root and emits
33
+ * Stacks-shaped files under a target root, returning a structured
34
+ * report so the CLI can show the user what landed and what got
35
+ * skipped.
36
+ *
37
+ * The migration is intentionally best-effort. The drivers prioritise
38
+ * the high-value 80% (schema → migrations, models, env) and surface
39
+ * unrecognised constructs as `skipped` entries rather than throwing —
40
+ * a partial port the user can clean up beats a full failure mid-run.
41
+ *
42
+ * stacksjs/stacks#1241.
43
+ */
44
+ export type SourceFramework = 'laravel' | 'rails';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
- "version": "0.70.44",
4
+ "version": "0.70.53",
5
5
  "description": "Meet Buddy. The Stacks runtime.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -96,13 +96,14 @@
96
96
  "@stacksjs/collections": "^0.70.23",
97
97
  "@stacksjs/config": "^0.70.23",
98
98
  "@stacksjs/database": "^0.70.23",
99
- "@stacksjs/desktop": "^0.70.23",
99
+ "@stacksjs/desktop": "^0.2.82",
100
100
  "@stacksjs/dns": "^0.70.23",
101
101
  "@stacksjs/email": "^0.70.23",
102
102
  "@stacksjs/enums": "^0.70.23",
103
103
  "@stacksjs/error-handling": "^0.70.23",
104
104
  "@stacksjs/events": "^0.70.23",
105
105
  "@stacksjs/git": "^0.70.23",
106
+ "@stacksjs/gitit": "^0.2.5",
106
107
  "@stacksjs/health": "^0.70.23",
107
108
  "@stacksjs/dnsx": "^0.2.3",
108
109
  "@stacksjs/httx": "^0.1.10",
@@ -115,7 +116,7 @@
115
116
  "@stacksjs/payments": "^0.70.23",
116
117
  "@stacksjs/realtime": "^0.70.23",
117
118
  "@stacksjs/router": "^0.70.23",
118
- "@stacksjs/rpx": "^0.11.2",
119
+ "@stacksjs/rpx": "^0.11.13",
119
120
  "@stacksjs/search-engine": "^0.70.23",
120
121
  "@stacksjs/security": "^0.70.23",
121
122
  "@stacksjs/server": "^0.70.23",
@@ -127,7 +128,7 @@
127
128
  "@stacksjs/ui": "^0.70.23",
128
129
  "@stacksjs/utils": "^0.70.23",
129
130
  "@stacksjs/validation": "^0.70.23",
130
- "@stacksjs/ts-cloud": "^0.2.15"
131
+ "@stacksjs/ts-cloud": "^0.7.12"
131
132
  },
132
133
  "devDependencies": {
133
134
  "better-dx": "^0.2.12"
package/dist/package.json DELETED
@@ -1 +0,0 @@
1
-