react-native-store-version 1.4.2 → 2.0.0-beta.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.
- package/CHANGELOG.md +28 -0
- package/README.md +514 -43
- package/dist/checkVersion.d.ts +73 -0
- package/dist/checkVersion.d.ts.map +1 -0
- package/dist/checkVersion.js +193 -0
- package/dist/cli/check.js +38 -0
- package/dist/cli/checks/ids.js +111 -0
- package/dist/cli/checks/installed.js +51 -0
- package/dist/cli/checks/peers.js +31 -0
- package/dist/cli/checks/rollout.js +141 -0
- package/dist/cli/checks/store.js +71 -0
- package/dist/cli/checks/versions.js +83 -0
- package/dist/cli/index.js +43 -0
- package/dist/cli/main.js +182 -0
- package/dist/cli/output.js +77 -0
- package/dist/cli/packages.js +40 -0
- package/dist/cli/project/android.js +60 -0
- package/dist/cli/project/expo.js +194 -0
- package/dist/cli/project/files.js +42 -0
- package/dist/cli/project/gradle.js +145 -0
- package/dist/cli/project/index.js +10 -0
- package/dist/cli/project/ios.js +190 -0
- package/dist/cli/project/jsx.js +327 -0
- package/dist/cli/project/plist.js +166 -0
- package/dist/cli/project/sources.js +86 -0
- package/dist/cli/result.js +12 -0
- package/dist/compare.d.ts +31 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +68 -0
- package/dist/detect.d.ts +68 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +103 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +23 -0
- package/dist/fetchers/android.d.ts +53 -0
- package/dist/fetchers/android.d.ts.map +1 -0
- package/dist/fetchers/android.js +91 -0
- package/dist/fetchers/custom.d.ts +29 -0
- package/dist/fetchers/custom.d.ts.map +1 -0
- package/dist/fetchers/custom.js +41 -0
- package/dist/fetchers/ios.d.ts +61 -0
- package/dist/fetchers/ios.d.ts.map +1 -0
- package/dist/fetchers/ios.js +79 -0
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -58
- package/dist/openStore.d.ts +29 -0
- package/dist/openStore.d.ts.map +1 -0
- package/dist/openStore.js +46 -0
- package/dist/policy.d.ts +75 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +128 -0
- package/dist/react/DefaultForceModal.d.ts +26 -0
- package/dist/react/DefaultForceModal.d.ts.map +1 -0
- package/dist/react/DefaultForceModal.js +58 -0
- package/dist/react/DefaultWarningSheet.d.ts +26 -0
- package/dist/react/DefaultWarningSheet.d.ts.map +1 -0
- package/dist/react/DefaultWarningSheet.js +66 -0
- package/dist/react/StoreVersionModal.d.ts +52 -0
- package/dist/react/StoreVersionModal.d.ts.map +1 -0
- package/dist/react/StoreVersionModal.js +250 -0
- package/dist/react/StoreVersionProvider.d.ts +51 -0
- package/dist/react/StoreVersionProvider.d.ts.map +1 -0
- package/dist/react/StoreVersionProvider.js +69 -0
- package/dist/react/defaultUi.d.ts +72 -0
- package/dist/react/defaultUi.d.ts.map +1 -0
- package/dist/react/defaultUi.js +118 -0
- package/dist/react/engine.d.ts +146 -0
- package/dist/react/engine.d.ts.map +1 -0
- package/dist/react/engine.js +541 -0
- package/dist/react/texts.d.ts +43 -0
- package/dist/react/texts.d.ts.map +1 -0
- package/dist/react/texts.js +57 -0
- package/dist/react/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/react/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/react/useIsomorphicLayoutEffect.js +6 -0
- package/dist/react/useStoreVersion.d.ts +121 -0
- package/dist/react/useStoreVersion.d.ts.map +1 -0
- package/dist/react/useStoreVersion.js +23 -0
- package/dist/request.d.ts +78 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +227 -0
- package/dist/resolve.d.ts +98 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +218 -0
- package/dist/texts/ja.d.ts +8 -0
- package/dist/texts/ja.d.ts.map +1 -0
- package/dist/texts/ja.js +18 -0
- package/dist/vendor/compare-versions.d.ts +52 -0
- package/dist/vendor/compare-versions.d.ts.map +1 -0
- package/dist/vendor/compare-versions.js +111 -0
- package/package.json +83 -44
- package/src/checkVersion.ts +273 -0
- package/src/cli/check.ts +59 -0
- package/src/cli/checks/ids.ts +163 -0
- package/src/cli/checks/installed.ts +75 -0
- package/src/cli/checks/peers.ts +34 -0
- package/src/cli/checks/rollout.ts +183 -0
- package/src/cli/checks/store.ts +105 -0
- package/src/cli/checks/versions.ts +108 -0
- package/src/cli/index.ts +51 -0
- package/src/cli/main.ts +229 -0
- package/src/cli/output.ts +122 -0
- package/src/cli/packages.ts +43 -0
- package/src/cli/project/android.ts +96 -0
- package/src/cli/project/expo.ts +232 -0
- package/src/cli/project/files.ts +56 -0
- package/src/cli/project/gradle.ts +181 -0
- package/src/cli/project/index.ts +18 -0
- package/src/cli/project/ios.ts +284 -0
- package/src/cli/project/jsx.ts +354 -0
- package/src/cli/project/plist.ts +179 -0
- package/src/cli/project/sources.ts +118 -0
- package/src/cli/result.ts +34 -0
- package/src/compare.ts +101 -0
- package/src/detect.ts +178 -0
- package/src/errors.ts +63 -0
- package/src/fetchers/android.ts +136 -0
- package/src/fetchers/custom.ts +84 -0
- package/src/fetchers/ios.ts +160 -0
- package/src/index.ts +37 -0
- package/src/openStore.ts +74 -0
- package/src/policy.ts +223 -0
- package/src/react/DefaultForceModal.tsx +100 -0
- package/src/react/DefaultWarningSheet.tsx +115 -0
- package/src/react/StoreVersionModal.tsx +365 -0
- package/src/react/StoreVersionProvider.tsx +149 -0
- package/src/react/defaultUi.tsx +205 -0
- package/src/react/engine.ts +768 -0
- package/src/react/texts.ts +119 -0
- package/src/react/useIsomorphicLayoutEffect.ts +8 -0
- package/src/react/useStoreVersion.ts +153 -0
- package/src/request.ts +328 -0
- package/src/resolve.ts +388 -0
- package/src/texts/ja.ts +26 -0
- package/src/vendor/compare-versions.ts +128 -0
- package/texts/ja.d.ts +2 -0
- package/texts/ja.js +3 -0
- package/.eslintrc.js +0 -51
- package/.github/FUNDING.yml +0 -3
- package/.github/workflows/pr.yml +0 -48
- package/.github/workflows/release.yml +0 -259
- package/.prettierrc.js +0 -6
- package/babel.config.js +0 -3
- package/dist/android.d.ts +0 -1
- package/dist/android.js +0 -17
- package/dist/ios.d.ts +0 -1
- package/dist/ios.js +0 -18
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// ソースの JSX の開始タグ(`<StoreVersionProvider …>`)の属性の簡易な読み取り(C8)。構文解析はせず、
|
|
2
|
+
// 属性の `{ … }` の中の波括弧・文字列・コメントを数えて、開始タグの終わりの `>` を見つける(`onResult={(r) => …}` の `>` で
|
|
3
|
+
// 終わらないように)。タグはコメントと文字列の中身を消した写し(maskSource)の上で探す(コメントアウトしたタグや、
|
|
4
|
+
// 文字列に書いたコードの例をタグとして数えない)。
|
|
5
|
+
// 限界(構文解析をしないため。詳しくは maskSource):
|
|
6
|
+
// - JSX のテキストの中のアポストロフィ・引用符(`fallback={<Text>Don't</Text>}` のように属性の式の中に書いた JSX や、
|
|
7
|
+
// タグの手前の同じ行の `<Text>Don't</Text>`)を文字列の始まりとして読み、その行の範囲を読み違える
|
|
8
|
+
// - JSX のテキストの中の `//` / ` /* `(空白の後)はコメントとして読む(`//` はその行、`/*` は次の `*/` まで消える)
|
|
9
|
+
// - テキストのバッククォートが、後ろの本物のテンプレートリテラルの開きと対になると、その間を文字列として読む
|
|
10
|
+
// - 割り算と正規表現は直前の文字だけで見分ける(`if (x) /re/.test(s)` の `)` の後や、JSX のテキストの `{a}/{b}/` は読み違える)
|
|
11
|
+
// - 属性の `{ … }` の中(readAttributes)は正規表現リテラルを数えない(属性の式ではまず使われない)
|
|
12
|
+
/** `text[i]` の引用符で始まる文字列の終わりの次の位置。テンプレートリテラルの `${ … }` は式として飛ばす */
|
|
13
|
+
const skipString = (text, i) => {
|
|
14
|
+
const quote = text[i];
|
|
15
|
+
let j = i + 1;
|
|
16
|
+
while (j < text.length) {
|
|
17
|
+
const c = text[j];
|
|
18
|
+
if (c === '\\') {
|
|
19
|
+
j += 2;
|
|
20
|
+
}
|
|
21
|
+
else if (c === quote) {
|
|
22
|
+
return j + 1;
|
|
23
|
+
}
|
|
24
|
+
else if (quote === '`' && c === '$' && text[j + 1] === '{') {
|
|
25
|
+
j = skipBraces(text, j + 1);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
j += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return text.length;
|
|
32
|
+
};
|
|
33
|
+
/** `text[i]` の `{` に対応する `}` の次の位置。閉じていなければ末尾 */
|
|
34
|
+
const skipBraces = (text, i) => {
|
|
35
|
+
let depth = 0;
|
|
36
|
+
let j = i;
|
|
37
|
+
while (j < text.length) {
|
|
38
|
+
const c = text[j];
|
|
39
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
40
|
+
j = skipString(text, j);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (c === '/' && text[j + 1] === '/') {
|
|
44
|
+
const end = text.indexOf('\n', j);
|
|
45
|
+
j = end === -1 ? text.length : end;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (c === '/' && text[j + 1] === '*') {
|
|
49
|
+
const end = text.indexOf('*/', j + 2);
|
|
50
|
+
j = end === -1 ? text.length : end + 2;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (c === '{') {
|
|
54
|
+
depth += 1;
|
|
55
|
+
}
|
|
56
|
+
else if (c === '}') {
|
|
57
|
+
depth -= 1;
|
|
58
|
+
if (depth === 0) {
|
|
59
|
+
return j + 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
j += 1;
|
|
63
|
+
}
|
|
64
|
+
return text.length;
|
|
65
|
+
};
|
|
66
|
+
const NAME = /^[A-Za-z_$][\w$.:-]*/;
|
|
67
|
+
/** `from` から開始タグの終わり(`>` / `/>`)までの属性を読む */
|
|
68
|
+
const readAttributes = (text, from) => {
|
|
69
|
+
const attributes = [];
|
|
70
|
+
let spreads = 0;
|
|
71
|
+
let i = from;
|
|
72
|
+
// 空白と、属性の間に書ける JS のコメント(`// …` / `/* … */`)を飛ばす
|
|
73
|
+
const skipSpace = () => {
|
|
74
|
+
for (;;) {
|
|
75
|
+
if (/\s/.test(text[i] ?? '')) {
|
|
76
|
+
i += 1;
|
|
77
|
+
}
|
|
78
|
+
else if (text.startsWith('//', i)) {
|
|
79
|
+
const end = text.indexOf('\n', i);
|
|
80
|
+
i = end === -1 ? text.length : end;
|
|
81
|
+
}
|
|
82
|
+
else if (text.startsWith('/*', i)) {
|
|
83
|
+
const end = text.indexOf('*/', i + 2);
|
|
84
|
+
i = end === -1 ? text.length : end + 2;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
for (;;) {
|
|
92
|
+
skipSpace();
|
|
93
|
+
const c = text[i];
|
|
94
|
+
if (c === undefined || c === '>' || (c === '/' && text[i + 1] === '>')) {
|
|
95
|
+
return { attributes, spreads };
|
|
96
|
+
}
|
|
97
|
+
if (c === '{') {
|
|
98
|
+
// `{...props}`。コメントだけの `{/* … */}` は spread ではない
|
|
99
|
+
const end = skipBraces(text, i);
|
|
100
|
+
if (maskSource(text.slice(i + 1, end - 1), { strings: false })
|
|
101
|
+
.trim()
|
|
102
|
+
.startsWith('...')) {
|
|
103
|
+
spreads += 1;
|
|
104
|
+
}
|
|
105
|
+
i = end;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const name = NAME.exec(text.slice(i))?.[0];
|
|
109
|
+
if (name === undefined) {
|
|
110
|
+
// 属性として読めない文字(壊れた・途中までのソース)は 1 文字ずつ飛ばす
|
|
111
|
+
i += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
i += name.length;
|
|
115
|
+
skipSpace();
|
|
116
|
+
if (text[i] !== '=') {
|
|
117
|
+
attributes.push({ name, value: null });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
i += 1;
|
|
121
|
+
skipSpace();
|
|
122
|
+
const start = i;
|
|
123
|
+
const v = text[i];
|
|
124
|
+
if (v === '"' || v === "'") {
|
|
125
|
+
i = skipString(text, i);
|
|
126
|
+
}
|
|
127
|
+
else if (v === '{') {
|
|
128
|
+
i = skipBraces(text, i);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
while (i < text.length && !/[\s>]/.test(text[i])) {
|
|
132
|
+
i += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
attributes.push({ name, value: text.slice(start, i) });
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const blank = (text) => text.replace(/[^\n]/g, ' ');
|
|
139
|
+
/** この文字の後の `/` は正規表現の始まり(割り算ではない) */
|
|
140
|
+
const REGEX_AFTER = /[(,=:[!&|?{};]/;
|
|
141
|
+
/** この語の後の `/` は正規表現の始まり */
|
|
142
|
+
const REGEX_AFTER_WORDS = new Set([
|
|
143
|
+
'return',
|
|
144
|
+
'typeof',
|
|
145
|
+
'case',
|
|
146
|
+
'do',
|
|
147
|
+
'else',
|
|
148
|
+
'in',
|
|
149
|
+
'of',
|
|
150
|
+
'new',
|
|
151
|
+
'delete',
|
|
152
|
+
'void',
|
|
153
|
+
'throw',
|
|
154
|
+
'instanceof',
|
|
155
|
+
'yield',
|
|
156
|
+
'await',
|
|
157
|
+
]);
|
|
158
|
+
/** ここまでのマスク(`out`)の後に来る `/` が正規表現の始まりか(直前の空白でない文字・語・行頭で決める) */
|
|
159
|
+
const startsRegex = (out) => {
|
|
160
|
+
const tail = out.slice(-64).replace(/[ \t]+$/, '');
|
|
161
|
+
if (tail === '' || tail.endsWith('\n')) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
if (REGEX_AFTER.test(tail[tail.length - 1])) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
const word = /[A-Za-z_$][\w$]*$/.exec(tail)?.[0];
|
|
168
|
+
return word !== undefined && REGEX_AFTER_WORDS.has(word);
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* `source[i]` の `/` で始まる正規表現リテラルの終わり(フラグの後)。文字クラス `[…]` の中の `/` とエスケープは閉じとみなさない。
|
|
172
|
+
* 同じ行に閉じが無ければ -1(正規表現ではない)
|
|
173
|
+
*/
|
|
174
|
+
const regexEnd = (source, i) => {
|
|
175
|
+
let inClass = false;
|
|
176
|
+
for (let j = i + 1; j < source.length; j += 1) {
|
|
177
|
+
const c = source[j];
|
|
178
|
+
if (c === '\n') {
|
|
179
|
+
return -1;
|
|
180
|
+
}
|
|
181
|
+
if (c === '\\') {
|
|
182
|
+
j += 1;
|
|
183
|
+
}
|
|
184
|
+
else if (inClass) {
|
|
185
|
+
inClass = c !== ']';
|
|
186
|
+
}
|
|
187
|
+
else if (c === '[') {
|
|
188
|
+
inClass = true;
|
|
189
|
+
}
|
|
190
|
+
else if (c === '/') {
|
|
191
|
+
let end = j + 1;
|
|
192
|
+
while (/[a-z]/.test(source[end] ?? '')) {
|
|
193
|
+
end += 1;
|
|
194
|
+
}
|
|
195
|
+
return end;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return -1;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* `source` と同じ長さの写しで、コメント(`// …` / `/* … *\/`)を空白にしたもの(改行は残す)。`strings` なら、
|
|
202
|
+
* 文字列・テンプレートリテラル・正規表現リテラルの中身も空白にする(引用符と `/` は残す)。
|
|
203
|
+
*
|
|
204
|
+
* 読み違えの被害を小さくするため:
|
|
205
|
+
* - `'…'` / `"…"` は行末で終わったものとして扱う(JS の文字列は改行をまたがない。JSX のテキストのアポストロフィで
|
|
206
|
+
* 読み違えても、その行だけで済む)
|
|
207
|
+
* - 正規表現リテラルは、`/` の前が `(` `,` `=` などの記号・`return` などの語・行頭のときだけ。同じ行に閉じが無ければ正規表現にしない
|
|
208
|
+
* - `\` の直後の `//` / `/*`、語の直後の `/*`(`src/*.ts` のようなパス・glob)はコメントの始まりにしない
|
|
209
|
+
* - 閉じないブロックコメント・テンプレートリテラルは、その記号をただの文字として扱い、続きをそのまま読む(残りを全部消さない)
|
|
210
|
+
*/
|
|
211
|
+
export const maskSource = (source, { strings }) => {
|
|
212
|
+
let out = '';
|
|
213
|
+
let i = 0;
|
|
214
|
+
while (i < source.length) {
|
|
215
|
+
const c = source[i];
|
|
216
|
+
const before = source[i - 1] ?? '';
|
|
217
|
+
if (c === '/' && source[i + 1] === '/' && before !== '\\') {
|
|
218
|
+
const close = source.indexOf('\n', i);
|
|
219
|
+
const end = close === -1 ? source.length : close;
|
|
220
|
+
out += blank(source.slice(i, end));
|
|
221
|
+
i = end;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (c === '/' && source[i + 1] === '*') {
|
|
225
|
+
const close = source.indexOf('*/', i + 2);
|
|
226
|
+
if (before !== '\\' && !/[\w$.]/.test(before) && close !== -1) {
|
|
227
|
+
out += blank(source.slice(i, close + 2));
|
|
228
|
+
i = close + 2;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (c === '/' && source[i + 1] !== '>' && startsRegex(out)) {
|
|
233
|
+
const end = regexEnd(source, i);
|
|
234
|
+
if (end !== -1) {
|
|
235
|
+
const close = source.lastIndexOf('/', end - 1);
|
|
236
|
+
const body = source.slice(i + 1, close);
|
|
237
|
+
out += `/${strings ? blank(body) : body}${source.slice(close, end)}`;
|
|
238
|
+
i = end;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
243
|
+
let j = i + 1;
|
|
244
|
+
while (j < source.length &&
|
|
245
|
+
source[j] !== c &&
|
|
246
|
+
(c === '`' || source[j] !== '\n')) {
|
|
247
|
+
j += source[j] === '\\' ? 2 : 1;
|
|
248
|
+
}
|
|
249
|
+
const closed = j < source.length && source[j] === c;
|
|
250
|
+
if (closed || c !== '`') {
|
|
251
|
+
const content = source.slice(i + 1, Math.min(j, source.length));
|
|
252
|
+
out += c + (strings ? blank(content) : content) + (closed ? c : '');
|
|
253
|
+
i = closed ? j + 1 : Math.min(j, source.length);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
// 閉じないテンプレートリテラル(テキストのバッククォート 1 つなど)は、ただの文字として続きを読む
|
|
257
|
+
}
|
|
258
|
+
out += c;
|
|
259
|
+
i += 1;
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* `source` の中の `<component …>` の開始タグ(`<componentX` / `<component.Item` は別の名前なので入らない)。
|
|
265
|
+
* コメント・文字列の中のものは数えない({@link maskSource})。属性は元のソースから読む
|
|
266
|
+
*/
|
|
267
|
+
export const findOpeningTags = (source, component) => {
|
|
268
|
+
const pattern = new RegExp(`<${component}(?![\\w$.:-])`, 'g');
|
|
269
|
+
const tags = [];
|
|
270
|
+
for (const match of maskSource(source, { strings: true }).matchAll(pattern)) {
|
|
271
|
+
tags.push(readAttributes(source, match.index + match[0].length));
|
|
272
|
+
}
|
|
273
|
+
return tags;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* `expression`(前後の空白は無視)が `{ … }` のオブジェクトリテラル(末尾の `as …` / `satisfies …` は除く)なら、
|
|
277
|
+
* その最上位のプロパティ。そうでなければ null
|
|
278
|
+
*/
|
|
279
|
+
export const readObjectLiteral = (expression) => {
|
|
280
|
+
const text = maskSource(expression, { strings: false }).trim();
|
|
281
|
+
if (!text.startsWith('{')) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const end = skipBraces(text, 0);
|
|
285
|
+
// 末尾の TypeScript の型の注釈(`as const` / `as Policy` / `satisfies Policy`。続けて書いたものも)は値を変えない
|
|
286
|
+
if (!/^((as|satisfies)\s+\S[\s\S]*)?$/.test(text.slice(end).trim())) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const body = text.slice(1, end - 1);
|
|
290
|
+
const parts = [];
|
|
291
|
+
let depth = 0;
|
|
292
|
+
let start = 0;
|
|
293
|
+
for (let i = 0; i < body.length;) {
|
|
294
|
+
const c = body[i];
|
|
295
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
296
|
+
i = skipString(body, i);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if ('([{'.includes(c)) {
|
|
300
|
+
depth += 1;
|
|
301
|
+
}
|
|
302
|
+
else if (')]}'.includes(c)) {
|
|
303
|
+
depth -= 1;
|
|
304
|
+
}
|
|
305
|
+
else if (c === ',' && depth === 0) {
|
|
306
|
+
parts.push(body.slice(start, i));
|
|
307
|
+
start = i + 1;
|
|
308
|
+
}
|
|
309
|
+
i += 1;
|
|
310
|
+
}
|
|
311
|
+
parts.push(body.slice(start));
|
|
312
|
+
const properties = new Map();
|
|
313
|
+
let spread = false;
|
|
314
|
+
for (const part of parts.map((p) => p.trim())) {
|
|
315
|
+
if (part === '') {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const match = /^(?:'([^']*)'|"([^"]*)"|([A-Za-z_$][\w$]*))\s*(?::([\s\S]*))?$/.exec(part);
|
|
319
|
+
if (match === null) {
|
|
320
|
+
// `...base` / `[key]: v` はキーが分からない。メソッド(`major() {}`)なども、ここでは分からないものとして扱う
|
|
321
|
+
spread = true;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
properties.set(match[1] ?? match[2] ?? match[3], match[4] === undefined ? null : match[4].trim());
|
|
325
|
+
}
|
|
326
|
+
return { properties, spread };
|
|
327
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Xcode の project.pbxproj(OpenStep 形式の ASCII property list)の読み取り。
|
|
2
|
+
// 正規表現で `PRODUCT_BUNDLE_IDENTIFIER = …` を拾うと、どのターゲット・どの構成(Debug / Release)の値かが分からない
|
|
3
|
+
// (テストや extension のターゲットも同じキーを持つ)ので、全体を値に直してからオブジェクト ID で辿る(ios.ts)
|
|
4
|
+
export const isDict = (value) => typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
/** 引用符なしの文字列を終える文字(空白と区切り記号) */
|
|
6
|
+
const DELIMITER = /[\s{}()=;,"<>]/;
|
|
7
|
+
const ESCAPES = {
|
|
8
|
+
n: '\n',
|
|
9
|
+
t: '\t',
|
|
10
|
+
r: '\r',
|
|
11
|
+
a: '\x07',
|
|
12
|
+
b: '\b',
|
|
13
|
+
f: '\f',
|
|
14
|
+
v: '\v',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* OpenStep 形式の property list を値に直す。コメント(`/* … *\/` と `// …`。先頭の `// !$*UTF8*$!` も)は読み飛ばす。
|
|
18
|
+
* 形が崩れていれば Error を投げる(位置を添える)
|
|
19
|
+
*/
|
|
20
|
+
export const parsePlist = (text) => {
|
|
21
|
+
let i = 0;
|
|
22
|
+
const fail = (what) => {
|
|
23
|
+
throw new Error(`${what} at offset ${i}`);
|
|
24
|
+
};
|
|
25
|
+
const skipSpace = () => {
|
|
26
|
+
for (;;) {
|
|
27
|
+
const c = text[i];
|
|
28
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
|
29
|
+
i += 1;
|
|
30
|
+
}
|
|
31
|
+
else if (c === '/' && text[i + 1] === '/') {
|
|
32
|
+
const end = text.indexOf('\n', i);
|
|
33
|
+
i = end === -1 ? text.length : end + 1;
|
|
34
|
+
}
|
|
35
|
+
else if (c === '/' && text[i + 1] === '*') {
|
|
36
|
+
const end = text.indexOf('*/', i + 2);
|
|
37
|
+
if (end === -1) {
|
|
38
|
+
fail('unterminated comment');
|
|
39
|
+
}
|
|
40
|
+
i = end + 2;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const expect = (char) => {
|
|
48
|
+
skipSpace();
|
|
49
|
+
if (text[i] !== char) {
|
|
50
|
+
fail(`expected "${char}"`);
|
|
51
|
+
}
|
|
52
|
+
i += 1;
|
|
53
|
+
};
|
|
54
|
+
const quoted = () => {
|
|
55
|
+
// 開きの `"` の次から
|
|
56
|
+
i += 1;
|
|
57
|
+
let out = '';
|
|
58
|
+
for (;;) {
|
|
59
|
+
const c = text[i];
|
|
60
|
+
if (c === undefined) {
|
|
61
|
+
return fail('unterminated string');
|
|
62
|
+
}
|
|
63
|
+
i += 1;
|
|
64
|
+
if (c === '"') {
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
if (c !== '\\') {
|
|
68
|
+
out += c;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const e = text[i];
|
|
72
|
+
if (e === undefined) {
|
|
73
|
+
return fail('unterminated string');
|
|
74
|
+
}
|
|
75
|
+
i += 1;
|
|
76
|
+
if ((e === 'U' || e === 'u') && /^[0-9A-Fa-f]{4}/.test(text.slice(i))) {
|
|
77
|
+
out += String.fromCharCode(Number.parseInt(text.slice(i, i + 4), 16));
|
|
78
|
+
i += 4;
|
|
79
|
+
}
|
|
80
|
+
else if (/[0-7]/.test(e)) {
|
|
81
|
+
// 8 進のエスケープ(最大 3 桁)
|
|
82
|
+
const digits = /^[0-7]{0,2}/.exec(text.slice(i))?.[0] ?? '';
|
|
83
|
+
out += String.fromCharCode(Number.parseInt(e + digits, 8));
|
|
84
|
+
i += digits.length;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
out += ESCAPES[e] ?? e;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const unquoted = () => {
|
|
92
|
+
const start = i;
|
|
93
|
+
while (i < text.length) {
|
|
94
|
+
const c = text[i];
|
|
95
|
+
// `/*` と `//` はコメントの始まり(Xcode は値の直後に空白を入れてコメントを書く)
|
|
96
|
+
if (DELIMITER.test(c) || (c === '/' && /[/*]/.test(text[i + 1] ?? ''))) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
i += 1;
|
|
100
|
+
}
|
|
101
|
+
if (i === start) {
|
|
102
|
+
fail(text[i] === undefined
|
|
103
|
+
? 'unexpected end'
|
|
104
|
+
: `unexpected ${JSON.stringify(text[i])}`);
|
|
105
|
+
}
|
|
106
|
+
return text.slice(start, i);
|
|
107
|
+
};
|
|
108
|
+
const value = () => {
|
|
109
|
+
skipSpace();
|
|
110
|
+
const c = text[i];
|
|
111
|
+
if (c === '{') {
|
|
112
|
+
i += 1;
|
|
113
|
+
const dict = Object.create(null);
|
|
114
|
+
for (;;) {
|
|
115
|
+
skipSpace();
|
|
116
|
+
if (text[i] === '}') {
|
|
117
|
+
i += 1;
|
|
118
|
+
return dict;
|
|
119
|
+
}
|
|
120
|
+
const key = text[i] === '"' ? quoted() : unquoted();
|
|
121
|
+
expect('=');
|
|
122
|
+
dict[key] = value();
|
|
123
|
+
expect(';');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (c === '(') {
|
|
127
|
+
i += 1;
|
|
128
|
+
const list = [];
|
|
129
|
+
for (;;) {
|
|
130
|
+
skipSpace();
|
|
131
|
+
if (text[i] === ')') {
|
|
132
|
+
i += 1;
|
|
133
|
+
return list;
|
|
134
|
+
}
|
|
135
|
+
list.push(value());
|
|
136
|
+
skipSpace();
|
|
137
|
+
if (text[i] === ',') {
|
|
138
|
+
i += 1;
|
|
139
|
+
}
|
|
140
|
+
else if (text[i] !== ')') {
|
|
141
|
+
fail('expected "," or ")"');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (c === '"') {
|
|
146
|
+
return quoted();
|
|
147
|
+
}
|
|
148
|
+
if (c === '<') {
|
|
149
|
+
// <data>(16 進のバイト列)。pbxproj では使われないが、形としては読み飛ばせるようにする
|
|
150
|
+
const end = text.indexOf('>', i);
|
|
151
|
+
if (end === -1) {
|
|
152
|
+
fail('unterminated data');
|
|
153
|
+
}
|
|
154
|
+
const data = text.slice(i, end + 1);
|
|
155
|
+
i = end + 1;
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
return unquoted();
|
|
159
|
+
};
|
|
160
|
+
const result = value();
|
|
161
|
+
skipSpace();
|
|
162
|
+
if (i < text.length) {
|
|
163
|
+
fail('unexpected content after the root value');
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// アプリのソース(.ts / .tsx / .js / .jsx)の走査(C8)。依存・ネイティブ・生成物のディレクトリは見ない。
|
|
2
|
+
// CI の時間を延ばさないよう、ファイル数と 1 ファイルの大きさに上限を設ける
|
|
3
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
4
|
+
import { extname, join } from 'node:path';
|
|
5
|
+
/** 走査しないディレクトリの名前(どの深さでも)。依存・ビルドの生成物・VCS */
|
|
6
|
+
export const EXCLUDED_DIRECTORIES = new Set([
|
|
7
|
+
'node_modules',
|
|
8
|
+
'dist',
|
|
9
|
+
'build',
|
|
10
|
+
'.expo',
|
|
11
|
+
'.git',
|
|
12
|
+
'Pods',
|
|
13
|
+
'coverage',
|
|
14
|
+
'.next',
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* `<cwd>` の直下のときだけ走査しないディレクトリ(ネイティブのプロジェクト)。深い所の `ios` / `android` は
|
|
18
|
+
* プラットフォーム別のソース(`src/android/Root.tsx` など)でありうるので走査する
|
|
19
|
+
*/
|
|
20
|
+
export const ROOT_EXCLUDED_DIRECTORIES = new Set([
|
|
21
|
+
'ios',
|
|
22
|
+
'android',
|
|
23
|
+
]);
|
|
24
|
+
const EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
25
|
+
export const SCAN_LIMITS = {
|
|
26
|
+
maxFiles: 5_000,
|
|
27
|
+
maxFileBytes: 1024 * 1024,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 型宣言(`.d.ts`)は対象にしない。宣言に `getLatestVersion` などの名前があっても使っていることにはならないため
|
|
31
|
+
* (ライブラリの型を写したものなどで、C8 を誤って黙らせないように)
|
|
32
|
+
*/
|
|
33
|
+
const isSource = (name) => EXTENSIONS.has(extname(name)) && !name.endsWith('.d.ts');
|
|
34
|
+
const readEntries = (dir) => {
|
|
35
|
+
try {
|
|
36
|
+
return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// 読めないディレクトリ(権限など)は飛ばす。C8 は簡易な注意なので、走査の失敗で CLI を落とさない
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* `<cwd>` の下のソースを、名前の順に幅優先で読んで `visit` に渡す。シンボリックリンクは(ファイルもディレクトリも)辿らない
|
|
45
|
+
* (node_modules の外を指すもの・循環を避ける)。読めないファイルは飛ばす
|
|
46
|
+
*/
|
|
47
|
+
export const scanSources = (cwd, visit, limits = SCAN_LIMITS) => {
|
|
48
|
+
const result = { scanned: 0, truncated: false, tooLarge: [] };
|
|
49
|
+
let seen = 0;
|
|
50
|
+
const queue = [cwd];
|
|
51
|
+
for (let dir = queue.shift(); dir !== undefined; dir = queue.shift()) {
|
|
52
|
+
for (const entry of readEntries(dir)) {
|
|
53
|
+
// Dirent は lstat の結果なので、シンボリックリンクは isDirectory() / isFile() が false になる
|
|
54
|
+
const path = join(dir, entry.name);
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
if (!EXCLUDED_DIRECTORIES.has(entry.name) &&
|
|
57
|
+
!(dir === cwd && ROOT_EXCLUDED_DIRECTORIES.has(entry.name))) {
|
|
58
|
+
queue.push(path);
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!entry.isFile() || !isSource(entry.name)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
seen += 1;
|
|
66
|
+
if (seen > limits.maxFiles) {
|
|
67
|
+
result.truncated = true;
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
let text;
|
|
71
|
+
try {
|
|
72
|
+
if (statSync(path).size > limits.maxFileBytes) {
|
|
73
|
+
result.tooLarge.push(path);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
text = readFileSync(path, 'utf8');
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
result.scanned += 1;
|
|
82
|
+
visit(path, text);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// チェックの結果の形と、そこから終了コードを決める部分。出力(output.ts)とチェック(checks/)が共有する
|
|
2
|
+
export const countEntries = (entries) => {
|
|
3
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
4
|
+
for (const { level } of entries) {
|
|
5
|
+
if (level === 'error' || level === 'warning' || level === 'info') {
|
|
6
|
+
counts[level] += 1;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return counts;
|
|
10
|
+
};
|
|
11
|
+
/** 終了コード。error があれば 1、`strict` なら warning でも 1。info は常に 0 */
|
|
12
|
+
export const exitCodeOf = (counts, strict) => counts.error > 0 || (strict && counts.warning > 0) ? 1 : 0;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** local と remote の比較結果(v1 の `compareVersion` の戻り値) */
|
|
2
|
+
export type CompareResult = 'new' | 'old' | 'equal';
|
|
3
|
+
/** v1 の `checkVersion` が返す `detail` */
|
|
4
|
+
export type CompareDetail = 'remote > local' | 'remote < local' | 'remote === local';
|
|
5
|
+
/** remote が local より新しいときの、差分の種類 */
|
|
6
|
+
export type DiffType = 'major' | 'minor' | 'patch';
|
|
7
|
+
/**
|
|
8
|
+
* v1 互換のバージョン比較。不正な値は compare-versions のエラー
|
|
9
|
+
* (`Invalid argument not valid semver ('x' received)` など)をそのまま投げる。
|
|
10
|
+
* v1 のメッセージを変えないために、ここでは包まない
|
|
11
|
+
*/
|
|
12
|
+
export declare const compareVersion: (local: string, remote: string) => CompareResult;
|
|
13
|
+
/** `compareVersion` の結果から v1 の `detail` を作る */
|
|
14
|
+
export declare const toDetail: (result: CompareResult) => CompareDetail;
|
|
15
|
+
/**
|
|
16
|
+
* remote が local より新しいときの差分の種類。major / minor / patch の数字を比べ、最初に違う所で決める。
|
|
17
|
+
* 3 つとも同じ(4 桁目以降やプレリリースだけの差)なら `patch`。欠けた桁は 0 とみなす。
|
|
18
|
+
* 入力は検証済みであること(不正値は compare-versions のエラーになる)
|
|
19
|
+
*/
|
|
20
|
+
export declare const diffType: (local: string, remote: string) => DiffType;
|
|
21
|
+
/**
|
|
22
|
+
* 比較できる値かを確かめる。だめなら `StoreVersionError('INVALID_VERSION')`(cause に元のエラー)を投げる。
|
|
23
|
+
* `label` は message に入れる、どの値かの名前(`local` / `remote` / `minimum`)
|
|
24
|
+
*/
|
|
25
|
+
export declare const assertValidVersion: (value: unknown, label: string) => void;
|
|
26
|
+
/**
|
|
27
|
+
* v2 の判定用の比較。不正値は `StoreVersionError('INVALID_VERSION')`(cause に元のエラー)で投げる。
|
|
28
|
+
* `labels` は message に入れる、a / b それぞれの名前(既定は `local` / `remote`)
|
|
29
|
+
*/
|
|
30
|
+
export declare const compareVersionOrThrow: (a: string, b: string, labels?: [string, string]) => CompareResult;
|
|
31
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../src/compare.ts"],"names":[],"mappings":"AAOA,uDAAuD;AACvD,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAEpD,uCAAuC;AACvC,MAAM,MAAM,aAAa,GACrB,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,CAAC;AAEvB,oCAAoC;AACpC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,cAAc,UAClB,MAAM,UACL,MAAM,KACb,aASF,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,QAAQ,WAAY,aAAa,KAAG,aAShD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,UAAW,MAAM,UAAU,MAAM,KAAG,QAWxD,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,UAAW,OAAO,SAAS,MAAM,KAAG,IAUlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,MAC7B,MAAM,KACN,MAAM,WACD,CAAC,MAAM,EAAE,MAAM,CAAC,KACvB,aAIF,CAAC"}
|