UNPKG

kdx

Version:

kintone CLI for development & deployment, with Developer Experience

358 lines (310 loc) 15.7 kB
// Copyright (c) 2020 Shellyl_N and Authors // license: MIT // https://github.com/shellyln import { promisify } from 'util'; import * as fs from 'fs'; import * as path from 'path'; import * as url from 'url'; import * as rimraf_ from 'rimraf'; import { ValidationContext } from 'tynder/modules/types'; import { getType } from 'tynder/modules/validator'; import getClient from '../client'; import { Lang } from '../../schema-types/kintone-meta'; import { MetaIndex, MetaHashIndex, MetaViewsIndex, MetaResourcesIndex } from '../../schema-types/kdx-meta'; import { hash, KdxMetaSchema as kdxSchema, validate } from '../util'; const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); const exists = promisify(fs.exists); const mkdir = promisify(fs.mkdir); const rimraf = promisify((rimraf_ as any).default || rimraf_); const kdxCtxGen: Partial<ValidationContext> = { checkAll: true, schema: kdxSchema, }; export const pullMetaInfos = async (profile: string, projectDir: string, appName: string, lang?: Lang, preview?: boolean) => { const metaIndexText = await readFile(path.join(projectDir, 'meta/meta-info.json'), { encoding: 'utf8' }); const metaIndex = validate<MetaIndex>(JSON.parse(metaIndexText), getType(kdxSchema, 'MetaIndex'), { ...kdxCtxGen }); const allAppNames = Object.keys(metaIndex.apps); const mapAppIdToName = new Map<number, string>(allAppNames.map(x => [metaIndex.apps[x][profile].appId, x])); const client = getClient(profile, metaIndex.apps[appName][profile].guestSpaceId); const appId = metaIndex.apps[appName][profile].appId; await mkdir(path.join(projectDir, 'meta/apps', appName, 'form'), { recursive: true }); await mkdir(path.join(projectDir, 'meta/apps', appName, 'acl'), { recursive: true }); let text = ''; const hashIndex: MetaHashIndex = {}; console.log(`(AppId: ${appId}) Start fetching...`); // app.json const metaApp = await client.app.getApp({ id: appId }); text = JSON.stringify(metaApp, null, 4); hashIndex['app.json'] = hash(text); console.log('Fetching app.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'app.json'), text, { encoding: 'utf8' }); console.log('Done!'); // settings.json const metaSettings = await client.app.getAppSettings({ app: appId, lang: lang, preview: preview ?? false }); text = JSON.stringify(metaSettings, null, 4); hashIndex['settings.json'] = hash(text); console.log('Fetching settings.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'settings.json'), text, { encoding: 'utf8' }); console.log('Done!'); // status.json const metaStatus = await client.app.getProcessManagement({ app: appId, lang: lang, preview: preview ?? false }); text = JSON.stringify(metaStatus, null, 4); hashIndex['status.json'] = hash(text); console.log('Fetching status.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'status.json'), text, { encoding: 'utf8' }); console.log('Done!'); // form/fields.json const metaFields = await client.app.getFormFields({ app: appId, lang: lang, preview: preview ?? false }); for (const k of Object.keys(metaFields.properties)) { const field: any = metaFields.properties[k]; if (field.type === 'REFERENCE_TABLE') { if (field?.referenceTable?.relatedApp?.app) { const n = Number.parseInt(field.referenceTable.relatedApp.app, 10); if (mapAppIdToName.has(n)) { field.referenceTable.relatedApp.app = `$appName:${mapAppIdToName.get(n)}$`; } } } else { if (field?.lookup?.relatedApp?.app) { const n = Number.parseInt(field.lookup.relatedApp.app, 10); if (mapAppIdToName.has(n)) { field.lookup.relatedApp.app = `$appName:${mapAppIdToName.get(n)}$`; } } } } text = JSON.stringify(metaFields, null, 4); hashIndex['form/fields.json'] = hash(text); console.log('Fetching form/fields.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'form/fields.json'), text, { encoding: 'utf8' }); console.log('Done!'); // form/layout.json const metaLayout = await client.app.getFormLayout({ app: appId, preview: preview ?? false }); text = JSON.stringify(metaLayout, null, 4); hashIndex['form/layout.json'] = hash(text); console.log('Fetching form/layout.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'form/layout.json'), text, { encoding: 'utf8' }); console.log('Done!'); // views.json const metaViews = await client.app.getViews({ app: appId, lang: lang, preview: preview ?? false }); text = JSON.stringify(metaViews, null, 4); hashIndex['views.json'] = hash(text); console.log('Fetching views.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'views.json'), text, { encoding: 'utf8' }); console.log('Done!'); { let viewIndex: MetaViewsIndex = {}; const viewIndexNameToEnt = new Map<string, any>(); const viewIndexNameToViewResName = new Map<string, string>(); const viewIndexPath = path.join(projectDir, 'meta/apps', appName, '_views.json'); if (await exists(viewIndexPath)) { let viewIndexText = ''; viewIndexText = await readFile(viewIndexPath, { encoding: 'utf8' }); viewIndex = validate<MetaViewsIndex>(JSON.parse(viewIndexText), getType(kdxSchema, 'MetaViewsIndex'), { ...kdxCtxGen }); } for (const viewResName of Object.keys(viewIndex)) { const entry = viewIndex[viewResName]; if (entry[profile] && entry[profile].id && entry[profile].name) { viewIndexNameToEnt.set(entry[profile].name, entry[profile]); viewIndexNameToViewResName.set(entry[profile].name, viewResName); } } for (const viewName of Object.keys(metaViews.views)) { const id = String(metaViews.views[viewName].id); const name = String(metaViews.views[viewName].name); if (viewIndexNameToEnt.has(name)) { // Update existing entry const resName = viewIndexNameToViewResName.get(name) as string; viewIndex[resName].view = { ...metaViews.views[viewName], }; delete (viewIndex[resName].view as any).id; delete (viewIndex[resName].view as any).name; const ent = viewIndexNameToEnt.get(name); ent.id = id; ent.name = metaViews.views[viewName].name; } else { // Append entry viewIndex[id] = viewIndex[id] || {}; viewIndex[id].view = { ...metaViews.views[viewName], }; delete (viewIndex[id].view as any).id; delete (viewIndex[id].view as any).name; viewIndex[id][profile] = viewIndex[id][profile] || {}; const ent = viewIndex[id][profile] = viewIndex[id][profile] || {}; ent.id = id; ent.name = metaViews.views[viewName].name; viewIndexNameToEnt.set(name, ent); } } await writeFile(viewIndexPath, JSON.stringify(viewIndex, null, 4), { encoding: 'utf8' }); const viewsDir = path.join(projectDir, 'views/apps', appName); await rimraf(viewsDir, {}); await mkdir(viewsDir, { recursive: true }); for (const viewName of Object.keys(metaViews.views)) { const id = metaViews.views[viewName].id; const name = metaViews.views[viewName].name; const html = (metaViews.views[viewName] as any).html; if (typeof html === 'string') { const resName = viewIndexNameToViewResName.get(name); await writeFile( path.join(viewsDir, `${resName ?? id}.html`), html, { encoding: 'utf8' }); } } } // acl/app.json const metaAclApp = await client.app.getAppAcl({ app: appId, preview: preview ?? false }); text = JSON.stringify(metaAclApp, null, 4); hashIndex['acl/app.json'] = hash(text); console.log('Fetching acl/app.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'acl/app.json'), text, { encoding: 'utf8' }); console.log('Done!'); // acl/field.json const metaAclField = await client.app.getFieldAcl({ app: appId, preview: preview ?? false }); text = JSON.stringify(metaAclField, null, 4); hashIndex['acl/field.json'] = hash(text); console.log('Fetching acl/field.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'acl/field.json'), text, { encoding: 'utf8' }); console.log('Done!'); // acl/record.json const metaAclRecord = await client.app.getRecordAcl({ app: appId, lang: lang, preview: preview ?? false }); text = JSON.stringify(metaAclRecord, null, 4); hashIndex['acl/record.json'] = hash(text); console.log('Fetching acl/record.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'acl/record.json'), text, { encoding: 'utf8' }); console.log('Done!'); // customize.json const metaCustomize = await client.app.getAppCustomize({ app: appId, preview: preview ?? false }); text = JSON.stringify(metaCustomize, null, 4); hashIndex['customize.json'] = hash(text); console.log('Fetching customize.json...'); await writeFile(path.join(projectDir, 'meta/apps', appName, 'customize.json'), text, { encoding: 'utf8' }); console.log('Done!'); text = JSON.stringify(hashIndex, null, 4); await writeFile(path.join(projectDir, 'meta/apps', appName, '_hash.json'), text, { encoding: 'utf8' }); { const resourceIndexPath = path.join(projectDir, 'meta/apps', appName, '_resources.json'); const resourcesDir = path.join(projectDir, 'meta/apps', appName, 'resources', profile); await rimraf(resourcesDir, {}); await mkdir(resourcesDir, { recursive: true }); const newResourceIndex: MetaResourcesIndex = { js: [], css: [], }; const pushFileResourceEntry = (target: string, fileType: string, custItem: any) => { newResourceIndex[fileType].push({ name: custItem.file.name, target: [target], file: { [profile]: `filekey:${custItem.file.fileKey}`, } }); }; const pushLinkResourceEntry = (target: string, fileType: string, custItem: any) => { newResourceIndex[fileType].push({ name: new url.URL(custItem.url).pathname.split('/').pop(), target: [target], file: { [profile]: custItem.url, } }); }; const fileKeys: string[] = []; for (const item of metaCustomize.desktop.js) { if (item.type === 'FILE') { fileKeys.push(item.file.fileKey); pushFileResourceEntry('desktop', 'js', item); } else if (item.type === 'URL') { pushLinkResourceEntry('desktop', 'js', item); } } for (const item of metaCustomize.desktop.css) { if (item.type === 'FILE') { fileKeys.push(item.file.fileKey); pushFileResourceEntry('desktop', 'css', item); } else if (item.type === 'URL') { pushLinkResourceEntry('desktop', 'css', item); } } for (const item of metaCustomize.mobile.js) { if (item.type === 'FILE') { fileKeys.push(item.file.fileKey); pushFileResourceEntry('mobile', 'js', item); } else if (item.type === 'URL') { pushLinkResourceEntry('mobile', 'js', item); } } for (const item of metaCustomize.mobile.css) { if (item.type === 'FILE') { fileKeys.push(item.file.fileKey); pushFileResourceEntry('mobile', 'css', item); } else if (item.type === 'URL') { pushLinkResourceEntry('mobile', 'css', item); } } for (const key of fileKeys) { const ab = await client.file.downloadFile({ fileKey: key }); await writeFile(path.join(resourcesDir, key), ab); } if (await exists(resourceIndexPath)) { const resourceIndexText = await readFile(resourceIndexPath, { encoding: 'utf8' }); const resourceIndex = validate<MetaResourcesIndex>(JSON.parse(resourceIndexText), getType(kdxSchema, 'MetaResourcesIndex'), { ...kdxCtxGen }); const mergeInfo = (fileType: string) => { OUTER: for (const newEnt of newResourceIndex[fileType]) { for (const origEnt of resourceIndex[fileType]) { if (! origEnt.target.includes(newEnt.target[0])) { continue; } if (newEnt.file[profile].startsWith('https:')) { if (typeof origEnt.file === 'string') { if (origEnt.file === newEnt.file[profile]) { continue OUTER; } } else { if (origEnt.file[profile] === newEnt.file[profile]) { continue OUTER; } } } else if (newEnt.name === origEnt.name) { if (typeof origEnt.file === 'string') { if (origEnt.file.startsWith('filekey:')) { origEnt.file = newEnt.file[profile]; } } else { if (origEnt.file[profile].startsWith('filekey:')) { origEnt.file[profile] = newEnt.file[profile]; } } continue OUTER; } } resourceIndex[fileType].push(newEnt); } }; mergeInfo('js'); mergeInfo('css'); text = JSON.stringify(resourceIndex, null, 4); await writeFile(resourceIndexPath, text, { encoding: 'utf8' }); } else { text = JSON.stringify(newResourceIndex, null, 4); await writeFile(resourceIndexPath, text, { encoding: 'utf8' }); } } console.log('All done!'); }