Skip to content

Commit e49152a

Browse files
committed
feat: Migrator
1 parent 04edd9f commit e49152a

13 files changed

Lines changed: 315 additions & 15 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Check we need migration 1`] = `
4+
Object {
5+
"app": Object {
6+
"mainLayout": Object {
7+
"areas": Array [
8+
Array [
9+
"menu",
10+
"editor",
11+
"support",
12+
],
13+
],
14+
"columns": Array [
15+
"250px",
16+
"1fr",
17+
"1fr",
18+
],
19+
"rows": Array [
20+
"1fr",
21+
],
22+
},
23+
"sceneStack": Array [
24+
"main",
25+
],
26+
},
27+
"buffer": Object {
28+
"autosave": true,
29+
"changed": false,
30+
"filepath": "",
31+
"filetype": "text",
32+
"lastSavedValue": "",
33+
"reloadCounter": 0,
34+
"value": "",
35+
},
36+
"config": Object {
37+
"committerEmail": "",
38+
"committerName": "",
39+
"doneTutorial": false,
40+
"editorFontFamily": "Inconsolata, monospace",
41+
"editorFontScale": 1,
42+
"editorSpellCheck": false,
43+
"githubApiToken": "",
44+
"githubProxy": "https://cors-buster-zashozaqfk.now.sh/github.com/",
45+
"isFirstVisit": true,
46+
"theme": "dark",
47+
},
48+
"git": Object {
49+
"branches": Array [],
50+
"currentBranch": null,
51+
"history": Array [],
52+
"loaded": false,
53+
"projectRoot": null,
54+
"remoteBranches": Array [],
55+
"remotes": Array [],
56+
"staging": null,
57+
"stagingLoading": true,
58+
"type": "loading",
59+
},
60+
"project": Object {
61+
"projectRoot": "/",
62+
"projects": Array [],
63+
},
64+
"repository": Object {
65+
"currentProjectRoot": "/playground",
66+
"dirCreatingDir": null,
67+
"fileCreatingDir": null,
68+
"renamingFilepath": null,
69+
"touchCounter": 0,
70+
},
71+
}
72+
`;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { rootReducer } from "../src/ui/reducers"
2+
3+
// This is special test for migration checker
4+
test("Check we need migration", () => {
5+
const initialState = rootReducer(undefined as any, { type: "init" })
6+
expect(initialState).toMatchSnapshot()
7+
})

decls.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare module "@babel/core"
66
declare module "@babel/preset-react"
77
declare module "react-contextify"
88
declare module "unified"
9+
declare module "v8n"
910
declare module "redux-logger"
1011
declare module "rehype-react"
1112
declare module "ini"

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@
119119
"remark-rehype": "3.0.0",
120120
"styled-components": "3.3.3",
121121
"unified": "7.0.0",
122-
"uuid": "3.3.2"
122+
"uuid": "3.3.2",
123+
"v8n": "^1.2.0"
123124
},
124125
"resolutions": {
125126
"uglifyjs-webpack-plugin/uglify-es": "3.3.9"

script/deploy.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env sh
2+
node script/migrator.js
3+
git add .
4+
git commit -m "chore: Update migrator"
5+
yarn release
6+
git push origin master --tags
7+
yarn deploy

script/migrator.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env node
2+
const fs = require("fs")
3+
const path = require("path")
4+
const { execSync } = require("child_process")
5+
const prettier = require("prettier")
6+
7+
try {
8+
execSync("yarn jest __tests__/checkWeNeedMigration.test.ts", {
9+
cwd: path.resolve(__dirname, "..")
10+
})
11+
} catch (e) {
12+
if (e.status === 1) {
13+
const migratorPath = path.join(__dirname, "../src/migrator.json")
14+
const jsonStr = fs.readFileSync(migratorPath).toString()
15+
const migrator = JSON.parse(jsonStr)
16+
const versions = Object.keys(migrator.migrations).map(n => Number(n))
17+
const latest = Math.max(...versions)
18+
const nextVersion = latest + 1
19+
20+
const newMigrator = {
21+
version: nextVersion,
22+
migrations: {
23+
...migrator.migrations,
24+
[nextVersion]: ["reuseConfigInState"]
25+
}
26+
}
27+
fs.writeFileSync(
28+
migratorPath,
29+
prettier.format(JSON.stringify(newMigrator), { parser: "json" })
30+
)
31+
console.log("----")
32+
console.log("created new migrator:", nextVersion, ["reuseConfigInState"])
33+
console.log("----")
34+
execSync("yarn jest __tests__/checkWeNeedMigration.test.ts -u", {
35+
cwd: path.resolve(__dirname, "..")
36+
})
37+
}
38+
}

src/migrator.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"version": 0,
3+
"migrations": {
4+
"0": ["reuseConfigInState"]
5+
}
6+
}

src/ui/components/organisms/GlobalHeader/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import {
22
Alignment,
33
Button,
44
Classes,
5+
Navbar,
56
NavbarGroup,
6-
NavbarHeading,
7-
Navbar
7+
NavbarHeading
88
} from "@blueprintjs/core"
99
import React from "react"
1010
import styled from "styled-components"
@@ -26,7 +26,7 @@ export const GlobalHeader = connector(
2626
<StyledNavbar className={Classes.DARK}>
2727
<NavbarGroup align={Alignment.LEFT} style={sharedNavbarStyle}>
2828
<NavbarHeading style={{ ...sharedNavbarStyle, paddingTop: 5 }}>
29-
NextEditor
29+
Next Editor
3030
</NavbarHeading>
3131
{/* <NavbarDivider />
3232
<Popover

src/ui/reducers/config.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ import {
44
createReducer,
55
Reducer
66
} from "hard-reducer"
7+
import v8n from "v8n"
8+
9+
v8n.extend({
10+
acceptableTheme() {
11+
return (value: string) => ["dark", "default"].includes(value)
12+
}
13+
})
14+
15+
export const rules = {
16+
committerName: v8n().string(),
17+
committerEmail: v8n().string(),
18+
githubApiToken: v8n().string(),
19+
editorFontScale: v8n().number(),
20+
editorFontFamily: v8n().string(),
21+
editorSpellCheck: v8n().boolean(),
22+
githubProxy: v8n().string(),
23+
theme: v8n()
24+
.string()
25+
.acceptableTheme(),
26+
isFirstVisit: v8n().boolean(),
27+
doneTutorial: v8n().boolean()
28+
}
729

830
const { createAction } = buildActionCreator({
931
prefix: "config/"
@@ -14,6 +36,9 @@ export type ConfigState = {
1436
committerEmail: string
1537
githubApiToken: string
1638
githubProxy: string
39+
editorSpellCheck: boolean
40+
editorFontFamily: string
41+
editorFontScale: number
1742
isFirstVisit: boolean
1843
doneTutorial: boolean
1944
theme: string
@@ -22,12 +47,22 @@ export type ConfigState = {
2247
export const setConfigValue: ActionCreator<{
2348
key: string
2449
value: string | boolean
25-
}> = createAction("set-config-value")
50+
valid: boolean
51+
}> = createAction("set-config-value", input => {
52+
const rule = (rules as any)[input.key]
53+
return {
54+
...input,
55+
valid: rule.test(input.value)
56+
}
57+
})
2658

2759
const initalState: ConfigState = {
2860
committerName: "",
2961
committerEmail: "",
3062
githubApiToken: "",
63+
editorFontScale: 1.0,
64+
editorFontFamily: "Inconsolata, monospace",
65+
editorSpellCheck: false,
3166
githubProxy: "https://cors-buster-zashozaqfk.now.sh/github.com/",
3267
theme: "dark",
3368
isFirstVisit: true,
@@ -36,10 +71,15 @@ const initalState: ConfigState = {
3671

3772
export const reducer: Reducer<ConfigState> = createReducer(initalState).case(
3873
setConfigValue,
39-
(state, { key, value }) => {
40-
return {
41-
...state,
42-
[key]: value
43-
} as any
74+
(state, { key, value, valid }) => {
75+
if (valid) {
76+
return {
77+
...state,
78+
[key]: value
79+
} as any
80+
} else {
81+
console.warn(`You can not set ${key}: ${value}`)
82+
return state
83+
}
4484
}
4585
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import assert from "power-assert"
2+
import { rootReducer } from "../../reducers"
3+
import { buildMigrator, reuseConfigInState } from "./../migrator"
4+
5+
const initialState = rootReducer(undefined as any, { type: "init" })
6+
7+
test("reuseConfigInState / reuse same value", () => {
8+
const prevState = {
9+
...initialState,
10+
config: {
11+
...initialState,
12+
editorSpellCheck: true
13+
}
14+
}
15+
16+
const migrated = reuseConfigInState(prevState as any)
17+
assert(initialState.config.editorSpellCheck === false)
18+
assert(migrated.config.editorSpellCheck === true)
19+
})
20+
21+
test("reuseConfigInState / invalidate keys", () => {
22+
const prevState = {
23+
...initialState,
24+
config: {
25+
...initialState,
26+
editorSpellCheck: "not-boolean",
27+
unknownKey: "will dissapear"
28+
}
29+
}
30+
31+
const migrated: any = reuseConfigInState(prevState as any)
32+
assert(initialState.config.editorSpellCheck === false)
33+
assert(migrated.config.editorSpellCheck === false)
34+
assert(migrated.config.unknownKey == null)
35+
})
36+
37+
test("buildMigrator", () => {
38+
const migratorDef = {
39+
version: 0,
40+
migrations: {
41+
0: ["reuseConfigInState"]
42+
}
43+
}
44+
45+
const migrator = buildMigrator(migratorDef)
46+
47+
const prevState = {
48+
...initialState,
49+
config: {
50+
...initialState,
51+
editorSpellCheck: true
52+
}
53+
}
54+
55+
const migrated: any = migrator["0"](prevState)
56+
assert(initialState.config.editorSpellCheck === false)
57+
assert(migrated.config.editorSpellCheck === true)
58+
})

0 commit comments

Comments
 (0)