@gradial/aci 0.1.1 → 0.1.2
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/README.md +46 -1
- package/bin/aci +0 -0
- package/bin/aci.js +139 -27
- package/dist/assets/index.d.ts +2 -0
- package/dist/assets/index.js +2 -0
- package/dist/astro/index.js +4 -4
- package/dist/block-ref.d.ts +34 -0
- package/dist/block-ref.js +34 -0
- package/dist/define-component.d.ts +1 -0
- package/dist/define-component.js +1 -0
- package/dist/define-layout.d.ts +1 -0
- package/dist/define-layout.js +1 -0
- package/dist/dev/index.d.ts +6 -0
- package/dist/dev/index.js +66 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/next/asset-route.d.ts +9 -0
- package/dist/next/asset-route.js +15 -0
- package/dist/next/config.d.ts +2 -10
- package/dist/next/config.js +5 -2
- package/dist/next/server.d.ts +4 -0
- package/dist/next/server.js +53 -0
- package/dist/providers/s3.d.ts +2 -0
- package/dist/providers/s3.js +13 -1
- package/dist/react/GradialMedia.d.ts +24 -0
- package/dist/react/GradialMedia.js +31 -0
- package/dist/react/GradialPicture.d.ts +14 -0
- package/dist/react/GradialPicture.js +30 -0
- package/dist/react/GradialVideoPlayer.d.ts +13 -0
- package/dist/react/GradialVideoPlayer.js +28 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +3 -0
- package/dist/sveltekit/index.js +6 -1
- package/dist/types/component.d.ts +8 -3
- package/dist/types/image.d.ts +2 -2
- package/dist/types/image.js +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/media.d.ts +69 -0
- package/dist/types/media.js +86 -0
- package/dist/types/video.d.ts +70 -0
- package/dist/types/video.js +22 -0
- package/package.json +13 -9
- package/src/cli/compile-registry.mjs +142 -1
- package/src/cli/validate-content.mjs +98 -0
- package/src/cli/css-stub-loader.mjs +0 -27
- package/src/cli/generate-registry.mjs +0 -72
|
@@ -5,6 +5,16 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { GradialImageSchema } from '../types/image.ts';
|
|
7
7
|
|
|
8
|
+
// Shim React global so JSX-containing component files can be loaded.
|
|
9
|
+
// The compile-registry only extracts contract metadata (schemas, imageSlots)
|
|
10
|
+
// and never renders components, but importing .tsx files requires the JSX
|
|
11
|
+
// runtime to be resolvable. Next.js uses jsx: "preserve" which leaves React
|
|
12
|
+
// references in the transpiled output.
|
|
13
|
+
try {
|
|
14
|
+
const React = await import('react');
|
|
15
|
+
if (!globalThis.React) globalThis.React = React;
|
|
16
|
+
} catch { /* react not available — component files without JSX will still work */ }
|
|
17
|
+
|
|
8
18
|
const args = parseArgs(process.argv.slice(2));
|
|
9
19
|
|
|
10
20
|
try {
|
|
@@ -62,6 +72,19 @@ async function compileRegistry(options) {
|
|
|
62
72
|
const seenLayouts = new Set();
|
|
63
73
|
const layoutEntries = layouts.map((layout, index) => validateLayout(layout, index, seenLayouts));
|
|
64
74
|
|
|
75
|
+
// Post-compilation: detect nested block patterns in JSON schemas and
|
|
76
|
+
// validate that referenced component names exist in the registry.
|
|
77
|
+
const knownNames = new Set(componentEntries.map((c) => c.name));
|
|
78
|
+
for (const entry of componentEntries) {
|
|
79
|
+
const schemaPath = path.join(outDir, entry.schemaFile);
|
|
80
|
+
const schemaRaw = await import('node:fs/promises').then((fs) => fs.readFile(schemaPath, 'utf-8'));
|
|
81
|
+
const schema = JSON.parse(schemaRaw);
|
|
82
|
+
const nestedBlocks = collectNestedBlockRefs(schema, knownNames, entry.name);
|
|
83
|
+
if (nestedBlocks.length > 0) {
|
|
84
|
+
entry.nestedBlocks = nestedBlocks;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
const registry = {
|
|
66
89
|
manifestVersion: '1',
|
|
67
90
|
generatedAt: new Date().toISOString(),
|
|
@@ -192,6 +215,14 @@ function collectGradialImageFieldsFromSchema(schema, prefix, out) {
|
|
|
192
215
|
return;
|
|
193
216
|
}
|
|
194
217
|
|
|
218
|
+
const unionOptions = unionOptionSchemas(schema);
|
|
219
|
+
if (unionOptions) {
|
|
220
|
+
for (const option of unionOptions) {
|
|
221
|
+
collectGradialImageFieldsFromSchema(option, prefix, out);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
195
226
|
const def = unwrapSchema(schema)?._def;
|
|
196
227
|
const shape = typeof def?.shape === 'function' ? def.shape() : def?.shape;
|
|
197
228
|
if (!shape || typeof shape !== 'object') {
|
|
@@ -204,6 +235,14 @@ function collectGradialImageFieldsFromSchema(schema, prefix, out) {
|
|
|
204
235
|
}
|
|
205
236
|
}
|
|
206
237
|
|
|
238
|
+
function unionOptionSchemas(schema) {
|
|
239
|
+
const def = unwrapSchema(schema)?._def;
|
|
240
|
+
if (!def) return null;
|
|
241
|
+
if (Array.isArray(def.options)) return def.options;
|
|
242
|
+
if (def.options instanceof Map) return Array.from(def.options.values());
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
207
246
|
function arrayItemSchema(schema) {
|
|
208
247
|
let current = schema;
|
|
209
248
|
const seen = new Set();
|
|
@@ -231,10 +270,13 @@ function isGradialImageSchema(schema) {
|
|
|
231
270
|
target: 'draft-2020-12',
|
|
232
271
|
unrepresentable: 'throw',
|
|
233
272
|
});
|
|
273
|
+
const sources = compiled?.properties?.sources;
|
|
274
|
+
const sourcesIsArray = sources?.type === 'array'
|
|
275
|
+
|| (Array.isArray(sources?.anyOf) && sources.anyOf.some((s) => s?.type === 'array'));
|
|
234
276
|
return compiled?.type === 'object'
|
|
235
277
|
&& compiled?.properties?.$type?.const === 'gradial.image'
|
|
236
278
|
&& compiled?.properties?.fallback?.type === 'object'
|
|
237
|
-
&&
|
|
279
|
+
&& sourcesIsArray;
|
|
238
280
|
} catch {
|
|
239
281
|
return false;
|
|
240
282
|
}
|
|
@@ -344,6 +386,105 @@ function childSchemas(def) {
|
|
|
344
386
|
return children;
|
|
345
387
|
}
|
|
346
388
|
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Nested block detection — walks generated JSON schemas to find blockRef
|
|
391
|
+
// patterns (objects with `component` enum + `props`) and validates that
|
|
392
|
+
// referenced component names exist in the registry.
|
|
393
|
+
// ---------------------------------------------------------------------------
|
|
394
|
+
|
|
395
|
+
function collectNestedBlockRefs(schema, knownNames, componentName) {
|
|
396
|
+
const results = [];
|
|
397
|
+
_walkSchemaForBlockRefs(schema, schema, '', results, knownNames, componentName);
|
|
398
|
+
return results;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function _walkSchemaForBlockRefs(node, rootSchema, currentPath, results, knownNames, componentName) {
|
|
402
|
+
if (!node || typeof node !== 'object') return;
|
|
403
|
+
|
|
404
|
+
// Resolve $ref
|
|
405
|
+
if (node.$ref) {
|
|
406
|
+
const resolved = resolveJsonSchemaRef(node.$ref, rootSchema);
|
|
407
|
+
if (resolved) _walkSchemaForBlockRefs(resolved, rootSchema, currentPath, results, knownNames, componentName);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check if this object matches the blockRef signature:
|
|
412
|
+
// { type: "object", properties: { id, component, props } }
|
|
413
|
+
if (isBlockRefSchema(node)) {
|
|
414
|
+
const allow = extractBlockRefAllow(node, rootSchema);
|
|
415
|
+
// Validate allowed names exist
|
|
416
|
+
for (const name of allow) {
|
|
417
|
+
if (!knownNames.has(name)) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`${componentName}: nested block allowlist references unknown component "${name}" at ${currentPath || '(root)'}`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
results.push({
|
|
424
|
+
path: currentPath || '(root)',
|
|
425
|
+
...(allow.length > 0 ? { allow } : {}),
|
|
426
|
+
});
|
|
427
|
+
return; // Don't recurse into the block schema itself
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Recurse into object properties
|
|
431
|
+
if (node.properties) {
|
|
432
|
+
for (const [key, propSchema] of Object.entries(node.properties)) {
|
|
433
|
+
const nextPath = currentPath ? `${currentPath}.${key}` : key;
|
|
434
|
+
_walkSchemaForBlockRefs(propSchema, rootSchema, nextPath, results, knownNames, componentName);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Recurse into array items
|
|
439
|
+
if (node.items) {
|
|
440
|
+
_walkSchemaForBlockRefs(node.items, rootSchema, `${currentPath}[]`, results, knownNames, componentName);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Recurse into anyOf / allOf / oneOf
|
|
444
|
+
for (const keyword of ['anyOf', 'allOf', 'oneOf']) {
|
|
445
|
+
if (Array.isArray(node[keyword])) {
|
|
446
|
+
for (const sub of node[keyword]) {
|
|
447
|
+
_walkSchemaForBlockRefs(sub, rootSchema, currentPath, results, knownNames, componentName);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function isBlockRefSchema(node) {
|
|
454
|
+
if (node.type !== 'object' || !node.properties) return false;
|
|
455
|
+
const props = node.properties;
|
|
456
|
+
// Must have component and props fields
|
|
457
|
+
if (!props.component || !props.props) return false;
|
|
458
|
+
// component must be a string or enum
|
|
459
|
+
const comp = props.component;
|
|
460
|
+
if (comp.type !== 'string' && !Array.isArray(comp.enum)) return false;
|
|
461
|
+
// props must be an object (record)
|
|
462
|
+
if (props.props.type !== 'object') return false;
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function extractBlockRefAllow(node, rootSchema) {
|
|
467
|
+
let comp = node.properties.component;
|
|
468
|
+
// Resolve $ref on the component field
|
|
469
|
+
if (comp.$ref) {
|
|
470
|
+
comp = resolveJsonSchemaRef(comp.$ref, rootSchema) ?? comp;
|
|
471
|
+
}
|
|
472
|
+
if (Array.isArray(comp.enum)) {
|
|
473
|
+
return comp.enum.filter((v) => typeof v === 'string');
|
|
474
|
+
}
|
|
475
|
+
return [];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function resolveJsonSchemaRef(ref, rootSchema) {
|
|
479
|
+
if (!ref || !ref.startsWith('#/')) return null;
|
|
480
|
+
const parts = ref.slice(2).split('/');
|
|
481
|
+
let current = rootSchema;
|
|
482
|
+
for (const part of parts) {
|
|
483
|
+
current = current?.[part];
|
|
484
|
+
}
|
|
485
|
+
return current ?? null;
|
|
486
|
+
}
|
|
487
|
+
|
|
347
488
|
function parseArgs(argv) {
|
|
348
489
|
const out = {};
|
|
349
490
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -53,8 +53,10 @@ async function validateContent(options) {
|
|
|
53
53
|
const registry = JSON.parse(registryRaw);
|
|
54
54
|
|
|
55
55
|
// Build schema map: component name → JSON Schema object
|
|
56
|
+
// Build nested blocks map: component name → array of { path, allow? }
|
|
56
57
|
const schemaMap = new Map();
|
|
57
58
|
const knownComponents = new Set();
|
|
59
|
+
const nestedBlocksMap = new Map();
|
|
58
60
|
for (const component of registry.components) {
|
|
59
61
|
knownComponents.add(component.name);
|
|
60
62
|
if (component.schemaFile) {
|
|
@@ -62,6 +64,9 @@ async function validateContent(options) {
|
|
|
62
64
|
const schemaRaw = await readFile(schemaPath, 'utf-8');
|
|
63
65
|
schemaMap.set(component.name, JSON.parse(schemaRaw));
|
|
64
66
|
}
|
|
67
|
+
if (Array.isArray(component.nestedBlocks) && component.nestedBlocks.length > 0) {
|
|
68
|
+
nestedBlocksMap.set(component.name, component.nestedBlocks);
|
|
69
|
+
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
// Find all compiled route files
|
|
@@ -128,6 +133,19 @@ async function validateContent(options) {
|
|
|
128
133
|
message: issue.message,
|
|
129
134
|
});
|
|
130
135
|
}
|
|
136
|
+
|
|
137
|
+
// Recursively validate nested blocks (blockRef patterns)
|
|
138
|
+
const nestedIssues = validateNestedBlocks(
|
|
139
|
+
block.component, props, knownComponents, schemaMap, nestedBlocksMap,
|
|
140
|
+
);
|
|
141
|
+
for (const issue of nestedIssues) {
|
|
142
|
+
collector.push({
|
|
143
|
+
page: relativePath,
|
|
144
|
+
blockId: block.id ?? '<unknown>',
|
|
145
|
+
component: block.component,
|
|
146
|
+
...issue,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
|
|
@@ -341,6 +359,86 @@ function resolveRef(ref, rootSchema) {
|
|
|
341
359
|
// Content helpers
|
|
342
360
|
// ---------------------------------------------------------------------------
|
|
343
361
|
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Nested block validation — recursively validates blocks inside blockRef slots
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
function validateNestedBlocks(parentComponent, props, knownComponents, schemaMap, nestedBlocksMap) {
|
|
367
|
+
const issues = [];
|
|
368
|
+
// Find all block-shaped arrays in props by walking the data
|
|
369
|
+
const nestedBlocks = findNestedBlockArrays(props);
|
|
370
|
+
for (const { path: blockPath, blocks } of nestedBlocks) {
|
|
371
|
+
for (const [index, nestedBlock] of blocks.entries()) {
|
|
372
|
+
if (!nestedBlock || typeof nestedBlock !== 'object') continue;
|
|
373
|
+
const childComponent = nestedBlock.component;
|
|
374
|
+
if (!childComponent || typeof childComponent !== 'string') continue;
|
|
375
|
+
|
|
376
|
+
const nestedPath = `${blockPath}[${index}]`;
|
|
377
|
+
|
|
378
|
+
// Check nested component exists in registry
|
|
379
|
+
if (!knownComponents.has(childComponent)) {
|
|
380
|
+
issues.push({
|
|
381
|
+
path: `${nestedPath}.component`,
|
|
382
|
+
message: `${parentComponent} → ${nestedPath}: unknown nested component "${childComponent}"`,
|
|
383
|
+
});
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Validate nested block's props against its own schema
|
|
388
|
+
const childSchema = schemaMap.get(childComponent);
|
|
389
|
+
if (!childSchema) continue;
|
|
390
|
+
|
|
391
|
+
const childProps = nestedBlock.props ?? {};
|
|
392
|
+
const childIssues = validateJsonSchema(childProps, childSchema, `${nestedPath}.props`);
|
|
393
|
+
for (const issue of childIssues) {
|
|
394
|
+
issues.push({
|
|
395
|
+
nestedComponent: childComponent,
|
|
396
|
+
path: issue.path,
|
|
397
|
+
message: `${parentComponent} → ${childComponent}: ${issue.message}`,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Recurse further if the nested component itself has nested blocks
|
|
402
|
+
const deeperIssues = validateNestedBlocks(
|
|
403
|
+
childComponent, childProps, knownComponents, schemaMap, nestedBlocksMap,
|
|
404
|
+
);
|
|
405
|
+
issues.push(...deeperIssues);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return issues;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Walks a props object looking for arrays of block-shaped objects
|
|
413
|
+
* (objects with `component` + `props` fields).
|
|
414
|
+
*/
|
|
415
|
+
function findNestedBlockArrays(obj, prefix = 'props') {
|
|
416
|
+
const results = [];
|
|
417
|
+
if (!obj || typeof obj !== 'object') return results;
|
|
418
|
+
|
|
419
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
420
|
+
const currentPath = `${prefix}.${key}`;
|
|
421
|
+
if (Array.isArray(value)) {
|
|
422
|
+
// Check if array contains block-shaped objects
|
|
423
|
+
const hasBlocks = value.some(
|
|
424
|
+
(item) => item && typeof item === 'object' && typeof item.component === 'string' && 'props' in item,
|
|
425
|
+
);
|
|
426
|
+
if (hasBlocks) {
|
|
427
|
+
results.push({ path: currentPath, blocks: value });
|
|
428
|
+
}
|
|
429
|
+
// Also recurse into array items that are objects (for nested structures like tabs[].blocks[])
|
|
430
|
+
for (const item of value) {
|
|
431
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
432
|
+
results.push(...findNestedBlockArrays(item, currentPath + '[]'));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
436
|
+
results.push(...findNestedBlockArrays(value, currentPath));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return results;
|
|
440
|
+
}
|
|
441
|
+
|
|
344
442
|
function collectBlocks(pageData) {
|
|
345
443
|
const blocks = [];
|
|
346
444
|
if (pageData.regions && typeof pageData.regions === 'object') {
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node.js custom loader that stubs CSS module imports.
|
|
3
|
-
* Used by CLI tools (compile-registry, validate-content) that need to import
|
|
4
|
-
* component files without actually loading CSS.
|
|
5
|
-
*
|
|
6
|
-
* Usage: node --import tsx --loader ./css-stub-loader.mjs <script>
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const CSS_EXTENSIONS = ['.css', '.module.css', '.scss', '.module.scss', '.less'];
|
|
10
|
-
|
|
11
|
-
export function resolve(specifier, context, nextResolve) {
|
|
12
|
-
if (CSS_EXTENSIONS.some((ext) => specifier.endsWith(ext))) {
|
|
13
|
-
return { url: `css-stub:${specifier}`, shortCircuit: true };
|
|
14
|
-
}
|
|
15
|
-
return nextResolve(specifier, context);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function load(url, context, nextLoad) {
|
|
19
|
-
if (url.startsWith('css-stub:')) {
|
|
20
|
-
return {
|
|
21
|
-
format: 'module',
|
|
22
|
-
source: 'export default {};',
|
|
23
|
-
shortCircuit: true,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return nextLoad(url, context);
|
|
27
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* generate-registry — Derives a runtime registry from co-located component contracts.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* baremetal generate-registry --contracts <path> --output <path>
|
|
7
|
-
*
|
|
8
|
-
* --contracts Path to the aggregation module (default-exports an array of ComponentContract).
|
|
9
|
-
* --output Path to the generated TypeScript file.
|
|
10
|
-
*
|
|
11
|
-
* The generated file imports the contracts module and derives:
|
|
12
|
-
* - `registry`: a flat map of component name -> component function (for the renderer)
|
|
13
|
-
* - `contractMap`: a flat map of component name -> full contract (for tooling/validation)
|
|
14
|
-
*/
|
|
15
|
-
import { writeFile, mkdir } from 'node:fs/promises';
|
|
16
|
-
import path from 'node:path';
|
|
17
|
-
|
|
18
|
-
const args = parseArgs(process.argv.slice(2));
|
|
19
|
-
|
|
20
|
-
if (!args.contracts) {
|
|
21
|
-
console.error('Error: --contracts <path> is required');
|
|
22
|
-
process.exitCode = 1;
|
|
23
|
-
} else if (!args.output) {
|
|
24
|
-
console.error('Error: --output <path> is required');
|
|
25
|
-
process.exitCode = 1;
|
|
26
|
-
} else {
|
|
27
|
-
try {
|
|
28
|
-
await generateRegistry(args);
|
|
29
|
-
console.log(`Registry generated at ${args.output}`);
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error(`Error: ${error?.message ?? error}`);
|
|
32
|
-
process.exitCode = 1;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function generateRegistry({ contracts, output }) {
|
|
37
|
-
const contractsPath = contracts.replace(/\.(ts|tsx|js|mjs)$/, '');
|
|
38
|
-
const outputPath = path.resolve(output);
|
|
39
|
-
|
|
40
|
-
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
41
|
-
|
|
42
|
-
const content = `// AUTO-GENERATED — do not edit. Run \`baremetal generate-registry\` to regenerate.
|
|
43
|
-
import type { ComponentContract } from '@baremetal/sdk';
|
|
44
|
-
import contracts from '${contractsPath}';
|
|
45
|
-
|
|
46
|
-
export const registry = Object.fromEntries(
|
|
47
|
-
contracts.filter((c) => c.component != null).map((c) => [c.name, c.component]),
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
export const contractMap: Record<string, ComponentContract> = Object.fromEntries(
|
|
51
|
-
contracts.map((c) => [c.name, c]),
|
|
52
|
-
);
|
|
53
|
-
`;
|
|
54
|
-
|
|
55
|
-
await writeFile(outputPath, content, 'utf-8');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function parseArgs(argv) {
|
|
59
|
-
const out = {};
|
|
60
|
-
for (let i = 0; i < argv.length; i++) {
|
|
61
|
-
const arg = argv[i];
|
|
62
|
-
if (!arg.startsWith('--')) continue;
|
|
63
|
-
const body = arg.slice(2);
|
|
64
|
-
const equals = body.indexOf('=');
|
|
65
|
-
if (equals >= 0) {
|
|
66
|
-
out[body.slice(0, equals)] = body.slice(equals + 1);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
out[body] = argv[++i];
|
|
70
|
-
}
|
|
71
|
-
return out;
|
|
72
|
-
}
|