@authrim/setup 0.1.29 → 0.1.38
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.
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +26 -10
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/core/cloudflare.d.ts +5 -0
- package/dist/core/cloudflare.d.ts.map +1 -1
- package/dist/core/cloudflare.js +49 -0
- package/dist/core/cloudflare.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +46 -12
- package/dist/web/api.js.map +1 -1
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +153 -37
- package/dist/web/ui.js.map +1 -1
- package/package.json +1 -1
package/dist/web/ui.js
CHANGED
|
@@ -567,11 +567,14 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
567
567
|
display: flex;
|
|
568
568
|
justify-content: space-between;
|
|
569
569
|
align-items: center;
|
|
570
|
+
cursor: pointer;
|
|
571
|
+
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
|
|
570
572
|
}
|
|
571
573
|
|
|
572
574
|
.env-card:hover {
|
|
573
575
|
border-color: var(--primary);
|
|
574
576
|
background: #f8fafc;
|
|
577
|
+
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1);
|
|
575
578
|
}
|
|
576
579
|
|
|
577
580
|
.env-card-info {
|
|
@@ -918,36 +921,32 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
918
921
|
<div class="domain-section">
|
|
919
922
|
<h4>🌐 API / Issuer Domain</h4>
|
|
920
923
|
|
|
921
|
-
<!-- Naked Domain Option -->
|
|
922
924
|
<div class="form-group" style="margin-bottom: 0.75rem;">
|
|
923
|
-
<label
|
|
925
|
+
<label for="base-domain">Base Domain (API Domain)</label>
|
|
926
|
+
<input type="text" id="base-domain" placeholder="oidc.example.com">
|
|
927
|
+
<small style="color: var(--text-muted)">Custom domain for Authrim. Leave empty to use workers.dev</small>
|
|
928
|
+
<label class="checkbox-item" style="display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem;">
|
|
924
929
|
<input type="checkbox" id="naked-domain">
|
|
925
|
-
<span>
|
|
930
|
+
<span>Exclude tenant name from URL</span>
|
|
926
931
|
</label>
|
|
927
932
|
<small style="color: var(--text-muted); margin-left: 1.5rem;">
|
|
928
|
-
Use https://example.com
|
|
933
|
+
Use https://example.com instead of https://{tenant}.example.com
|
|
929
934
|
</small>
|
|
930
935
|
</div>
|
|
931
936
|
|
|
932
|
-
<div class="form-group" style="margin-bottom: 0.75rem;">
|
|
933
|
-
<label for="base-domain">Base Domain (API Domain)</label>
|
|
934
|
-
<input type="text" id="base-domain" placeholder="oidc.example.com">
|
|
935
|
-
<small style="color: var(--text-muted)">Custom domain for Authrim. Leave empty to use workers.dev</small>
|
|
936
|
-
</div>
|
|
937
|
-
|
|
938
937
|
<!-- Default Tenant (hidden when naked domain is checked) -->
|
|
939
938
|
<div id="tenant-fields">
|
|
940
939
|
<div class="form-group" style="margin-bottom: 0.5rem;">
|
|
941
|
-
<label for="tenant-name">Default Tenant
|
|
940
|
+
<label for="tenant-name">Default Tenant ID</label>
|
|
942
941
|
<input type="text" id="tenant-name" placeholder="default" value="default">
|
|
943
942
|
<small style="color: var(--text-muted)">First tenant identifier (lowercase, no spaces)</small>
|
|
944
943
|
</div>
|
|
945
944
|
</div>
|
|
946
945
|
|
|
947
946
|
<div class="form-group" style="margin-bottom: 0;">
|
|
948
|
-
<label for="tenant-display">
|
|
949
|
-
<input type="text" id="tenant-display" placeholder="My
|
|
950
|
-
<small style="color: var(--text-muted)">
|
|
947
|
+
<label for="tenant-display">Tenant Display Name</label>
|
|
948
|
+
<input type="text" id="tenant-display" placeholder="My Company" value="Default Tenant">
|
|
949
|
+
<small style="color: var(--text-muted)">Name shown on login page and consent screen</small>
|
|
951
950
|
</div>
|
|
952
951
|
</div>
|
|
953
952
|
|
|
@@ -983,6 +982,10 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
983
982
|
<!-- 4. Preview Section (at the bottom) -->
|
|
984
983
|
<div class="infra-section" id="config-preview">
|
|
985
984
|
<h4>📋 Configuration Preview</h4>
|
|
985
|
+
<div class="infra-item">
|
|
986
|
+
<span class="infra-label">Components:</span>
|
|
987
|
+
<span class="infra-value" id="preview-components">API, Login UI, Admin UI</span>
|
|
988
|
+
</div>
|
|
986
989
|
<div class="infra-item">
|
|
987
990
|
<span class="infra-label">Workers:</span>
|
|
988
991
|
<span class="infra-value" id="preview-workers">{env}-ar-router, {env}-ar-auth, ...</span>
|
|
@@ -1051,6 +1054,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1051
1054
|
<div class="button-group">
|
|
1052
1055
|
<button class="btn-secondary" id="btn-back-config">Back</button>
|
|
1053
1056
|
<button class="btn-primary" id="btn-provision">Create Resources</button>
|
|
1057
|
+
<button class="btn-secondary hidden" id="btn-save-config-provision" title="Save configuration to file">💾 Save Config</button>
|
|
1054
1058
|
<button class="btn-primary hidden" id="btn-goto-deploy">Continue to Deploy →</button>
|
|
1055
1059
|
</div>
|
|
1056
1060
|
</div>
|
|
@@ -1089,11 +1093,15 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1089
1093
|
<div class="alert alert-info" style="margin-top: 1rem;">
|
|
1090
1094
|
<strong>Next Steps:</strong>
|
|
1091
1095
|
<ol style="margin-left: 1.5rem; margin-top: 0.5rem;">
|
|
1092
|
-
<li>Visit the
|
|
1096
|
+
<li>Visit the <strong>Admin Setup</strong> URL above to register your first admin with Passkey</li>
|
|
1093
1097
|
<li>Log in to the Admin UI to create OAuth clients</li>
|
|
1094
1098
|
<li>Configure your application to use the OIDC endpoints</li>
|
|
1095
1099
|
</ol>
|
|
1096
1100
|
</div>
|
|
1101
|
+
|
|
1102
|
+
<div class="button-group" style="margin-top: 1.5rem;">
|
|
1103
|
+
<button class="btn-secondary" id="btn-save-config-complete" title="Save configuration to file">💾 Save Configuration</button>
|
|
1104
|
+
</div>
|
|
1097
1105
|
</div>
|
|
1098
1106
|
|
|
1099
1107
|
<!-- Environment Management: List -->
|
|
@@ -1295,6 +1303,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1295
1303
|
let selectedEnvForDetail = null;
|
|
1296
1304
|
let selectedEnvForDelete = null;
|
|
1297
1305
|
let workingDirectory = '';
|
|
1306
|
+
let workersSubdomain = ''; // e.g., 'sgrastar' for {worker}.sgrastar.workers.dev
|
|
1298
1307
|
|
|
1299
1308
|
// API helpers (with session token authentication)
|
|
1300
1309
|
async function api(endpoint, options = {}) {
|
|
@@ -1436,8 +1445,9 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1436
1445
|
prereqStatus.textContent = 'Ready';
|
|
1437
1446
|
prereqStatus.className = 'status-badge status-success';
|
|
1438
1447
|
|
|
1439
|
-
// Store working directory for later use
|
|
1448
|
+
// Store working directory and workers subdomain for later use
|
|
1440
1449
|
workingDirectory = result.cwd || '';
|
|
1450
|
+
workersSubdomain = result.workersSubdomain || '';
|
|
1441
1451
|
|
|
1442
1452
|
const alertDiv = document.createElement('div');
|
|
1443
1453
|
alertDiv.className = 'alert alert-success';
|
|
@@ -1586,21 +1596,41 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1586
1596
|
const loginDomain = document.getElementById('login-domain').value.trim();
|
|
1587
1597
|
const adminDomain = document.getElementById('admin-domain').value.trim();
|
|
1588
1598
|
|
|
1599
|
+
// Components - build list based on mode and selections
|
|
1600
|
+
const components = ['API'];
|
|
1601
|
+
if (setupMode === 'quick') {
|
|
1602
|
+
components.push('Login UI', 'Admin UI');
|
|
1603
|
+
} else {
|
|
1604
|
+
if (document.getElementById('comp-login-ui').checked) components.push('Login UI');
|
|
1605
|
+
if (document.getElementById('comp-admin-ui').checked) components.push('Admin UI');
|
|
1606
|
+
if (document.getElementById('comp-saml').checked) components.push('SAML IdP');
|
|
1607
|
+
if (document.getElementById('comp-async').checked) components.push('Device Flow/CIBA');
|
|
1608
|
+
if (document.getElementById('comp-vc').checked) components.push('Verifiable Credentials');
|
|
1609
|
+
}
|
|
1610
|
+
document.getElementById('preview-components').textContent = components.join(', ');
|
|
1611
|
+
|
|
1589
1612
|
// Workers
|
|
1590
1613
|
document.getElementById('preview-workers').textContent = env + '-ar-router, ' + env + '-ar-auth, ...';
|
|
1591
1614
|
|
|
1592
1615
|
// Issuer URL
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1616
|
+
const workersDomain = workersSubdomain
|
|
1617
|
+
? env + '-ar-router.' + workersSubdomain + '.workers.dev'
|
|
1618
|
+
: env + '-ar-router.workers.dev';
|
|
1619
|
+
|
|
1620
|
+
if (nakedDomain) {
|
|
1621
|
+
// Naked domain: no tenant prefix
|
|
1622
|
+
if (baseDomain) {
|
|
1596
1623
|
document.getElementById('preview-issuer').textContent = 'https://' + baseDomain;
|
|
1597
1624
|
} else {
|
|
1598
|
-
|
|
1599
|
-
document.getElementById('preview-issuer').textContent = 'https://' + tenantName + '.' + baseDomain;
|
|
1625
|
+
document.getElementById('preview-issuer').textContent = 'https://' + workersDomain;
|
|
1600
1626
|
}
|
|
1601
1627
|
} else {
|
|
1602
|
-
//
|
|
1603
|
-
|
|
1628
|
+
// With tenant prefix
|
|
1629
|
+
if (baseDomain) {
|
|
1630
|
+
document.getElementById('preview-issuer').textContent = 'https://' + tenantName + '.' + baseDomain;
|
|
1631
|
+
} else {
|
|
1632
|
+
document.getElementById('preview-issuer').textContent = 'https://' + tenantName + '.' + workersDomain;
|
|
1633
|
+
}
|
|
1604
1634
|
}
|
|
1605
1635
|
|
|
1606
1636
|
// Login UI - check if component is enabled (in custom mode)
|
|
@@ -1635,7 +1665,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1635
1665
|
}
|
|
1636
1666
|
|
|
1637
1667
|
// Attach event listeners to all inputs
|
|
1638
|
-
['env', 'base-domain', 'naked-domain', 'tenant-name', 'login-domain', 'admin-domain', 'comp-login-ui', 'comp-admin-ui'].forEach(id => {
|
|
1668
|
+
['env', 'base-domain', 'naked-domain', 'tenant-name', 'login-domain', 'admin-domain', 'comp-login-ui', 'comp-admin-ui', 'comp-saml', 'comp-async', 'comp-vc'].forEach(id => {
|
|
1639
1669
|
const el = document.getElementById(id);
|
|
1640
1670
|
if (el) {
|
|
1641
1671
|
el.addEventListener('input', updatePreview);
|
|
@@ -1966,11 +1996,49 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1966
1996
|
if (result.success) {
|
|
1967
1997
|
output.textContent += '\\n✓ Deployment complete!\\n';
|
|
1968
1998
|
scrollToBottom(log);
|
|
1999
|
+
|
|
2000
|
+
// Complete admin setup to get setup URL
|
|
2001
|
+
output.textContent += '\\nSetting up initial admin...\\n';
|
|
2002
|
+
scrollToBottom(log);
|
|
2003
|
+
const workersDomain = workersSubdomain
|
|
2004
|
+
? config.env + '-ar-router.' + workersSubdomain + '.workers.dev'
|
|
2005
|
+
: config.env + '-ar-router.workers.dev';
|
|
2006
|
+
// Build API URL with tenant prefix if not naked domain
|
|
2007
|
+
let apiUrl;
|
|
2008
|
+
if (config.apiDomain) {
|
|
2009
|
+
// Custom domain - add tenant prefix if not naked domain
|
|
2010
|
+
if (config.tenant && config.tenant.name && !config.tenant.nakedDomain) {
|
|
2011
|
+
apiUrl = 'https://' + config.tenant.name + '.' + config.apiDomain;
|
|
2012
|
+
} else {
|
|
2013
|
+
apiUrl = 'https://' + config.apiDomain;
|
|
2014
|
+
}
|
|
2015
|
+
} else if (config.tenant && config.tenant.name && !config.tenant.nakedDomain) {
|
|
2016
|
+
// Workers.dev with tenant prefix
|
|
2017
|
+
apiUrl = 'https://' + config.tenant.name + '.' + workersDomain;
|
|
2018
|
+
} else {
|
|
2019
|
+
// Workers.dev without tenant prefix (naked domain or no tenant)
|
|
2020
|
+
apiUrl = 'https://' + workersDomain;
|
|
2021
|
+
}
|
|
2022
|
+
const adminSetupResult = await api('/admin/setup', {
|
|
2023
|
+
method: 'POST',
|
|
2024
|
+
body: {
|
|
2025
|
+
env: config.env,
|
|
2026
|
+
baseUrl: apiUrl,
|
|
2027
|
+
keysDir: '.keys',
|
|
2028
|
+
},
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
if (adminSetupResult.success && adminSetupResult.setupUrl) {
|
|
2032
|
+
output.textContent += '✓ Admin setup ready!\\n';
|
|
2033
|
+
} else if (adminSetupResult.alreadyCompleted) {
|
|
2034
|
+
output.textContent += 'ℹ Admin setup already completed\\n';
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1969
2037
|
status.textContent = 'Complete';
|
|
1970
2038
|
status.className = 'status-badge status-success';
|
|
1971
2039
|
|
|
1972
|
-
// Show completion
|
|
1973
|
-
showComplete(result);
|
|
2040
|
+
// Show completion with setup URL
|
|
2041
|
+
showComplete({ ...result, setupUrl: adminSetupResult.setupUrl });
|
|
1974
2042
|
} else {
|
|
1975
2043
|
throw new Error(result.error || 'Deployment failed');
|
|
1976
2044
|
}
|
|
@@ -1988,13 +2056,38 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
1988
2056
|
const urlsEl = document.getElementById('urls');
|
|
1989
2057
|
const env = config.env;
|
|
1990
2058
|
|
|
1991
|
-
|
|
2059
|
+
// Generate correct workers.dev URL with account subdomain
|
|
2060
|
+
const workersDomain = workersSubdomain
|
|
2061
|
+
? env + '-ar-router.' + workersSubdomain + '.workers.dev'
|
|
2062
|
+
: env + '-ar-router.workers.dev';
|
|
2063
|
+
|
|
2064
|
+
// Build API URL with tenant prefix if not naked domain
|
|
2065
|
+
let apiUrl;
|
|
2066
|
+
if (config.apiDomain) {
|
|
2067
|
+
// Custom domain - add tenant prefix if not naked domain
|
|
2068
|
+
if (config.tenant && config.tenant.name && !config.tenant.nakedDomain) {
|
|
2069
|
+
apiUrl = 'https://' + config.tenant.name + '.' + config.apiDomain;
|
|
2070
|
+
} else {
|
|
2071
|
+
apiUrl = 'https://' + config.apiDomain;
|
|
2072
|
+
}
|
|
2073
|
+
} else if (config.tenant && config.tenant.name && !config.tenant.nakedDomain) {
|
|
2074
|
+
// Workers.dev with tenant prefix
|
|
2075
|
+
apiUrl = 'https://' + config.tenant.name + '.' + workersDomain;
|
|
2076
|
+
} else {
|
|
2077
|
+
// Workers.dev without tenant prefix (naked domain or no tenant)
|
|
2078
|
+
apiUrl = 'https://' + workersDomain;
|
|
2079
|
+
}
|
|
1992
2080
|
const loginUrl = config.loginUiDomain ? 'https://' + config.loginUiDomain : 'https://' + env + '-ar-ui.pages.dev';
|
|
1993
2081
|
const adminUrl = config.adminUiDomain ? 'https://' + config.adminUiDomain : 'https://' + env + '-ar-ui.pages.dev/admin';
|
|
1994
2082
|
|
|
1995
2083
|
// Clear and rebuild URLs section safely
|
|
1996
2084
|
urlsEl.textContent = '';
|
|
2085
|
+
|
|
2086
|
+
// API URL with OIDC Discovery link
|
|
1997
2087
|
urlsEl.appendChild(createUrlItem('API (Issuer):', apiUrl));
|
|
2088
|
+
const discoveryUrl = apiUrl + '/.well-known/openid-configuration';
|
|
2089
|
+
urlsEl.appendChild(createUrlItem('Discovery:', discoveryUrl));
|
|
2090
|
+
|
|
1998
2091
|
urlsEl.appendChild(createUrlItem('Login UI:', loginUrl));
|
|
1999
2092
|
urlsEl.appendChild(createUrlItem('Admin UI:', adminUrl));
|
|
2000
2093
|
|
|
@@ -2072,18 +2165,49 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
2072
2165
|
function updateProvisionButtons() {
|
|
2073
2166
|
const btnProvision = document.getElementById('btn-provision');
|
|
2074
2167
|
const btnGotoDeploy = document.getElementById('btn-goto-deploy');
|
|
2168
|
+
const btnSaveConfig = document.getElementById('btn-save-config-provision');
|
|
2075
2169
|
|
|
2076
2170
|
if (provisioningCompleted) {
|
|
2077
2171
|
btnProvision.textContent = 'Re-provision (Delete & Create)';
|
|
2078
2172
|
btnProvision.disabled = false;
|
|
2079
2173
|
btnGotoDeploy.classList.remove('hidden');
|
|
2174
|
+
btnSaveConfig.classList.remove('hidden');
|
|
2080
2175
|
} else {
|
|
2081
2176
|
btnProvision.textContent = 'Create Resources';
|
|
2082
2177
|
btnProvision.disabled = false;
|
|
2083
2178
|
btnGotoDeploy.classList.add('hidden');
|
|
2179
|
+
btnSaveConfig.classList.add('hidden');
|
|
2084
2180
|
}
|
|
2085
2181
|
}
|
|
2086
2182
|
|
|
2183
|
+
// Save configuration to file
|
|
2184
|
+
function saveConfigToFile() {
|
|
2185
|
+
if (!config) {
|
|
2186
|
+
alert('No configuration to save');
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
const configToSave = {
|
|
2191
|
+
...config,
|
|
2192
|
+
savedAt: new Date().toISOString(),
|
|
2193
|
+
version: '0.1.36',
|
|
2194
|
+
};
|
|
2195
|
+
|
|
2196
|
+
const blob = new Blob([JSON.stringify(configToSave, null, 2)], { type: 'application/json' });
|
|
2197
|
+
const url = URL.createObjectURL(blob);
|
|
2198
|
+
const a = document.createElement('a');
|
|
2199
|
+
a.href = url;
|
|
2200
|
+
a.download = 'authrim-config.json';
|
|
2201
|
+
document.body.appendChild(a);
|
|
2202
|
+
a.click();
|
|
2203
|
+
document.body.removeChild(a);
|
|
2204
|
+
URL.revokeObjectURL(url);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// Save config button handlers
|
|
2208
|
+
document.getElementById('btn-save-config-provision').addEventListener('click', saveConfigToFile);
|
|
2209
|
+
document.getElementById('btn-save-config-complete').addEventListener('click', saveConfigToFile);
|
|
2210
|
+
|
|
2087
2211
|
// =============================================================================
|
|
2088
2212
|
// Environment Management
|
|
2089
2213
|
// =============================================================================
|
|
@@ -2196,16 +2320,8 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
|
2196
2320
|
info.appendChild(stats);
|
|
2197
2321
|
card.appendChild(info);
|
|
2198
2322
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
const detailBtn = document.createElement('button');
|
|
2203
|
-
detailBtn.className = 'btn-info';
|
|
2204
|
-
detailBtn.textContent = '📋 Details';
|
|
2205
|
-
detailBtn.addEventListener('click', () => showEnvDetail(env));
|
|
2206
|
-
actions.appendChild(detailBtn);
|
|
2207
|
-
|
|
2208
|
-
card.appendChild(actions);
|
|
2323
|
+
// Make entire card clickable
|
|
2324
|
+
card.addEventListener('click', () => showEnvDetail(env));
|
|
2209
2325
|
container.appendChild(card);
|
|
2210
2326
|
}
|
|
2211
2327
|
}
|
package/dist/web/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,UAAU,eAAe,CAAC,YAAqB,EAAE,UAAoB;IACzE,gDAAgD;IAChD,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAErD,OAAO
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,UAAU,eAAe,CAAC,YAAqB,EAAE,UAAoB;IACzE,gDAAgD;IAChD,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAErD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAyuCoB,SAAS;0BACZ,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+1ChC,CAAC;AACT,CAAC"}
|