Skip to content

Commit 9d9becc

Browse files
committed
feat: Added build script to bin
1 parent 6b7aac5 commit 9d9becc

File tree

4 files changed

+223
-10
lines changed

4 files changed

+223
-10
lines changed

bin/build.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env node
2+
import { Ajv } from 'ajv';
3+
import { IpcMessageSchema, MessageStatus, ResourceSchema } from 'codify-schemas';
4+
import mergeJsonSchemas from 'merge-json-schemas';
5+
import { fork } from 'node:child_process';
6+
import fs from 'node:fs';
7+
import path from 'node:path';
8+
9+
import { SequentialPty, VerbosityLevel } from '../dist/index.js';
10+
11+
const ajv = new Ajv({
12+
strict: true
13+
});
14+
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
15+
16+
function sendMessageAndAwaitResponse(process, message) {
17+
return new Promise((resolve, reject) => {
18+
process.on('message', (response) => {
19+
if (!ipcMessageValidator(response)) {
20+
throw new Error(`Invalid message from plugin. ${JSON.stringify(message, null, 2)}`);
21+
}
22+
23+
// Wait for the message response. Other messages such as sudoRequest may be sent before the response returns
24+
if (response.cmd === message.cmd + '_Response') {
25+
if (response.status === MessageStatus.SUCCESS) {
26+
resolve(response.data)
27+
} else {
28+
reject(new Error(String(response.data)))
29+
}
30+
}
31+
});
32+
33+
// Send message last to ensure listeners are all registered
34+
process.send(message);
35+
});
36+
}
37+
38+
function fetchDocumentationMaps() {
39+
console.log('Building documentation...');
40+
41+
const results = new Map();
42+
const resourcesPath = path.resolve(process.cwd(), 'src', 'resources');
43+
const resourcesDir = fs.readdirSync(resourcesPath);
44+
45+
for (const resource of resourcesDir) {
46+
const resourcePath = path.join(resourcesPath, resource);
47+
if (!isDirectory(resourcePath)) continue;
48+
49+
const contents = fs.readdirSync(resourcePath);
50+
const isGroup = contents.some((content) => isDirectory(path.join(resourcePath, content)));
51+
const isAllDir = contents.every((content) => isDirectory(path.join(resourcePath, content)));
52+
53+
if (isGroup && !isAllDir) {
54+
throw new Error(`Documentation groups must only contain directories. ${resourcePath} does not`);
55+
}
56+
57+
if (!isGroup) {
58+
if (contents.includes('README.md')) {
59+
results.set(resource, resource);
60+
}
61+
} else {
62+
for (const innerDir of contents) {
63+
const innerDirReadme = path.join(resourcePath, innerDir, 'README.md');
64+
if (isFile(innerDirReadme)) {
65+
results.set(innerDir, path.relative('./src/resources', path.join(resourcePath, innerDir)));
66+
}
67+
}
68+
}
69+
}
70+
71+
return results;
72+
}
73+
74+
function isDirectory(path) {
75+
try {
76+
return fs.statSync(path).isDirectory();
77+
} catch {
78+
return false;
79+
}
80+
}
81+
82+
function isFile(path) {
83+
try {
84+
return fs.statSync(path).isFile();
85+
} catch {
86+
return false;
87+
}
88+
}
89+
90+
VerbosityLevel.set(3);
91+
const $ = new SequentialPty();
92+
93+
await $.spawn('rm -rf ./dist')
94+
await $.spawn('npm run rollup -- -f es', { interactive: true });
95+
96+
const plugin = fork(
97+
'./dist/index.js',
98+
[],
99+
{
100+
// Use default true to test plugins in secure mode (un-able to request sudo directly)
101+
detached: true,
102+
env: { ...process.env },
103+
execArgv: ['--import', 'tsx/esm'],
104+
},
105+
)
106+
107+
const initializeResult = await sendMessageAndAwaitResponse(plugin, {
108+
cmd: 'initialize',
109+
data: {}
110+
})
111+
112+
const { resourceDefinitions } = initializeResult;
113+
const resourceTypes = resourceDefinitions.map((i) => i.type);
114+
const resourceInfoMap = new Map();
115+
116+
const schemasMap = new Map()
117+
for (const type of resourceTypes) {
118+
const resourceInfo = await sendMessageAndAwaitResponse(plugin, {
119+
cmd: 'getResourceInfo',
120+
data: { type }
121+
})
122+
123+
schemasMap.set(type, resourceInfo.schema);
124+
resourceInfoMap.set(type, resourceInfo);
125+
}
126+
127+
console.log(resourceInfoMap);
128+
129+
const mergedSchemas = [...schemasMap.entries()].map(([type, schema]) => {
130+
// const resolvedSchema = await $RefParser.dereference(schema)
131+
const resourceSchema = JSON.parse(JSON.stringify(ResourceSchema));
132+
133+
delete resourceSchema.$id;
134+
delete resourceSchema.$schema;
135+
delete resourceSchema.title;
136+
delete resourceSchema.oneOf;
137+
delete resourceSchema.properties.type;
138+
139+
if (schema) {
140+
delete schema.$id;
141+
delete schema.$schema;
142+
delete schema.title;
143+
delete schema.oneOf;
144+
}
145+
146+
return mergeJsonSchemas([schema ?? {}, resourceSchema, { properties: { type: { const: type, type: 'string' } } }]);
147+
});
148+
149+
150+
await $.spawn('rm -rf ./dist')
151+
await $.spawn('npm run rollup', { interactive: true }); // re-run rollup without building for es
152+
153+
console.log('Generated JSON Schemas for all resources')
154+
155+
const distFolder = path.resolve(process.cwd(), 'dist');
156+
const schemaOutputPath = path.resolve(distFolder, 'schemas.json');
157+
fs.writeFileSync(schemaOutputPath, JSON.stringify(mergedSchemas, null, 2));
158+
159+
console.log('Successfully wrote schema to ./dist/schemas.json');
160+
161+
const documentationMap = fetchDocumentationMaps();
162+
163+
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
164+
165+
fs.writeFileSync('./dist/manifest.json', JSON.stringify({
166+
name: packageJson.name,
167+
version: packageJson.version,
168+
description: packageJson.description,
169+
resources: [...resourceInfoMap.values()].map((info) => ({
170+
type: info.type,
171+
description: info.description ?? info.schema?.description,
172+
sensitiveParameters: info.sensitiveParameters,
173+
schema: info.schema,
174+
operatingSystems: info.operatingSystems,
175+
documentationKey: documentationMap.get(info.type),
176+
})),
177+
}, null, 2), 'utf8');
178+
179+
for (const key of documentationMap.values()) {
180+
fs.mkdirSync(path.join('dist', 'documentation', key), { recursive: true })
181+
182+
fs.copyFileSync(
183+
path.resolve(path.join('src', 'resources', key, 'README.md')),
184+
path.resolve(path.join('dist', 'documentation', key, 'README.md')),
185+
);
186+
}
187+
188+
plugin.kill(9);
189+
process.exit(0);

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codify-plugin-lib",
3-
"version": "1.0.182-beta46",
3+
"version": "1.0.182-beta55",
44
"description": "Library plugin library",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -11,7 +11,7 @@
1111
"prepublishOnly": "tsc"
1212
},
1313
"bin": {
14-
"codify-deploy": "./dist/bin/deploy-plugin.js"
14+
"codify-build": "./bin/build.js"
1515
},
1616
"keywords": [],
1717
"author": "",
@@ -22,7 +22,7 @@
2222
"ajv": "^8.12.0",
2323
"ajv-formats": "^2.1.1",
2424
"clean-deep": "^3.4.0",
25-
"codify-schemas": "1.0.86-beta7",
25+
"codify-schemas": "1.0.86-beta10",
2626
"lodash.isequal": "^4.5.0",
2727
"nanoid": "^5.0.9",
2828
"strip-ansi": "^7.1.0",

rollup.config.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import commonjs from '@rollup/plugin-commonjs';
2+
import json from '@rollup/plugin-json';
3+
import nodeResolve from '@rollup/plugin-node-resolve';
4+
import terser from '@rollup/plugin-terser';
5+
import typescript from '@rollup/plugin-typescript';
6+
7+
export default {
8+
input: 'src/index.ts',
9+
output: {
10+
dir: 'dist',
11+
format: 'cjs',
12+
inlineDynamicImports: true,
13+
},
14+
external: ['@homebridge/node-pty-prebuilt-multiarch'],
15+
plugins: [
16+
json(),
17+
nodeResolve({ exportConditions: ['node'] }),
18+
typescript({
19+
exclude: ['**/*.test.ts', '**/*.d.ts', 'test']
20+
}),
21+
commonjs(),
22+
terser()
23+
]
24+
}

0 commit comments

Comments
 (0)