mysqlsyncr 1.1.10 → 1.2.1

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.
Files changed (62) hide show
  1. package/README.md +246 -186
  2. package/__tests__/acceptCurrent.test.js +117 -0
  3. package/__tests__/columnAttributes.test.js +101 -0
  4. package/__tests__/options.test.js +259 -0
  5. package/__tests__/parser.test.js +167 -166
  6. package/__tests__/parser2.test.js +155 -152
  7. package/__tests__/paths.test.js +21 -0
  8. package/__tests__/prompt.test.js +106 -0
  9. package/__tests__/sync.test.js +348 -0
  10. package/__tests__/templateDrift.test.js +72 -0
  11. package/__tests__/versions.test.js +138 -0
  12. package/dist/index.cjs +4070 -1730
  13. package/dist/index.js +4106 -1768
  14. package/package.json +66 -59
  15. package/rollup.config.js +25 -24
  16. package/src/DBStructureChecker.js +47 -0
  17. package/src/acceptCurrent.js +225 -0
  18. package/src/apply.js +481 -348
  19. package/src/cli.js +61 -34
  20. package/src/compare.js +106 -92
  21. package/src/differences.js +258 -212
  22. package/src/dump.js +63 -54
  23. package/src/getters.js +151 -139
  24. package/src/index.js +223 -170
  25. package/src/loggers.js +156 -139
  26. package/src/normalizers.js +28 -6
  27. package/src/options.js +133 -0
  28. package/src/parsers.js +197 -145
  29. package/src/paths.js +41 -0
  30. package/src/prompt.js +109 -0
  31. package/src/rebuild.js +106 -0
  32. package/src/rows.js +46 -0
  33. package/src/socketServer.js +311 -229
  34. package/src/templateDrift.js +93 -0
  35. package/src/versions.js +112 -0
  36. package/ui/README.md +55 -40
  37. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-k1h7X_-h.woff +0 -0
  38. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-B7du-70m.woff +0 -0
  39. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-CoAZ_DKt.woff +0 -0
  40. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-D0406B4n.woff +0 -0
  41. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-CnAg2DeQ.woff +0 -0
  42. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-BG9VWE5v.woff +0 -0
  43. package/ui/dist/spa/assets/MainLayout-DLH1Kfg-.js +1 -0
  44. package/ui/dist/spa/assets/axios-2hQ_j_Tz.js +1 -0
  45. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-OUIwM9U8.woff +0 -0
  46. package/ui/dist/spa/assets/index-Cqrg985P.js +23 -0
  47. package/ui/dist/spa/assets/index-pOWtXlXQ.css +1 -0
  48. package/ui/dist/spa/index.html +2 -2
  49. package/ui/package.json +43 -42
  50. package/ui/src/components/DefinitionDiff.vue +169 -0
  51. package/ui/src/pages/MainComponent.vue +704 -78
  52. package/ui/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-Y2mpUUkw.woff +0 -0
  53. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--ViM7Ag4Q.woff +0 -0
  54. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--CrFzpgl6.woff +0 -0
  55. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--CW3_XjG_.woff +0 -0
  56. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--Dr8fnyGc.woff +0 -0
  57. package/ui/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-DAYh6T4l.woff +0 -0
  58. package/ui/dist/spa/assets/MainLayout-CvQD3xKr.js +0 -1
  59. package/ui/dist/spa/assets/axios-C0XcclIb.js +0 -1
  60. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
  61. package/ui/dist/spa/assets/index-CwWo8xmP.js +0 -40
  62. package/ui/dist/spa/assets/index-kXpA8n8r.css +0 -5
