Skip to content

Commit 04fa4cb

Browse files
committed
feat: Fixed up build script and added publish script
1 parent addc754 commit 04fa4cb

File tree

4 files changed

+604
-174
lines changed

4 files changed

+604
-174
lines changed

bin/build.js

100644100755
Lines changed: 189 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,57 @@
11
#!/usr/bin/env node
22
import { IpcMessageSchema, MessageStatus, ResourceSchema } from '@codifycli/schemas';
3+
import commonjs from '@rollup/plugin-commonjs';
4+
import json from '@rollup/plugin-json';
5+
import nodeResolve from '@rollup/plugin-node-resolve';
6+
import typescript from '@rollup/plugin-typescript';
37
import { Ajv } from 'ajv';
48
import mergeJsonSchemas from 'merge-json-schemas';
59
import { fork } from 'node:child_process';
6-
import fs from 'node:fs';
10+
import fs from 'node:fs/promises';
711
import path from 'node:path';
12+
import { rollup } from 'rollup';
13+
14+
const rollupConfig = {
15+
input: 'src/index.ts',
16+
output: {
17+
dir: 'dist',
18+
format: 'cjs',
19+
inlineDynamicImports: true,
20+
},
21+
treeshake: true,
22+
external: ['@homebridge/node-pty-prebuilt-multiarch'],
23+
plugins: [
24+
json(),
25+
nodeResolve({ exportConditions: ['node'] }),
26+
typescript({
27+
exclude: ['**/*.test.ts', '**/*.d.ts', 'test', 'bin']
28+
}),
29+
commonjs(),
30+
// terser()
31+
]
32+
};
833

9-
import { SequentialPty, VerbosityLevel } from '../dist/index.js';
1034

1135
const ajv = new Ajv({
1236
strict: true
1337
});
1438
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
1539

40+
async function rollupProject() {
41+
await fs.mkdir('./dist', { recursive: true });
42+
43+
const bundle = await rollup(rollupConfig);
44+
const { output } = await bundle.generate({ dir: 'dist', format: 'es' })
45+
46+
for (const a of output) {
47+
if (a.type !== 'asset') {
48+
await fs.writeFile(path.join('dist', a.fileName), a.code);
49+
}
50+
}
51+
52+
await bundle.close();
53+
}
54+
1655
function sendMessageAndAwaitResponse(process, message) {
1756
return new Promise((resolve, reject) => {
1857
process.on('message', (response) => {
@@ -35,39 +74,50 @@ function sendMessageAndAwaitResponse(process, message) {
3574
});
3675
}
3776

38-
function fetchDocumentationMaps() {
77+
async function buildDocumentation() {
3978
console.log('Building documentation...');
4079

4180
const results = new Map();
4281
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;
4882

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)));
83+
// Helper function to recursively find README files
84+
async function findReadmeFiles(dir, relativePath = '') {
85+
const entries = await fs.readdir(dir, { withFileTypes: true });
86+
87+
for (const entry of entries) {
88+
const fullPath = path.join(dir, entry.name);
89+
const currentRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
90+
91+
if (entry.isDirectory()) {
92+
// Recurse into subdirectories
93+
await findReadmeFiles(fullPath, currentRelativePath);
94+
} else if (entry.isFile() && (entry.name === 'README.md' || entry.name === 'README.mdx')) {
95+
// Found a README file - determine its output path
96+
const sourceFile = path.join(dir, entry.name);
97+
const dirRelativePath = path.dirname(currentRelativePath);
98+
99+
let outputPath;
100+
if (relativePath === '') {
101+
// Root README.md in /src/resources -> /dist/docs/index.md
102+
outputPath = 'index.md';
103+
} else if (dirRelativePath === '.') {
104+
// One level deep: /src/resources/git/README.md -> /dist/docs/resources/git.md
105+
outputPath = path.join('resources', path.basename(currentRelativePath, path.extname(currentRelativePath)) + '.md');
106+
} else {
107+
// Deeper nesting: maintain parent folders
108+
// /src/resources/package-managers/homebrew/README.md -> /dist/docs/resources/package-managers/homebrew.md
109+
const parentPath = path.dirname(dirRelativePath);
110+
const fileName = path.basename(dirRelativePath);
111+
outputPath = path.join('resources', parentPath, fileName + '.md');
66112
}
113+
114+
results.set(sourceFile, outputPath);
67115
}
68116
}
69117
}
70118

119+
await findReadmeFiles(resourcesPath);
120+
71121
return results;
72122
}
73123

@@ -87,103 +137,136 @@ function isFile(path) {
87137
}
88138
}
89139

90-
VerbosityLevel.set(3);
91-
const $ = new SequentialPty();
140+
async function main() {
141+
await fs.rm('./dist', { recursive: true, force: true });
92142

93-
await $.spawn('rm -rf ./dist')
94-
await $.spawn('npm run rollup -- -f es', { interactive: true });
143+
await fs.mkdir('./dist');
144+
await rollupProject();
95145

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-
}
146+
const plugin = fork(
147+
'./dist/index.js',
148+
[],
149+
{
150+
// Use default true to test plugins in secure mode (un-able to request sudo directly)
151+
detached: true,
152+
env: { ...process.env },
153+
execArgv: ['--import', 'tsx/esm'],
154+
},
155+
)
126156

