Prepare the project for production deployment and final client
acceptance testing. Adds preflight fail-fast command, hardened FX rate
importer, currency seeder, deploy scripts, E2E smoke test, and the
43-item UAT checklist document with sign-off form.
Key changes:
- Phase 3.9.1 — hotfix (AddressBookEntry):
- Add $attributes defaults (use_count=0, is_default=false) so
instance values are populated right after create() without
refresh(). Resolves the red test
`it_increments_use_count_and_sets_last_used_at`.
- Phase 3.10 — deploy infrastructure (Laravel):
- Add `ifnex:preflight` command: fail-fast audit of production
environment (APP_ENV, APP_DEBUG, APP_KEY, IFNEX_API_KEY,
IFNEX_BRIDGE_API_KEY, ZARINPAL_SANDBOX, DB_CONNECTION, CORS,
currencies seeded, PHP extensions, storage writable). Exits 1
if any check fails; supports --skip-db for pre-migrate runs.
- Add `ifnex:import-fx-rates` command: replacement for
`ifnex:update-rates`. Fetches USD/AED from Exir.io + EUR/CNY
from FreeCurrencyAPI. Supports --auto-approve (cron use) and
--threshold (max change % for auto-approve). Logs critical on
HTTP failure. URL configurable via EXIR_API_URL env.
- Add `CurrencySeeder`: creates 5 base currencies (USD/AED/EUR/
CNY/IRR) with rate=0 (IRR=1). Idempotent — does not overwrite
real rates already set by admin or cron.
- Update `DatabaseSeeder`: skip super_admin with password
'password' in production (still created in local/testing).
- Fix `bootstrap/app.php`: remove dead `use ApiKeyAuth` import
(actual class is ApiKeyMiddleware). Latent fatal if api_key
alias was used.
- Add `config/ifnex.php` exir block + preflight config.
- Harden `.env.example`: MySQL default, EXIR_API_URL,
SANCTUM_STATEFUL_DOMAINS, APP_TIMEZONE, LOG_DAILY_DAYS, fix
duplicate CURRENCY_API_KEY.
- Phase 3.10 — deploy scripts (Linux):
- Add `scripts/deploy.sh`: idempotent 13-step deploy (composer
install → key:generate → preflight → migrate → seed → FX import
→ cache → chown). Fail-fast with set -e.
- Add `scripts/cron-exir-rates.sh`: daily cron wrapper for
`ifnex:import-fx-rates --source=all --auto-approve`.
- Phase 3.10.1 — preflight hotfix:
- Make expected DB connection configurable
(ifnex.preflight.expected_db_connection) instead of hardcoded
'mysql'. Prevents cascade test failure when tests would have to
mutate database.default to mysql in a sqlite test environment
(Unknown database ':memory:' → broken connection pool → 22
previously-green tests fail with "There is already an active
transaction").
- Rewrite IfnexPreflightTest to use config() rather than mutating
database.default.
- Phase 3.11 — UAT:
- Add `ifnex:smoke-test` command: E2E post-deploy automated test
(25+ checks: DB connection, core tables, currencies with
non-zero rates, countries count, roles, admin user, system
settings, storage writable, config/route cache, 4 API
endpoints: /up, /countries, /track/{invalid}, /customer/profile
without token).
- Add 9 feature tests for smoke-test (PASS + 6 FAIL + custom URL
+ 422/500 handling + auth bypass detection).
- Add `UAT-3.11.md`: 43-item Persian checklist for client
acceptance testing across 15 feature areas. Includes sign-off
form for both parties (client + VernaSoft).
- Update `CLIENT_DELIVERY.md` to record phases 3.10 and 3.11.
Tests:
- Total: 51 → 82 tests, 150 → 220+ assertions, all green.
- New: IfnexPreflightTest (9), IfnexImportFxRatesTest (9),
CurrencySeederTest (4), IfnexSmokeTestTest (9) = 31 new tests.
Documentation:
- DEPLOYMENT_HANDOFF.md §2.4: add CurrencySeeder + import-fx-rates.
- DEPLOYMENT_HANDOFF.md §2.8: update cron to use cron-exir-rates.sh.
- UAT-3.11.md: new 43-item client acceptance checklist + sign-off.
- CLIENT_DELIVERY.md: record phases 3.10 and 3.11 completion.
232 lines
6.7 KiB
JavaScript
232 lines
6.7 KiB
JavaScript
(function() {
|
|
"use strict";
|
|
var wp;
|
|
(wp ||= {}).blockSerializationDefaultParser = (() => {
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// packages/block-serialization-default-parser/build-module/index.mjs
|
|
var index_exports = {};
|
|
__export(index_exports, {
|
|
parse: () => parse
|
|
});
|
|
var document;
|
|
var offset;
|
|
var output;
|
|
var stack;
|
|
var tokenizer = /<!--[ \t\f\r\n]+(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)[ \t\f\r\n]+({(?:(?=([^}]+|}+(?=})|(?!}[ \t\f\r\n]+\/?-->)[^])*)\5|[^]*?)}[ \t\f\r\n]+)?(\/)?-->/g;
|
|
function Block(blockName, attrs, innerBlocks, innerHTML, innerContent) {
|
|
return {
|
|
blockName,
|
|
attrs,
|
|
innerBlocks,
|
|
innerHTML,
|
|
innerContent
|
|
};
|
|
}
|
|
function Freeform(innerHTML) {
|
|
return Block(null, {}, [], innerHTML, [innerHTML]);
|
|
}
|
|
function Frame(block, tokenStart, tokenLength, prevOffset, leadingHtmlStart) {
|
|
return {
|
|
block,
|
|
tokenStart,
|
|
tokenLength,
|
|
prevOffset: prevOffset || tokenStart + tokenLength,
|
|
leadingHtmlStart
|
|
};
|
|
}
|
|
var parse = (doc) => {
|
|
document = doc;
|
|
offset = 0;
|
|
output = [];
|
|
stack = [];
|
|
tokenizer.lastIndex = 0;
|
|
do {
|
|
} while (proceed());
|
|
return output;
|
|
};
|
|
function proceed() {
|
|
const stackDepth = stack.length;
|
|
const next = nextToken();
|
|
const [tokenType, blockName, attrs, startOffset, tokenLength] = next;
|
|
const leadingHtmlStart = startOffset > offset ? offset : null;
|
|
switch (tokenType) {
|
|
case "no-more-tokens":
|
|
if (0 === stackDepth) {
|
|
addFreeform();
|
|
return false;
|
|
}
|
|
if (1 === stackDepth) {
|
|
addBlockFromStack();
|
|
return false;
|
|
}
|
|
while (0 < stack.length) {
|
|
addBlockFromStack();
|
|
}
|
|
return false;
|
|
case "void-block":
|
|
if (0 === stackDepth) {
|
|
if (null !== leadingHtmlStart) {
|
|
output.push(
|
|
Freeform(
|
|
document.substr(
|
|
leadingHtmlStart,
|
|
startOffset - leadingHtmlStart
|
|
)
|
|
)
|
|
);
|
|
}
|
|
output.push(Block(blockName, attrs, [], "", []));
|
|
offset = startOffset + tokenLength;
|
|
return true;
|
|
}
|
|
addInnerBlock(
|
|
Block(blockName, attrs, [], "", []),
|
|
startOffset,
|
|
tokenLength
|
|
);
|
|
offset = startOffset + tokenLength;
|
|
return true;
|
|
case "block-opener":
|
|
stack.push(
|
|
Frame(
|
|
Block(blockName, attrs, [], "", []),
|
|
startOffset,
|
|
tokenLength,
|
|
startOffset + tokenLength,
|
|
leadingHtmlStart
|
|
)
|
|
);
|
|
offset = startOffset + tokenLength;
|
|
return true;
|
|
case "block-closer":
|
|
if (0 === stackDepth) {
|
|
addFreeform();
|
|
return false;
|
|
}
|
|
if (1 === stackDepth) {
|
|
addBlockFromStack(startOffset);
|
|
offset = startOffset + tokenLength;
|
|
return true;
|
|
}
|
|
const stackTop = stack.pop();
|
|
const html = document.substr(
|
|
stackTop.prevOffset,
|
|
startOffset - stackTop.prevOffset
|
|
);
|
|
stackTop.block.innerHTML += html;
|
|
stackTop.block.innerContent.push(html);
|
|
stackTop.prevOffset = startOffset + tokenLength;
|
|
addInnerBlock(
|
|
stackTop.block,
|
|
stackTop.tokenStart,
|
|
stackTop.tokenLength,
|
|
startOffset + tokenLength
|
|
);
|
|
offset = startOffset + tokenLength;
|
|
return true;
|
|
default:
|
|
addFreeform();
|
|
return false;
|
|
}
|
|
}
|
|
function parseJSON(input) {
|
|
try {
|
|
return JSON.parse(input);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function nextToken() {
|
|
const matches = tokenizer.exec(document);
|
|
if (null === matches) {
|
|
return ["no-more-tokens", "", null, 0, 0];
|
|
}
|
|
const startedAt = matches.index;
|
|
const [
|
|
match,
|
|
closerMatch,
|
|
namespaceMatch,
|
|
nameMatch,
|
|
attrsMatch,
|
|
,
|
|
voidMatch
|
|
] = matches;
|
|
const length = match.length;
|
|
const isCloser = !!closerMatch;
|
|
const isVoid = !!voidMatch;
|
|
const namespace = namespaceMatch || "core/";
|
|
const name = namespace + nameMatch;
|
|
const hasAttrs = !!attrsMatch;
|
|
const attrs = hasAttrs ? parseJSON(attrsMatch) : {};
|
|
if (isCloser && (isVoid || hasAttrs)) {
|
|
}
|
|
if (isVoid) {
|
|
return ["void-block", name, attrs, startedAt, length];
|
|
}
|
|
if (isCloser) {
|
|
return ["block-closer", name, null, startedAt, length];
|
|
}
|
|
return ["block-opener", name, attrs, startedAt, length];
|
|
}
|
|
function addFreeform(rawLength) {
|
|
const length = rawLength ? rawLength : document.length - offset;
|
|
if (0 === length) {
|
|
return;
|
|
}
|
|
output.push(Freeform(document.substr(offset, length)));
|
|
}
|
|
function addInnerBlock(block, tokenStart, tokenLength, lastOffset) {
|
|
const parent = stack[stack.length - 1];
|
|
parent.block.innerBlocks.push(block);
|
|
const html = document.substr(
|
|
parent.prevOffset,
|
|
tokenStart - parent.prevOffset
|
|
);
|
|
if (html) {
|
|
parent.block.innerHTML += html;
|
|
parent.block.innerContent.push(html);
|
|
}
|
|
parent.block.innerContent.push(null);
|
|
parent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength;
|
|
}
|
|
function addBlockFromStack(endOffset) {
|
|
const { block, leadingHtmlStart, prevOffset, tokenStart } = stack.pop();
|
|
const html = endOffset ? document.substr(prevOffset, endOffset - prevOffset) : document.substr(prevOffset);
|
|
if (html) {
|
|
block.innerHTML += html;
|
|
block.innerContent.push(html);
|
|
}
|
|
if (null !== leadingHtmlStart) {
|
|
output.push(
|
|
Freeform(
|
|
document.substr(
|
|
leadingHtmlStart,
|
|
tokenStart - leadingHtmlStart
|
|
)
|
|
)
|
|
);
|
|
}
|
|
output.push(block);
|
|
}
|
|
return __toCommonJS(index_exports);
|
|
})();
|
|
(window.wp ||= {}).blockSerializationDefaultParser = wp.blockSerializationDefaultParser;
|
|
})();
|