Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .github/workflows/upgrade-command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Upgrade command

on:
workflow_dispatch:
push:
branches: [2.x]
paths:
- packages/upgrade/**
- packages/core/database/migrations/**
- .github/workflows/upgrade-command.yml
pull_request:
branches: [2.x]
paths:
- packages/upgrade/**
- packages/core/database/migrations/**
- .github/workflows/upgrade-command.yml

jobs:
lunar-upgrade:
name: v1.5 to v2 (${{ matrix.database }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- database: sqlite
db_connection: sqlite
db_database: /tmp/vanilla-lunar/database/database.sqlite
- database: mysql
db_connection: mysql
db_port: "3306"
db_database: lunar_upgrade
db_username: root
db_password: root
- database: postgres
db_connection: pgsql
db_port: "5432"
db_database: lunar_upgrade
db_username: postgres
db_password: postgres
- database: mariadb
db_connection: mariadb
db_port: "3307"
db_database: lunar_upgrade
db_username: root
db_password: root
env:
APP_DIR: /tmp/vanilla-lunar
LUNAR_V1: "1.5.0"
APP_ENV: local
APP_DEBUG: "true"
SCOUT_DRIVER: collection
QUEUE_CONNECTION: sync
DB_CONNECTION: ${{ matrix.db_connection }}
DB_HOST: 127.0.0.1
DB_PORT: ${{ matrix.db_port }}
DB_DATABASE: ${{ matrix.db_database }}
DB_USERNAME: ${{ matrix.db_username }}
DB_PASSWORD: ${{ matrix.db_password }}
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: lunar_upgrade
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
mariadb:
image: mariadb:11
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: lunar_upgrade
ports:
- 3307:3306
options: >-
--health-cmd="mariadb-admin ping -h 127.0.0.1 -uroot -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: lunar_upgrade
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=10s
--health-timeout=5s
--health-retries=10
steps:
- uses: actions/checkout@v4

- uses: shivammathur/setup-php@v2
with:
php-version: "8.5"
extensions: bcmath, exif, intl, mbstring, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite
tools: composer:v2
coverage: none

- name: Create Laravel 13 app
run: composer create-project laravel/laravel:^13.0 "$APP_DIR" --prefer-dist --no-interaction --no-dev

- name: Configure app
working-directory: /tmp/vanilla-lunar
run: |
php artisan key:generate --force --no-interaction
if [ "${{ matrix.database }}" = "sqlite" ]; then
mkdir -p database
touch database/database.sqlite
fi
{
echo "APP_ENV=local"
echo "APP_DEBUG=true"
echo "SCOUT_DRIVER=collection"
echo "QUEUE_CONNECTION=sync"
echo "DB_CONNECTION=$DB_CONNECTION"
echo "DB_HOST=$DB_HOST"
echo "DB_PORT=$DB_PORT"
echo "DB_DATABASE=$DB_DATABASE"
echo "DB_USERNAME=$DB_USERNAME"
echo "DB_PASSWORD=$DB_PASSWORD"
} >> .env

- name: Install Lunar v1.5 core
working-directory: /tmp/vanilla-lunar
run: composer require "lunarphp/core:${LUNAR_V1}" --no-interaction --no-progress

- name: Migrate v1 schema
working-directory: /tmp/vanilla-lunar
run: php artisan migrate --force --no-interaction

- name: Switch app to this v2 checkout
working-directory: /tmp/vanilla-lunar
run: |
composer config minimum-stability dev
composer config prefer-stable true
composer config repositories.lunar-core "{\"type\":\"path\",\"url\":\"${GITHUB_WORKSPACE}/packages/core\",\"options\":{\"symlink\":true}}"
composer config repositories.lunar-upgrade "{\"type\":\"path\",\"url\":\"${GITHUB_WORKSPACE}/packages/upgrade\",\"options\":{\"symlink\":true}}"
composer require lunarphp/core:@dev lunarphp/upgrade:@dev --no-interaction --no-progress --with-all-dependencies

- name: Run lunar:upgrade
working-directory: /tmp/vanilla-lunar
run: php artisan lunar:upgrade --no-interaction

- name: Apply remaining migrations
working-directory: /tmp/vanilla-lunar
run: php artisan migrate --force --no-interaction
1 change: 1 addition & 0 deletions packages/upgrade/config/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
'2026_01_01_000068_create_country_region_table',
'2026_01_01_000069_add_region_id_to_carts_and_orders',
'2026_01_01_000059_add_orders_cart_id_foreign_key',
'2026_01_01_000071_add_product_types_default_tax_class_foreign_key',
'2026_01_01_900000_create_staff_table',
'2026_01_01_900001_create_activity_log_table',
'2026_01_01_900002_create_permission_tables',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
* 6. Convert `attributes.validation_rules` from the v1 pipe-delimited string
* (`min:1|max:10`) to the v2 json list (`["min:1", "max:10"]`) — split on
* `|` exactly as Laravel's validator parses string rules.
* 7. Add `attribute_groups.system`, drop the morph columns and unused
* `section` / `default_value`, make `attributes.attribute_group_id`
* nullable.
* 7. Add `attribute_groups.system`, drop the morph columns (and the v1
* indexes that still sit on them) and unused `section` / `default_value`,
* make `attributes.attribute_group_id` nullable.
*
* One-way — `down()` is intentionally absent. Restore from backup to reverse.
*/
Expand Down Expand Up @@ -319,6 +319,9 @@ protected function reshapeAttributeGroupsSchema(): void
}