@@ -0,0 +1,112 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ // '8.0.36-log' -> { major: 8, minor: 0 }; also takes '5.7'
5
+ const parseVersion = (version) => {
6
+ const match = typeof version === 'string' && version.match(/^(\d+)\.(\d+)/);
7
+ return match ? { major: Number(match[1]), minor: Number(match[2]) } : null;
8
+ }
9
+
10
+ export const majorMinor = (version) => {
11
+ const parsed = parseVersion(version);
12
+ return parsed ? `${parsed.major}.${parsed.minor}` : null;
13
+ }
14
+
15
+ const isBefore80 = (version) => {
16
+ const parsed = parseVersion(version);
17
+ return !!parsed && parsed.major < 8;
18
+ }
19
+
20
+ export const getServerVersion = async (connection) => {
21
+ try {
22
+ const rows = await connection.query('SELECT VERSION() AS version');
23
+ return rows?.[0]?.version ?? null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * The MySQL version a template was dumped from: recorded in dumpInfo.json by
31
+ * dump, or else inferred from how its SQL is written. Returns
32
+ * { version, inferred, reason? } or null when it can't tell.
33
+ */
34
+ export const getTemplateVersion = async (dir) => {
35
+ try {
36
+ const info = JSON.parse(await fs.readFile(path.join(dir, 'dumpInfo.json'), 'utf-8'));
37
+ if (info.mysqlVersion) return { version: info.mysqlVersion, inferred: false };
38
+ } catch {
39
+ // No dumpInfo.json, or written before versions were recorded
40
+ }
41
+
42
+ let sql = '';
43
+ try {
44
+ for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
45
+ if (!entry.isDirectory()) continue;
46
+ try {
47
+ const structure = JSON.parse(await fs.readFile(path.join(dir, entry.name, `${entry.name}.json`), 'utf-8'));
48
+ sql += Object.values(structure.tables || {}).map(t => t.createSQL || '').join('\n');
49
+ } catch {
50
+ // Not a database folder
51
+ }
52
+ }
53
+ } catch {
54
+ return null;
55
+ }
56
+ if (/utf8mb4_0900_|\butf8mb3\b/i.test(sql)) {
57
+ return { version: '8.0', inferred: true, reason: 'uses utf8mb4_0900 collations or utf8mb3, which only 8.0 prints' };
58
+ }
59
+ if (/\b(?:smallint|mediumint|int|bigint)\(\d+\)|\btinyint\((?!1\))\d+\)/i.test(sql)) {
60
+ return { version: '5.7', inferred: true, reason: 'shows integer display widths like int(11), as 5.7 and 8.0 before 8.0.19 do' };
61
+ }
62
+ if (/\b(?:smallint|mediumint|int|bigint)\b(?!\()/i.test(sql)) {
63
+ return { version: '8.0', inferred: true, reason: 'shows integers without display widths, as 8.0.19 and later do' };
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * What to know when the template and the server are different MySQL versions.
70
+ */
71
+ export const versionNotes = (templateVersion, serverVersion) => {
72
+ const template = majorMinor(templateVersion);
73
+ const server = majorMinor(serverVersion);
74
+ if (!template || !server || template === server) return [];
75
+ const notes = [
76
+ `The template is from MySQL ${template} and the server is MySQL ${server}. Integer display widths (int(11) vs int), YEAR(4) and utf8 vs utf8mb3 are printed differently by these versions and are ignored when comparing.`
77
+ ];
78
+ if (isBefore80(serverVersion) && !isBefore80(templateVersion)) {
79
+ notes.push(`MySQL ${server} has no utf8mb4_0900 collations, so tables and columns are created with utf8mb4_unicode_ci (utf8mb4_bin for accent- or case-sensitive ones) instead.`);
80
+ notes.push(`Expression defaults such as DEFAULT (...) need MySQL 8.0.13 or later and fail on MySQL ${server}.`);
81
+ }
82
+ return notes;
83
+ }
84
+
85
+ export const getVersionInfo = async (connection, templateDir) => {
86
+ const server = await getServerVersion(connection);
87
+ const template = await getTemplateVersion(templateDir);
88
+ return { server, template, notes: versionNotes(template?.version, server) };
89
+ }
90
+
91
+ // MySQL 8 added the _0900_ collation family, which does not exist on 5.7. The _unicode_ci
92
+ // collation of the same charset is accent- and case-insensitive like _0900_ai_ci and is
93
+ // present on both versions, so a table created with it means the same thing either way.
94
+ // Accent- or case-sensitive _0900_ collations map to the charset's _bin. Null if collation
95
+ // isn't a _0900_ one.
96
+ export const portableCollation = (collation) => {
97
+ const match = /^(\w+?)_0900_(\w+)$/i.exec(collation || '');
98
+ if (!match) return null;
99
+ return /^(?:ai_ci|as_ci)$/i.test(match[2]) ? `${match[1]}_unicode_ci` : `${match[1]}_bin`;
100
+ }
101
+
102
+ /**
103
+ * Rewrite SQL from a template so the server can run it: MySQL 5.7 doesn't
104
+ * know the utf8mb4_0900 collations that 8.0 uses by default, or the utf8mb3_
105
+ * collation names 8.0 prints.
106
+ */
107
+ export const adaptSQLForServer = (sql, serverVersion) => {
108
+ if (typeof sql !== 'string' || !isBefore80(serverVersion)) return sql;
109
+ return sql
110
+ .replace(/\b\w+?_0900_\w+/gi, (collation) => portableCollation(collation) ?? collation)
111
+ .replace(/\butf8mb3_(\w+)/gi, 'utf8_$1');
112
+ }
package/ui/README.md CHANGED
@@ -1,40 +1,55 @@
1
- # MySQLsyncr (mysqlsyncr)
2
-
3
- A simple ui for the sync features in mysqlsyncr
4
-
5
- ## Install the dependencies
6
- ```bash
7
- yarn
8
- # or
9
- npm install
10
- ```
11
-
12
- ### Start the app in development mode (hot-code reloading, error reporting, etc.)
13
- ```bash
14
- quasar dev
15
- ```
16
-
17
-
18
- ### Lint the files
19
- ```bash
20
- yarn lint
21
- # or
22
- npm run lint
23
- ```
24
-
25
-
26
- ### Format the files
27
- ```bash
28
- yarn format
29
- # or
30
- npm run format
31
- ```
32
-
33
-
34
- ### Build the app for production
35
- ```bash
36
- quasar build
37
- ```
38
-
39
- ### Customize the configuration
40
- See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
1
+ # MySQLsyncr (mysqlsyncr)
2
+
3
+ A simple ui for the sync features in mysqlsyncr
4
+
5
+ ## Requirements
6
+
7
+ Building or running the UI in development mode needs **Node.js 22.22.0 or later** (or 24, 26, 28, 30). The Quasar CLI (`@quasar/app-vite`) checks this itself and stops with "INCOMPATIBLE NODE VERSION" on anything older, even though its `package.json` accepts any Node 22.
8
+
9
+ ```bash
10
+ node -v # check
11
+ nvm install 22 # then nvm use 22, if you use nvm
12
+ ```
13
+
14
+ This only applies to building. `mysqlsyncr ui` serves the prebuilt files in `dist/spa`, and installing or running mysqlsyncr doesn't need these packages or this Node version.
15
+
16
+ ## Install the dependencies
17
+ ```bash
18
+ yarn
19
+ # or
20
+ npm install
21
+ ```
22
+
23
+ ### Start the app in development mode (hot-code reloading, error reporting, etc.)
24
+ ```bash
25
+ quasar dev
26
+ ```
27
+
28
+
29
+ ### Lint the files
30
+ ```bash
31
+ yarn lint
32
+ # or
33
+ npm run lint
34
+ ```
35
+
36
+
37
+ ### Format the files
38
+ ```bash
39
+ yarn format
40
+ # or
41
+ npm run format
42
+ ```
43
+
44
+
45
+ ### Build the app for production
46
+ ```bash
47
+ npm run build
48
+ # or
49
+ quasar build
50
+ ```
51
+
52
+ This writes the UI to `dist/spa`, which is what `mysqlsyncr ui` serves and what gets published. It's committed to the repository, so rebuild and commit it after changing anything in `src/`; otherwise the served UI won't include the change.
53
+
54
+ ### Customize the configuration
55
+ See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
@@ -0,0 +1 @@
1
+ import{A as e,C as t,D as n,E as r,F as i,I as a,L as o,M as s,N as c,O as l,P as u,R as d,S as f,T as p,_ as m,a as h,b as g,d as _,g as v,h as y,i as b,j as x,k as S,l as C,m as w,n as T,o as E,p as D,r as O,s as k,t as A,u as j,v as M,w as N,x as P,y as F}from"./index-Cqrg985P.js";var I=m({name:`QPageContainer`,setup(n,{slots:r}){let i=_(),a=p(w,D);if(a===D)return console.error(`QPageContainer needs to be child of QLayout`),D;e(y,!0);let o=F(()=>{let e={};return a.header.space&&(e.paddingTop=`${a.header.size}px`),a.right.space&&(e[`padding${i.lang.rtl?`Left`:`Right`}`]=`${a.right.size}px`),a.footer.space&&(e.paddingBottom=`${a.footer.size}px`),a.left.space&&(e[`padding${i.lang.rtl?`Right`:`Left`}`]=`${a.left.size}px`),e});return()=>t(`div`,{class:`q-page-container`,style:o.value},j(r.default))}});function L(e){let t=o({width:0,height:0}),i=f(),a=null,s=null,c=null,l=0,p,m={width:-1,height:-1};function h(){c!==null&&(clearTimeout(c),c=null)}function g(){if(h(),s===null)return;let{offsetWidth:e,offsetHeight:n}=a;(e!==m.width||n!==m.height)&&(m={width:e,height:n},t.value=m,p?.(m))}function _(){l>0?c===null&&(c=setTimeout(g,l)):g()}function v(){h(),s!==null&&(s.disconnect(),s=null)}let y=new u(()=>{let t=d(e)??{},n=C(t.target,i);l=Number(t.debounce)||0,p=t.onResize,n!==a&&(v(),a=n,m={width:-1,height:-1}),t.disabled===!0||a===null?v():s===null&&(s=new ResizeObserver(_),s.observe(a,{box:`border-box`}),g())});return y.scheduler=()=>{y.run()},i===null?y.run():(n(()=>{y.run()}),r(v)),{elementSize:t,refreshElementSize:g,stopElementSize(){v(),a=null,y.stop()}}}var{passive:R}=v;function z(e){let t=o({top:0,left:0}),i=a(`down`),s=a(!1),c=o({top:0,left:0}),l=o({top:0,left:0}),p={scrollPosition:t,scrollDirection:i,scrollDirectionChanged:s,scrollDelta:c,scrollInflectionPoint:l},m=f(),h=m!==null||N()?_():void 0,g=null,v=!1,y=null,x=`vertical`,S,w,T,D=0,k=0,A=`down`,j={top:0,left:0};function M(){y!==null&&(y(),y=null)}function P(){if(M(),g===null)return;let e=Math.max(0,E(g)),n=O(g),r=e-D,a=n-k;if(x===`vertical`&&r===0||x===`horizontal`&&a===0||r===0&&a===0)return;let o=Math.abs(r)>=Math.abs(a)?r<0?`up`:`down`:a<0?`left`:`right`,u=A!==o,d={top:e,left:n},f={top:r,left:a};D=e,k=n,t.value=d,c.value=f,s.value=u,u&&(A=o,j=d,i.value=o,l.value=d),T?.({position:d,direction:o,directionChanged:u,delta:f,inflectionPoint:j})}function F(){if(S===0)P();else if(y===null){if(S===void 0){let e=requestAnimationFrame(P);y=()=>{cancelAnimationFrame(e)}}else{let e=setTimeout(P,S);y=()=>{clearTimeout(e)}}}}function I(){M(),v&&=(g.removeEventListener(`scroll`,F,R),!1)}let L=new u(()=>{let n=d(e)??{},r=d(n.scrollTarget),a=C(n.target,m),o=r==null?a===null?null:b(a):b(a,r);x=n.axis??`vertical`,S=n.debounce===void 0||n.debounce===null?void 0:Number(n.debounce)||0,T=n.onScroll,o!==g&&(I(),g=o,(D!==0||k!==0)&&(D=k=0,t.value={top:D,left:k},c.value={top:D,left:k}),A!==`down`&&(A=`down`,i.value=A),s.value&&=!1,(j.top!==0||j.left!==0)&&(j={top:0,left:0},l.value=j));let u=h?.lang.rtl===!0,f=w!==void 0&&w!==u;w=u,n.disabled===!0||g===null?I():v?f&&P():(g.addEventListener(`scroll`,F,R),v=!0,P())});return L.scheduler=()=>{L.run()},m===null?L.run():(n(()=>{L.run()}),r(I)),{...p,refreshScroll:P,stopScroll(){I(),g=null,L.stop()}}}var B=/^(h|l)h(h|r) lpr (f|l)f(f|r)$/,V=m({name:`QLayout`,props:{container:Boolean,view:{type:String,default:`hhh lpr fff`,validator:e=>B.test(e.toLowerCase())},onScroll:Function,onScrollHeight:Function,onResize:Function},setup(n,{slots:r,emit:c}){let u=_(),d=o(null),f=o(null),p=()=>n.container?f.value:d.value,m=a(u.screen.height),g=a(n.container?0:u.screen.width),v=a({position:0,direction:`down`,inflectionPoint:0}),y=a(0),b=a(M.value?0:h()),x=F(()=>`q-layout q-layout--`+(n.container?`containerized`:`standard`)),S=F(()=>n.container?null:{minHeight:u.screen.height+`px`}),C=F(()=>b.value===0?null:{[u.lang.rtl?`left`:`right`]:`${b.value}px`}),E=F(()=>b.value===0?null:{[u.lang.rtl?`right`:`left`]:0,[u.lang.rtl?`left`:`right`]:`-${b.value}px`,width:`calc(100% + ${b.value}px)`}),D=null;function O(e){let t={position:e.position.top,direction:e.direction,directionChanged:e.directionChanged,inflectionPoint:e.inflectionPoint.top,delta:e.delta.top};n.container||!document.qScrollPrevented?(D=null,k(t)):D=t}function k(e){v.value=e,n.onScroll!==void 0&&c(`scroll`,e)}function N(e){let{height:t,width:r}=e,i=!1;m.value!==t&&(i=!0,m.value=t,n.onScrollHeight!==void 0&&c(`scrollHeight`,t),I()),g.value!==r&&(i=!0,g.value=r),i&&n.onResize!==void 0&&c(`resize`,e)}function P({height:e}){y.value!==e&&(y.value=e,I())}function I(){if(n.container){let e=m.value>y.value?h():0;b.value!==e&&(b.value=e)}}z({target:p,onScroll:O}),L({target:p,debounce:100,onResize:N}),L(()=>({target:d,debounce:100,disabled:n.container!==!0,onResize:P}));let R=null,B={instances:{},view:F(()=>n.view),isContainer:F(()=>n.container),rootRef:d,height:m,containerHeight:y,scrollbarWidth:b,totalWidth:F(()=>n.container?g.value+b.value:u.screen.width),rows:F(()=>{let e=n.view.toLowerCase().split(` `);return{top:[...e[0]],middle:[...e[1]],bottom:[...e[2]]}}),header:i({size:0,offset:0,space:!1}),right:i({size:300,offset:0,space:!1}),footer:i({size:0,offset:0,space:!1}),left:i({size:300,offset:0,space:!1}),scroll:v,animate(){R===null?document.body.classList.add(`q-body--layout-animate`):clearTimeout(R),R=setTimeout(()=>{R=null,document.body.classList.remove(`q-body--layout-animate`)},155)},update(e,t,n){B[e][t]=n}};e(w,B);{let e=()=>{if(D!==null){let e=D;D=null,k(e)}};A(e),l(()=>{T(e)})}if(h()>0){let e=null,t=document.documentElement,r=()=>{e=null,t.classList.remove(`hide-scrollbar`)},i=()=>{if(e===null){if(t.scrollHeight>u.screen.height)return;t.classList.add(`hide-scrollbar`)}else clearTimeout(e);e=setTimeout(r,300)},a=t=>{e!==null&&t===`remove`&&(clearTimeout(e),r()),window[`${t}EventListener`](`resize`,i)};s(()=>n.container?`remove`:`add`,a),n.container||a(`add`),l(()=>{a(`remove`)})}return()=>{let e=t(`div`,{class:x.value,style:S.value,ref:n.container?f:d,tabindex:-1},j(r.default));return n.container?t(`div`,{class:`q-layout-container overflow-hidden`,ref:d},[t(`div`,{class:`absolute-full`,style:C.value},[t(`div`,{class:`scroll`,style:E.value},[e])])]):e}}}),H={};function U(e,t){let n=x(`router-view`);return S(),g(V,{view:`lHh Lpr lFf`},{default:c(()=>[P(I,null,{default:c(()=>[P(n)]),_:1})]),_:1})}var W=k(H,[[`render`,U]]);export{W as default};
@@ -0,0 +1 @@
1
+ import{c as e,f as t}from"./index-Cqrg985P.js";var n=e.create({baseURL:`/`}),r=t(({app:t})=>{t.config.globalProperties.$axios=e,t.config.globalProperties.$api=n});export{n as api,r as default};