odant-work 1.0.12 → 1.0.13
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/#system/certs/russian_trusted_root_ca.pem +33 -0
- package/#system/mail.json.example +9 -0
- package/#system/package-manifest.json +986 -0
- package/#system/sandbox.json +19 -0
- package/#system/settings.json.example +3 -0
- package/package.json +1 -1
- package/run.mjs +78 -11
- package/scripts/check-ignored-code.mjs +25 -1
- package/sources/data/api.js +1071 -0
- package/sources/data/choices.js +125 -0
- package/sources/data/classes.js +412 -0
- package/sources/data/db.js +383 -0
- package/sources/data/errors.js +15 -0
- package/sources/data/expr.js +456 -0
- package/sources/data/objects.js +950 -0
- package/sources/data/posting.js +657 -0
- package/sources/data/print.js +524 -0
- package/sources/data/progress.md +526 -0
- package/sources/data/readme.md +77 -0
- package/sources/data/reports.js +1033 -0
- package/sources/data/schema.js +586 -0
- package/sources/data/scope.js +281 -0
- package/sources/data/sql-inspector.js +892 -0
- package/sources/data/time.js +67 -0
- package/sources/data/workspace.js +152 -0
- package/sources/host/auto-updater.js +52 -10
- package/.vscode/launch.json +0 -32
- package/.vscode/settings.json +0 -2
- package/.vscode/tasks.json +0 -65
- package/ORGANIZATIONS//320/221/320/270/320/267/320/275/320/265/321/201 /320/230/320/275/321/202/320/265/321/200 /320/241/320/276/321/204/321/202/orders/$structure/video/IMG_8524.MP4 +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Время в учётном ядре. Моменты хранятся в миллисекундах; день — местный
|
|
3
|
+
* (сервер стоит в контуре организации), а не UTC: «сегодня» бухгалтера
|
|
4
|
+
* начинается в 00:00 по его часам.
|
|
5
|
+
*/
|
|
6
|
+
import { DataError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
const DAY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
9
|
+
|
|
10
|
+
/** Начало местного дня 'YYYY-MM-DD' в мс. */
|
|
11
|
+
export function dayStart(day) {
|
|
12
|
+
const m = DAY_RE.exec(String(day));
|
|
13
|
+
if (!m)
|
|
14
|
+
throw new DataError(`дата «${day}»: ожидается ГГГГ-ММ-ДД`);
|
|
15
|
+
const t = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])).getTime();
|
|
16
|
+
if (localDay(t) !== day)
|
|
17
|
+
throw new DataError(`дата «${day}» не существует`);
|
|
18
|
+
return t;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Местный день момента: 'YYYY-MM-DD'. */
|
|
22
|
+
export function localDay(ms) {
|
|
23
|
+
const d = new Date(ms);
|
|
24
|
+
const p = n => String(n).padStart(2, '0');
|
|
25
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Момент из значения: число (мс), Date, 'YYYY-MM-DD' (начало местного дня), строка ISO.
|
|
30
|
+
* @returns {number}
|
|
31
|
+
*/
|
|
32
|
+
export function parseTime(v, what = 'время') {
|
|
33
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
34
|
+
return Math.trunc(v);
|
|
35
|
+
if (v instanceof Date && Number.isFinite(v.getTime()))
|
|
36
|
+
return v.getTime();
|
|
37
|
+
const s = String(v ?? '').trim();
|
|
38
|
+
if (DAY_RE.test(s))
|
|
39
|
+
return dayStart(s);
|
|
40
|
+
if (/^\d+$/.test(s))
|
|
41
|
+
return Number(s);
|
|
42
|
+
const t = Date.parse(s);
|
|
43
|
+
if (!s || !Number.isFinite(t))
|
|
44
|
+
throw new DataError(`${what}: «${s}» — не дата`);
|
|
45
|
+
return t;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Полуинтервал [from, to) в мс из границ периода. Граница-день 'YYYY-MM-DD' в `to`
|
|
50
|
+
* включает весь день. Пустая граница — без ограничения.
|
|
51
|
+
*/
|
|
52
|
+
export function periodRange(from, to) {
|
|
53
|
+
const a = from === undefined || from === null || from === '' ? -Infinity : parseTime(from, 'начало периода');
|
|
54
|
+
let b = Infinity;
|
|
55
|
+
if (!(to === undefined || to === null || to === '')) {
|
|
56
|
+
b = parseTime(to, 'конец периода');
|
|
57
|
+
if (DAY_RE.test(String(to).trim()))
|
|
58
|
+
b = nextDay(b);
|
|
59
|
+
}
|
|
60
|
+
return [a, b];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Начало следующего местного дня (корректно при переходе на летнее время). */
|
|
64
|
+
export function nextDay(ms) {
|
|
65
|
+
const d = new Date(ms);
|
|
66
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1).getTime();
|
|
67
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Рабочее пространство пользователя: подразделения и места, где он работает, и меню каждого места.
|
|
3
|
+
*
|
|
4
|
+
* Меню места — ссылки места на классы (справочники, операции, счета), то есть ровно то, что места
|
|
5
|
+
* дают в правах: свои ссылки места — со своим `access`, ссылки вложенных мест — на чтение,
|
|
6
|
+
* общие ссылки подразделения — всем. BOSS видит места подразделения на чтение, ADMIN — со своими
|
|
7
|
+
* правами. Реальные права проверяет сервер при каждом вызове; здесь только описание для интерфейса.
|
|
8
|
+
*
|
|
9
|
+
* Чистая часть (`buildWorkspace`) не знает про сервер: подразделения приходят готовыми
|
|
10
|
+
* (класс, роли пользователя, `#security`), поэтому проверяется без дерева файлов.
|
|
11
|
+
*/
|
|
12
|
+
import { getClass, children } from './classes.js';
|
|
13
|
+
import { FAMILY } from './schema.js';
|
|
14
|
+
import { normalizeSecurity, walkPlaces, trailKey, memberTrailKeys } from '../server/access/workplaces.js';
|
|
15
|
+
|
|
16
|
+
const RANK = { read: 1, write: 2 };
|
|
17
|
+
const SCOPE_RANK = { null: 0, own: 1, place: 2, unit: 3, all: 4 };
|
|
18
|
+
const LIMIT_CLASSES = 5000;
|
|
19
|
+
|
|
20
|
+
/** Права на ссылку: у BOSS и вышестоящего места — только чтение. */
|
|
21
|
+
const readOnly = links => links.map(l => ({ ...l, access: 'read' }));
|
|
22
|
+
|
|
23
|
+
/** Слияние ссылок по id: выше доступ, при равном — шире видимость. */
|
|
24
|
+
function mergeLinks(...lists) {
|
|
25
|
+
const out = new Map();
|
|
26
|
+
for (const l of lists.flat()) {
|
|
27
|
+
const cur = out.get(l.id);
|
|
28
|
+
const better = !cur
|
|
29
|
+
|| (RANK[l.access] ?? 0) > (RANK[cur.access] ?? 0)
|
|
30
|
+
|| ((RANK[l.access] ?? 0) === (RANK[cur.access] ?? 0) && (SCOPE_RANK[l.scope ?? null] ?? 0) > (SCOPE_RANK[cur.scope ?? null] ?? 0));
|
|
31
|
+
if (better)
|
|
32
|
+
out.set(l.id, { id: l.id, access: l.access, scope: l.scope ?? null });
|
|
33
|
+
}
|
|
34
|
+
return [...out.values()];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ownLinks = node => node.LINKS.map(l => ({ id: l.id, access: l.access, scope: l.scope ?? null }));
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Подразделения и места пользователя с меню.
|
|
41
|
+
* @param {object} db БД данных (кэш классов)
|
|
42
|
+
* @param {object} input
|
|
43
|
+
* @param {Array<{ cid, path, label, type, parent, own, roles: string[], admin: boolean, security: object }>} input.units
|
|
44
|
+
* подразделения корня и всех вложенных: класс, роли пользователя в нём, секция `#security`
|
|
45
|
+
* @param {string|null} input.uid пользователь
|
|
46
|
+
* @returns {{ uid, units: Array, contexts: Array, classes: object }}
|
|
47
|
+
* units — плоский список от корня (`parent` — cid вложенного в состав), у видимого: `rights`
|
|
48
|
+
* (`admin`|`boss`|`member`), `links` — меню подразделения без места (общие ссылки и места
|
|
49
|
+
* на чтение для BOSS/ADMIN), `places` — места (`key`, `parent`, `view`: `mine`|`below`|`boss`|`admin`, `links`);
|
|
50
|
+
* contexts — где пользователь работает; classes — путь класса → описание с `children` (пути)
|
|
51
|
+
*/
|
|
52
|
+
export function buildWorkspace(db, { units, uid = null }) {
|
|
53
|
+
const byCid = new Map(units.map(u => [u.cid, u]));
|
|
54
|
+
const rel = u => (u.admin ? 'admin' : u.roles.includes('BOSS') ? 'boss' : u.roles.length ? 'member' : null);
|
|
55
|
+
const visible = new Set(units.filter(u => rel(u)).map(u => u.cid));
|
|
56
|
+
const shown = new Set(visible);
|
|
57
|
+
for (const cid of visible)
|
|
58
|
+
for (let p = byCid.get(cid)?.parent; p && byCid.has(p) && !shown.has(p); p = byCid.get(p).parent)
|
|
59
|
+
shown.add(p);
|
|
60
|
+
|
|
61
|
+
const wanted = new Set();
|
|
62
|
+
const out = [];
|
|
63
|
+
const contexts = [];
|
|
64
|
+
|
|
65
|
+
for (const u of units) {
|
|
66
|
+
if (!shown.has(u.cid))
|
|
67
|
+
continue;
|
|
68
|
+
const entry = {
|
|
69
|
+
cid: u.cid,
|
|
70
|
+
path: u.path,
|
|
71
|
+
label: u.label,
|
|
72
|
+
icon: u.own?.icon ?? '',
|
|
73
|
+
parent: byCid.has(u.parent) && shown.has(u.parent) ? u.parent : null,
|
|
74
|
+
rights: rel(u),
|
|
75
|
+
places: [],
|
|
76
|
+
links: [],
|
|
77
|
+
};
|
|
78
|
+
out.push(entry);
|
|
79
|
+
if (!entry.rights)
|
|
80
|
+
continue;
|
|
81
|
+
|
|
82
|
+
const { common, places } = normalizeSecurity(u.security);
|
|
83
|
+
const nodes = walkPlaces(places).map(({ node, trail }) => ({
|
|
84
|
+
key: trailKey(trail), parent: trail.length > 1 ? trailKey(trail.slice(0, -1)) : null, node,
|
|
85
|
+
}));
|
|
86
|
+
const mine = new Set(memberTrailKeys(u.security, uid));
|
|
87
|
+
const underMine = key => [...mine].some(m => key === m || key.startsWith(m + '/'));
|
|
88
|
+
const commonLinks = common.map(l => ({ id: l.id, access: l.access, scope: l.scope ?? null }));
|
|
89
|
+
const placeMenu = (n, view) => {
|
|
90
|
+
const nested = nodes.filter(x => x.key.startsWith(n.key + '/')).flatMap(x => ownLinks(x.node));
|
|
91
|
+
const own = ownLinks(n.node);
|
|
92
|
+
return mergeLinks(commonLinks, view === 'mine' || view === 'admin' ? own : readOnly(own), readOnly(nested));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
for (const n of nodes) {
|
|
96
|
+
const view = entry.rights === 'admin' ? 'admin'
|
|
97
|
+
: mine.has(n.key) ? 'mine'
|
|
98
|
+
: underMine(n.key) ? 'below'
|
|
99
|
+
: entry.rights === 'boss' ? 'boss' : null;
|
|
100
|
+
if (!view)
|
|
101
|
+
continue;
|
|
102
|
+
entry.places.push({
|
|
103
|
+
key: n.key, parent: n.parent, label: n.node.label, icon: n.node.icon, view, mine: mine.has(n.key),
|
|
104
|
+
links: placeMenu(n, view),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const wide = entry.rights === 'admin' || entry.rights === 'boss';
|
|
109
|
+
const all = nodes.flatMap(n => ownLinks(n.node));
|
|
110
|
+
entry.links = mergeLinks(commonLinks, entry.rights === 'admin' ? all : wide ? readOnly(all) : []);
|
|
111
|
+
|
|
112
|
+
for (const p of entry.places)
|
|
113
|
+
if (p.mine)
|
|
114
|
+
contexts.push({ unit: u.cid, place: p.key, kind: 'place', label: `${u.label} / ${p.label}` });
|
|
115
|
+
if (wide || !entry.places.some(p => p.mine))
|
|
116
|
+
contexts.push({ unit: u.cid, place: null, kind: 'unit', label: u.label });
|
|
117
|
+
|
|
118
|
+
for (const l of [...entry.links, ...entry.places.flatMap(p => p.links)])
|
|
119
|
+
wanted.add(l.id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { uid, units: out, contexts, classes: describeClasses(db, wanted) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Класс для меню: название, значок, тип, семейство и вложенные классы (пути). */
|
|
126
|
+
function describe(c, kids) {
|
|
127
|
+
return {
|
|
128
|
+
cid: c.cid, path: c.path, label: c.label, icon: c.own?.icon ?? '', type: c.type,
|
|
129
|
+
family: FAMILY[c.type] ?? 'other', leaf: kids.length === 0, children: kids.map(k => k.path),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Описание классов-ссылок и всего их поддерева (меню разворачивает ветки без новых запросов). */
|
|
134
|
+
function describeClasses(db, paths) {
|
|
135
|
+
const out = {};
|
|
136
|
+
const stack = [...paths];
|
|
137
|
+
let count = 0;
|
|
138
|
+
while (stack.length && count < LIMIT_CLASSES) {
|
|
139
|
+
const path = stack.pop();
|
|
140
|
+
if (out[path])
|
|
141
|
+
continue;
|
|
142
|
+
const c = getClass(db, path);
|
|
143
|
+
if (!c)
|
|
144
|
+
continue;
|
|
145
|
+
const kids = children(db, c.cid);
|
|
146
|
+
out[c.path] = describe(c, kids);
|
|
147
|
+
count++;
|
|
148
|
+
for (const k of kids)
|
|
149
|
+
stack.push(k.path);
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
@@ -192,17 +192,38 @@ export function scanTrackedFiles(rootDir = '.') {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
/**
|
|
195
|
-
* Прочитать текущую версию из package.json.
|
|
195
|
+
* Прочитать текущую версию из package.json (или #system/package-manifest.json, если в корне лежит package.json хост-проекта).
|
|
196
196
|
*/
|
|
197
197
|
export function readLocalPackageVersion(rootDir = '.') {
|
|
198
|
+
const absRoot = path.resolve(rootDir);
|
|
199
|
+
try {
|
|
200
|
+
const pkgPath = path.join(absRoot, 'package.json');
|
|
201
|
+
if (fs.existsSync(pkgPath)) {
|
|
202
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
203
|
+
if (pkg.version)
|
|
204
|
+
return String(pkg.version);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch { /* ignore */ }
|
|
198
208
|
try {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
209
|
+
const manifestPath = path.join(absRoot, MANIFEST_REL_PATH);
|
|
210
|
+
if (fs.existsSync(manifestPath)) {
|
|
211
|
+
const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
212
|
+
if (raw?.version)
|
|
213
|
+
return String(raw.version);
|
|
214
|
+
}
|
|
202
215
|
}
|
|
203
|
-
catch {
|
|
204
|
-
|
|
216
|
+
catch { /* ignore */ }
|
|
217
|
+
try {
|
|
218
|
+
const nmPkgPath = path.join(absRoot, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
219
|
+
if (fs.existsSync(nmPkgPath)) {
|
|
220
|
+
const nmPkg = JSON.parse(fs.readFileSync(nmPkgPath, 'utf8'));
|
|
221
|
+
if (nmPkg?.version)
|
|
222
|
+
return String(nmPkg.version);
|
|
223
|
+
}
|
|
205
224
|
}
|
|
225
|
+
catch { /* ignore */ }
|
|
226
|
+
return '1.0.0';
|
|
206
227
|
}
|
|
207
228
|
|
|
208
229
|
/**
|
|
@@ -428,14 +449,24 @@ export function applyUpdateFromDirectory({
|
|
|
428
449
|
const upstreamBackupRoot = path.join(absRoot, '#system', 'updates', `upstream-${targetVersion}`);
|
|
429
450
|
|
|
430
451
|
// Проверим, изменились ли dependencies в package.json
|
|
452
|
+
let oldPkg = null;
|
|
453
|
+
let newPkg = null;
|
|
431
454
|
try {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
455
|
+
if (fs.existsSync(path.join(absRoot, 'package.json')))
|
|
456
|
+
oldPkg = JSON.parse(fs.readFileSync(path.join(absRoot, 'package.json'), 'utf8'));
|
|
457
|
+
if (fs.existsSync(path.join(absSource, 'package.json')))
|
|
458
|
+
newPkg = JSON.parse(fs.readFileSync(path.join(absSource, 'package.json'), 'utf8'));
|
|
459
|
+
if (oldPkg && newPkg && JSON.stringify(oldPkg.dependencies || {}) !== JSON.stringify(newPkg.dependencies || {})) {
|
|
435
460
|
report.dependenciesChanged = true;
|
|
436
461
|
}
|
|
437
462
|
}
|
|
438
463
|
catch { /* ignore */ }
|
|
464
|
+
const isHostWrapperPkg = Boolean(
|
|
465
|
+
oldPkg && (
|
|
466
|
+
(oldPkg.name && oldPkg.name !== PACKAGE_NAME)
|
|
467
|
+
|| (!oldPkg.name && oldPkg.dependencies?.[PACKAGE_NAME])
|
|
468
|
+
)
|
|
469
|
+
);
|
|
439
470
|
|
|
440
471
|
const walkSource = (currSourceDir) => {
|
|
441
472
|
for (const entry of fs.readdirSync(currSourceDir)) {
|
|
@@ -453,7 +484,18 @@ export function applyUpdateFromDirectory({
|
|
|
453
484
|
continue;
|
|
454
485
|
|
|
455
486
|
const dstFile = path.join(absRoot, rel);
|
|
456
|
-
|
|
487
|
+
let newBuf = fs.readFileSync(srcFile);
|
|
488
|
+
if (rel === 'package.json' && isHostWrapperPkg && newPkg) {
|
|
489
|
+
const mergedPkg = {
|
|
490
|
+
...newPkg,
|
|
491
|
+
name: oldPkg.name || 'odant-work-workspace',
|
|
492
|
+
dependencies: {
|
|
493
|
+
...(newPkg.dependencies || {}),
|
|
494
|
+
...(oldPkg.dependencies?.[PACKAGE_NAME] ? { [PACKAGE_NAME]: `^${targetVersion}` } : {}),
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
newBuf = Buffer.from(JSON.stringify(mergedPkg, null, 2) + '\n', 'utf8');
|
|
498
|
+
}
|
|
457
499
|
const newHash = sha256Buffer(newBuf);
|
|
458
500
|
|
|
459
501
|
// 1. Защищённые пользовательские данные никогда не перезаписываются, если уже существуют
|
package/.vscode/launch.json
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.2.0",
|
|
3
|
-
"configurations": [
|
|
4
|
-
{
|
|
5
|
-
"type": "node",
|
|
6
|
-
"request": "launch",
|
|
7
|
-
"name": "WORK (prod)",
|
|
8
|
-
"skipFiles": [
|
|
9
|
-
"<node_internals>/**"
|
|
10
|
-
],
|
|
11
|
-
"program": "${workspaceFolder}/run.mjs",
|
|
12
|
-
"cwd": "${workspaceFolder}",
|
|
13
|
-
"env": {
|
|
14
|
-
"WORK_DEV": "false",
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"type": "node",
|
|
19
|
-
"request": "launch",
|
|
20
|
-
"name": "WORK (dev)",
|
|
21
|
-
"skipFiles": [
|
|
22
|
-
"<node_internals>/**"
|
|
23
|
-
],
|
|
24
|
-
|
|
25
|
-
"program": "${workspaceFolder}/run.mjs",
|
|
26
|
-
"cwd": "${workspaceFolder}",
|
|
27
|
-
"env": {
|
|
28
|
-
"WORK_DEV": "true",
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
}
|
package/.vscode/settings.json
DELETED
package/.vscode/tasks.json
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "2.0.0",
|
|
3
|
-
"inputs": [
|
|
4
|
-
{
|
|
5
|
-
"id": "commitMessage",
|
|
6
|
-
"type": "promptString",
|
|
7
|
-
"description": "Сообщение коммита (SVN + Git)"
|
|
8
|
-
}
|
|
9
|
-
],
|
|
10
|
-
"tasks": [
|
|
11
|
-
{
|
|
12
|
-
"label": "Publish: SVN → Git",
|
|
13
|
-
"type": "shell",
|
|
14
|
-
"command": "powershell",
|
|
15
|
-
"args": [
|
|
16
|
-
"-NoProfile",
|
|
17
|
-
"-ExecutionPolicy",
|
|
18
|
-
"Bypass",
|
|
19
|
-
"-File",
|
|
20
|
-
"${workspaceFolder}/scripts/svn-publish.ps1",
|
|
21
|
-
"-Message",
|
|
22
|
-
"${input:commitMessage}"
|
|
23
|
-
],
|
|
24
|
-
"presentation": {
|
|
25
|
-
"reveal": "always",
|
|
26
|
-
"panel": "shared"
|
|
27
|
-
},
|
|
28
|
-
"problemMatcher": []
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"label": "Sync: SVN update → Git",
|
|
32
|
-
"type": "shell",
|
|
33
|
-
"command": "powershell",
|
|
34
|
-
"args": [
|
|
35
|
-
"-NoProfile",
|
|
36
|
-
"-ExecutionPolicy",
|
|
37
|
-
"Bypass",
|
|
38
|
-
"-File",
|
|
39
|
-
"${workspaceFolder}/scripts/svn-sync.ps1"
|
|
40
|
-
],
|
|
41
|
-
"presentation": {
|
|
42
|
-
"reveal": "always",
|
|
43
|
-
"panel": "shared"
|
|
44
|
-
},
|
|
45
|
-
"problemMatcher": []
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"label": "Mirror to Git only",
|
|
49
|
-
"type": "shell",
|
|
50
|
-
"command": "powershell",
|
|
51
|
-
"args": [
|
|
52
|
-
"-NoProfile",
|
|
53
|
-
"-ExecutionPolicy",
|
|
54
|
-
"Bypass",
|
|
55
|
-
"-File",
|
|
56
|
-
"${workspaceFolder}/scripts/mirror-to-git.ps1"
|
|
57
|
-
],
|
|
58
|
-
"presentation": {
|
|
59
|
-
"reveal": "always",
|
|
60
|
-
"panel": "shared"
|
|
61
|
-
},
|
|
62
|
-
"problemMatcher": []
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
}
|