if (Schema::hasColumn($table, 'attributable_type')) {
// SQLite refuses DROP COLUMN while the v1 index remains.
$this->dropIndexIfExists($table, ['attributable_type']);

Schema::table($table, function (Blueprint $table) {
$table->dropColumn('attributable_type');
});
Expand Down Expand Up @@ -393,6 +396,12 @@ protected function reshapeAttributesSchema(): void
));

if ($drops !== []) {
if (in_array('attribute_type', $drops, true)) {
// MariaDB 1072 / SQLite: DROP COLUMN fails while these remain.
$this->dropIndexIfExists($table, ['attribute_type', 'handle'], 'unique');
$this->dropIndexIfExists($table, ['attribute_type']);
}

Schema::table($table, function (Blueprint $blueprint) use ($drops) {
$blueprint->dropColumn($drops);
});
Expand All @@ -406,6 +415,35 @@ protected function reshapeAttributesSchema(): void
}
}

/**
* Drop a v1 index by its live name. `Schema::hasIndex(..., 'index')` misses
* SQLite, which reports the type as `btree` rather than `index`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems a few more sqlite specific issue, not sure worth handling for upgrade command as unlikely anyone using it for prod

*
* @param list<string> $columns
*/
protected function dropIndexIfExists(string $table, array $columns, string $type = 'index'): void
{
$wantUnique = $type === 'unique';

foreach (Schema::getIndexes($table) as $index) {
if ($index['columns'] !== $columns || (bool) $index['unique'] !== $wantUnique) {
continue;
}

Schema::table($table, function (Blueprint $blueprint) use ($index, $wantUnique): void {
if ($wantUnique) {
$blueprint->dropUnique($index['name']);

return;
}

$blueprint->dropIndex($index['name']);
});

return;
}
}