127-
console.log(resourceInfoMap);
157+
try {
128158

129-
const mergedSchemas = [...schemasMap.entries()].map(([type, schema]) => {
130-
// const resolvedSchema = await $RefParser.dereference(schema)
131-
const resourceSchema = JSON.parse(JSON.stringify(ResourceSchema));
159+
const initializeResult = await sendMessageAndAwaitResponse(plugin, {
160+
cmd: 'initialize',
161+
data: {}
162+
})
132163

133-
delete resourceSchema.$id;
134-
delete resourceSchema.$schema;
135-
delete resourceSchema.title;
136-
delete resourceSchema.oneOf;
137-
delete resourceSchema.properties.type;
164+
const {resourceDefinitions} = initializeResult;
165+
const resourceTypes = resourceDefinitions.map((i) => i.type);
166+
const resourceInfoMap = new Map();
138167

139-
if (schema) {
140-
delete schema.$id;
141-
delete schema.$schema;
142-
delete schema.title;
143-
delete schema.oneOf;
144-
}
168+
const schemasMap = new Map()
169+
for (const type of resourceTypes) {
170+
const resourceInfo = await sendMessageAndAwaitResponse(plugin, {
171+
cmd: 'getResourceInfo',
172+
data: {type}
173+
})
145174

146-
return mergeJsonSchemas([schema ?? {}, resourceSchema, { properties: { type: { const: type, type: 'string' } } }]);
147-
});
175+
schemasMap.set(type, resourceInfo.schema);
176+
resourceInfoMap.set(type, resourceInfo);
177+
}
178+
179+
console.log(resourceInfoMap);
180+
181+
const mergedSchemas = [...schemasMap.entries()].map(([type, schema]) => {
182+
// const resolvedSchema = await $RefParser.dereference(schema)
183+
const resourceSchema = JSON.parse(JSON.stringify(ResourceSchema));
184+
185+
delete resourceSchema.$id;
186+
delete resourceSchema.$schema;
187+
delete resourceSchema.title;
188+
delete resourceSchema.oneOf;
189+
delete resourceSchema.properties.type;
190+
191+
if (schema) {
192+
delete schema.$id;
193+
delete schema.$schema;
194+
delete schema.title;
195+
delete schema.oneOf;
196+
}
197+
198+
return mergeJsonSchemas([schema ?? {}, resourceSchema, {properties: {type: {const: type, type: 'string'}}}]);
199+
});
148200

149201

150-
await $.spawn('rm -rf ./dist')
151-
await $.spawn('npm run rollup', { interactive: true }); // re-run rollup without building for es
202+
await fs.rm('./dist', {recursive: true, force: true});
203+
await rollupProject();
152204

153-
console.log('Generated JSON Schemas for all resources')
205+
console.log('Generated JSON Schemas for all resources')
154206

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));
207+
const distFolder = path.resolve(process.cwd(), 'dist');
208+
const schemaOutputPath = path.resolve(distFolder, 'schemas.json');
209+
await fs.writeFile(schemaOutputPath, JSON.stringify(mergedSchemas, null, 2));
158210

159-
console.log('Successfully wrote schema to ./dist/schemas.json');
211+
console.log('Successfully wrote schema to ./dist/schemas.json');
160212

161-
const documentationMap = fetchDocumentationMaps();
213+
const documentationMap = await buildDocumentation();
214+
console.log('Documentation Map:', documentationMap);
162215

163-
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
216+
// Build reverse map for resource type -> documentation path
217+
const resourceTypeToDocPath = new Map();
218+
for (const [sourceFile, outputPath] of documentationMap.entries()) {
219+
// Extract resource type from source file path
220+
// e.g., /src/resources/git/README.md -> git
221+
// e.g., /src/resources/package-managers/homebrew/README.md -> homebrew
222+
const relativePath = path.relative(path.resolve(process.cwd(), 'src', 'resources'), sourceFile);
223+
const parts = relativePath.split(path.sep);
164224

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');
225+
// Remove README.md/README.mdx from the end
226+
parts.pop();
178227

179-
for (const key of documentationMap.values()) {
180-
fs.mkdirSync(path.join('dist', 'documentation', key), { recursive: true })
228+
if (parts.length > 0) {
229+
// Use the last directory name as the resource type
230+
const resourceType = parts[parts.length - 1];
231+
resourceTypeToDocPath.set(resourceType, outputPath);
232+
}
233+
}
181234

182-
fs.copyFileSync(
183-
path.resolve(path.join('src', 'resources', key, 'README.md')),
184-
path.resolve(path.join('dist', 'documentation', key, 'README.md')),
185-
);
235+
const packageJson = JSON.parse(await fs.readFile('./package.json', 'utf8'));
236+
237+
await fs.writeFile('./dist/manifest.json', JSON.stringify({
238+
name: packageJson.name,
239+
version: packageJson.version,
240+
description: packageJson.description,
241+
resources: [...resourceInfoMap.values()].map((info) => ({
242+
type: info.type,
243+
description: info.description ?? info.schema?.description,
244+
sensitiveParameters: info.sensitiveParameters,
245+
schema: info.schema,
246+
operatingSystems: info.operatingSystems,
247+
documentationKey: resourceTypeToDocPath.get(info.type),
248+
})),
249+
}, null, 2), 'utf8');
250+
251+
// Copy documentation files to /dist/docs
252+
const docsPath = path.join('dist', 'docs');
253+
await fs.mkdir(docsPath, { recursive: true });
254+
255+
for (const [sourceFile, outputPath] of documentationMap.entries()) {
256+
const destFile = path.join(docsPath, outputPath);
257+
const destDir = path.dirname(destFile);
258+
259+
await fs.mkdir(destDir, { recursive: true });
260+
await fs.copyFile(sourceFile, destFile);
261+
262+
console.log(`Copied ${sourceFile} -> ${destFile}`);
263+
}
264+
} catch(e) {
265+
console.error(e);
266+
} finally {
267+
plugin.kill(9);
268+
process.exit(0);
269+
}
186270
}
187271

188-
plugin.kill(9);
189-
process.exit(0);
272+
main();

0 commit comments

Comments
 (0)