Vendor platform-tools: everything this site runs is now in one repo
shared/platform-tools was a git submodule. On this branch it is a plain
directory of tracked files, frozen at ea3616abae30106df283e73a3e2f9e5bb0e6ae90
— the same commit fotografia.andmore.pt pins. Contents are byte-identical to
that tree, verified blob by blob.
The submodule caused every deployment failure this site has had:
- .gitmodules named no `branch`, so the update fell back to
refs/remotes/origin/HEAD — a ref a plain server-side clone never writes.
"Atualizar submódulo" failed with "Unable to find refs/remotes/origin/HEAD
revision in submodule path".
- Its URL was http://, which the origin answers with 523.
- This install is deployed by uploading files, and a submodule is invisible
to that. The tree moved forward while shared/platform-tools stayed on the
old pin, config/functions_secrets.php died on a missing compat_secrets.php,
and the whole site — public pages included — went down.
Each of those was separately fixable. Together they mean a second repository
nobody can see from the deployment path, gating whether the first one boots. A
plain directory deploys like every other file and cannot fall out of step.
The cost is that platform-tools updates no longer arrive by bumping a pin; they
are a copy-over-and-commit. shared/platform-tools/VENDORED.md records the
upstream SHA, the procedure, and which four files differ from upstream in line
endings only (social/*.js, .css, functions_publications.php — CRLF upstream,
normalised to LF here; content otherwise identical).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e034232e9
commit
4d40adbf64
67 files changed
+17478
-5
No files matched your search
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
// ─── Schema export (shared) ──────────────────────────────────────────────────
|
||||
//
|
||||
// Generates a deterministic, structure-only SQL dump of the host database into
|
||||
// <host_repo>/database/schema.sql, suitable for committing to git.
|
||||
//
|
||||
// "Deterministic" is the whole point: no generation timestamp, no server-version
|
||||
// header, AUTO_INCREMENT counters stripped, view DEFINERs removed, and tables
|
||||
// emitted in alphabetical order. Re-running against an unchanged schema produces
|
||||
// a byte-identical file — so a `git diff` on schema.sql is a true changelog of
|
||||
// structural changes, not export noise. (Contrast the hand-made phpMyAdmin dumps,
|
||||
// whose "Generation Time" line and AUTO_INCREMENT counters churn on every export.)
|
||||
//
|
||||
// Built entirely over the existing PDO connection (SHOW FULL TABLES /
|
||||
// SHOW CREATE TABLE / SHOW CREATE VIEW) — no mysqldump binary and no shell
|
||||
// access required. That matters here: the platform hosts have no usable PHP or
|
||||
// mysql CLI, so this runs web-triggered from the platform update page.
|
||||
//
|
||||
// This is schema only. Row data is out of scope by design — that's what the
|
||||
// backup API (functions_backup.php) is for.
|
||||
//
|
||||
// Public API:
|
||||
// schema_dump_path(): string — absolute path to schema.sql
|
||||
// schema_build(PDO): string — full SQL text (does not write)
|
||||
// schema_dump_to_file(PDO): array — {ok, path?, bytes?, tables?, views?, error?}
|
||||
|
||||
/**
|
||||
* Resolve the target schema.sql path. Reuses the migrations dir resolution so
|
||||
* the file lands next to migration_*.sql in the host's database/ folder.
|
||||
*/
|
||||
function schema_dump_path(): string {
|
||||
if (function_exists('migrations_dir')) {
|
||||
$dir = migrations_dir();
|
||||
} else {
|
||||
$dir = $GLOBALS['MIGRATIONS_DIR'] ?? (dirname(__DIR__, 2) . '/database');
|
||||
$dir = rtrim($dir, '/');
|
||||
}
|
||||
return $dir . '/schema.sql';
|
||||
}
|
||||
|
||||
/** Strip the AUTO_INCREMENT counter so a changed row count isn't a schema diff. */
|
||||
function schema_normalize_table_ddl(string $sql): string {
|
||||
return preg_replace('/\s+AUTO_INCREMENT=\d+\b/', '', $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the per-host DEFINER (and its SQL SECURITY clause) from a view DDL.
|
||||
* The definer references a DB user that differs between dev/live and between
|
||||
* tenants, so leaving it in would make the dump non-deterministic and break
|
||||
* replay on a host where that user does not exist.
|
||||
*/
|
||||
function schema_normalize_view_ddl(string $sql): string {
|
||||
$sql = preg_replace('/\sDEFINER=`[^`]*`@`[^`]*`/', '', $sql, 1);
|
||||
$sql = preg_replace('/\sSQL SECURITY DEFINER/', '', $sql, 1);
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/** Quote an identifier for interpolation into a SHOW CREATE statement. */
|
||||
function schema_quote_ident(string $name): string {
|
||||
return '`' . str_replace('`', '``', $name) . '`';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the full schema dump as a string. Throws PDOException on query failure
|
||||
* (the caller turns that into a flash message).
|
||||
*/
|
||||
function schema_build(PDO $pdo): string {
|
||||
$dbName = (string) $pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
|
||||
// Split base tables from views: views must be (re)created after the tables
|
||||
// they reference, and SHOW CREATE differs per object type.
|
||||
$tables = [];
|
||||
$views = [];
|
||||
foreach ($pdo->query('SHOW FULL TABLES')->fetchAll(PDO::FETCH_NUM) as $row) {
|
||||
[$name, $type] = $row;
|
||||
if (strcasecmp((string) $type, 'VIEW') === 0) {
|
||||
$views[] = $name;
|
||||
} else {
|
||||
$tables[] = $name;
|
||||
}
|
||||
}
|
||||
sort($tables, SORT_STRING);
|
||||
sort($views, SORT_STRING);
|
||||
|
||||
$out = [];
|
||||
$out[] = '-- Schema dump for database `' . $dbName . '`';
|
||||
$out[] = '-- Structure only. Generated by platform-tools (functions_schema.php).';
|
||||
$out[] = '-- Deterministic: no timestamp, AUTO_INCREMENT stripped, objects sorted.';
|
||||
$out[] = '-- Do NOT edit by hand — re-run the export from the platform update page.';
|
||||
$out[] = '';
|
||||
$out[] = 'SET FOREIGN_KEY_CHECKS = 0;';
|
||||
$out[] = '';
|
||||
|
||||
foreach ($tables as $t) {
|
||||
$create = $pdo->query('SHOW CREATE TABLE ' . schema_quote_ident($t))
|
||||
->fetch(PDO::FETCH_NUM)[1] ?? '';
|
||||
$create = schema_normalize_table_ddl($create);
|
||||
$out[] = '-- -----------------------------------------------------------';
|
||||
$out[] = '-- Table `' . $t . '`';
|
||||
$out[] = '-- -----------------------------------------------------------';
|
||||
$out[] = 'DROP TABLE IF EXISTS ' . schema_quote_ident($t) . ';';
|
||||
$out[] = $create . ';';
|
||||
$out[] = '';
|
||||
}
|
||||
|
||||
foreach ($views as $v) {
|
||||
$create = $pdo->query('SHOW CREATE VIEW ' . schema_quote_ident($v))
|
||||
->fetch(PDO::FETCH_NUM)[1] ?? '';
|
||||
$create = schema_normalize_view_ddl($create);
|
||||
$out[] = '-- -----------------------------------------------------------';
|
||||
$out[] = '-- View `' . $v . '`';
|
||||
$out[] = '-- -----------------------------------------------------------';
|
||||
$out[] = 'DROP VIEW IF EXISTS ' . schema_quote_ident($v) . ';';
|
||||
$out[] = $create . ';';
|
||||
$out[] = '';
|
||||
}
|
||||
|
||||
$out[] = 'SET FOREIGN_KEY_CHECKS = 1;';
|
||||
$out[] = '';
|
||||
|
||||
return implode("\n", $out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the dump and write it atomically to schema.sql. Never throws — returns
|
||||
* a result array the view can render directly.
|
||||
*/
|
||||
function schema_dump_to_file(PDO $pdo): array {
|
||||
try {
|
||||
$sql = schema_build($pdo);
|
||||
} catch (Throwable $e) {
|
||||
return ['ok' => false, 'error' => 'Erro ao ler o esquema: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
$path = schema_dump_path();
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir) && !@mkdir($dir, 0775, true)) {
|
||||
return ['ok' => false, 'error' => 'Não foi possível criar a pasta ' . $dir . '.'];
|
||||
}
|
||||
|
||||
// Write to a temp file then rename: a crash mid-write can't leave a
|
||||
// truncated schema.sql staged for commit.
|
||||
$tmp = $path . '.tmp';
|
||||
if (@file_put_contents($tmp, $sql, LOCK_EX) === false) {
|
||||
return ['ok' => false, 'error' => 'Falha ao escrever ' . basename($path) . ' (permissões?).'];
|
||||
}
|
||||
if (!@rename($tmp, $path)) {
|
||||
@unlink($tmp);
|
||||
return ['ok' => false, 'error' => 'Falha ao substituir ' . basename($path) . '.'];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'path' => $path,
|
||||
'bytes' => strlen($sql),
|
||||
'tables' => preg_match_all('/^CREATE TABLE /m', $sql),
|
||||
'views' => preg_match_all('/^CREATE(?: ALGORITHM=\S+)? VIEW /m', $sql),
|
||||
];
|
||||
}
|
||||
Reference in new issue
Block a user