protected function normaliseMorph(?string $value): ?string
{
if ($value === null || $value === '') {
Expand Down
24 changes: 22 additions & 2 deletions packages/upgrade/src/Steps/LedgerRewriteStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Database\Migrations\Migrator;
use Illuminate\Support\Facades\Schema;
use Lunar\Upgrade\Support\StepReport;

/**
Expand All @@ -19,7 +20,8 @@
* the app's own files do not, and deleting their rows would make `migrate`
* re-run them), and a baseline row is only inserted when its file is present
* (marking-run a migration from an uninstalled sub-package would silently
* skip it if that package is installed later).
* skip it if that package is installed later). Staff is extra: the create
* file now lives in core, so we also require the table to exist.
*/
class LedgerRewriteStep implements UpgradeStep
{
Expand Down Expand Up @@ -72,7 +74,8 @@ public function run(StepContext $context): void

$toInsert = array_values(array_filter(
array_diff($config['v2_baseline'], $existing),
fn (string $migration): bool => isset($known[$migration]),
fn (string $migration): bool => isset($known[$migration])
&& $this->baselineSchemaPresent($migration, $context->connection),
));

if ($context->dryRun) {
Expand Down Expand Up @@ -107,6 +110,23 @@ public function run(StepContext $context): void
);
}

/**
* Staff lived in the v1 admin package. v2 folded `create_staff` into core,
* so the migration file is always present even when the table was never
* created (core-only / headless v1). Marking it run would skip the create
* and leave later core staff migrations altering a missing table.
*/
protected function baselineSchemaPresent(string $migration, ?string $connection): bool
{
if ($migration !== '2026_01_01_900000_create_staff_table') {
return true;
}

return Schema::connection($connection)->hasTable(
config('lunar.database.table_prefix').'staff',
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in v1 staff is in panel package, so there is a chance someone using lunarphp for core without panel

}

/**
* Migration names the application can currently load, keyed by name —
* every path registered with the migrator (package `loadMigrationsFrom`
Expand Down
6 changes: 4 additions & 2 deletions tests/upgrade/Feature/ConvertAttributeDataKeysToIdsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function simulateV1AttributeShape(): void

Schema::create(SPEC0019_PREFIX.'attribute_groups', function (Blueprint $table) {
$table->id();
$table->string('attributable_type');
$table->string('attributable_type')->index();
$table->json('name');
$table->string('handle');
$table->integer('position');
Expand All @@ -59,7 +59,7 @@ function simulateV1AttributeShape(): void

Schema::create(SPEC0019_PREFIX.'attributes', function (Blueprint $table) {
$table->id();
$table->string('attribute_type');
$table->string('attribute_type')->index();
$table->foreignId('attribute_group_id');
$table->integer('position');
$table->json('name');
Expand All @@ -74,6 +74,8 @@ function simulateV1AttributeShape(): void
$table->boolean('searchable')->default(true);
$table->boolean('filterable')->default(false);
$table->timestamps();

$table->unique(['attribute_type', 'handle']);
});

Schema::create(SPEC0019_PREFIX.'attributables', function (Blueprint $table) {
Expand Down
56 changes: 56 additions & 0 deletions tests/upgrade/Unit/LedgerRewriteStepTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,62 @@
assertDatabaseHas('migrations', ['migration' => '2026_06_01_000000_rewrite_lunar_class_strings']);
});

it('does not mark create_staff as run when the staff table is missing', function () {
touch($this->migrationPath.'/2026_01_01_900000_create_staff_table.php');

Config::set('lunar.upgrade.ledger', [
'v1_match' => [],
'v2_baseline' => [
'2026_01_01_000000_create_channels_table',
'2026_01_01_900000_create_staff_table',
],
]);

$report = new StepReport;
$context = new StepContext(
dryRun: false,
paths: [],
output: new OutputStyle(new StringInput(''), new BufferedOutput),
report: $report,
);

app(LedgerRewriteStep::class)->run($context);

assertDatabaseHas('migrations', ['migration' => '2026_01_01_000000_create_channels_table']);
assertDatabaseMissing('migrations', ['migration' => '2026_01_01_900000_create_staff_table']);
});

it('marks create_staff as run when the staff table already exists', function () {
$table = config('lunar.database.table_prefix').'staff';

Schema::create($table, function ($blueprint) {
$blueprint->id();
});

touch($this->migrationPath.'/2026_01_01_900000_create_staff_table.php');

Config::set('lunar.upgrade.ledger', [
'v1_match' => [],
'v2_baseline' => ['2026_01_01_900000_create_staff_table'],
]);

$report = new StepReport;
$context = new StepContext(
dryRun: false,
paths: [],
output: new OutputStyle(new StringInput(''), new BufferedOutput),
report: $report,
);

try {
app(LedgerRewriteStep::class)->run($context);

assertDatabaseHas('migrations', ['migration' => '2026_01_01_900000_create_staff_table']);
} finally {
Schema::dropIfExists($table);
}
});

it('does not insert baseline rows for migrations the app cannot load', function () {
Config::set('lunar.upgrade.ledger', [
'v1_match' => [],
Expand Down
Loading