@pie-element/cli 0.1.1
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/.compatibility/report.json +2491 -0
- package/.turbo/turbo-build.log +1 -0
- package/CHANGELOG.md +8 -0
- package/README.md +240 -0
- package/REFACTORING.md +131 -0
- package/bin/dev.js +5 -0
- package/bin/run.js +5 -0
- package/dist/commands/dev/demo/index.d.ts +33 -0
- package/dist/commands/dev/demo/index.d.ts.map +1 -0
- package/dist/commands/dev/demo/index.js +274 -0
- package/dist/commands/dev/demo/index.js.map +1 -0
- package/dist/commands/dev/test-bundler.d.ts +18 -0
- package/dist/commands/dev/test-bundler.d.ts.map +1 -0
- package/dist/commands/dev/test-bundler.js +284 -0
- package/dist/commands/dev/test-bundler.js.map +1 -0
- package/dist/commands/mathquill/migrate.d.ts +20 -0
- package/dist/commands/mathquill/migrate.d.ts.map +1 -0
- package/dist/commands/mathquill/migrate.js +240 -0
- package/dist/commands/mathquill/migrate.js.map +1 -0
- package/dist/commands/mathquill/status.d.ts +20 -0
- package/dist/commands/mathquill/status.d.ts.map +1 -0
- package/dist/commands/mathquill/status.js +231 -0
- package/dist/commands/mathquill/status.js.map +1 -0
- package/dist/commands/packages/create-controllers.d.ts +13 -0
- package/dist/commands/packages/create-controllers.d.ts.map +1 -0
- package/dist/commands/packages/create-controllers.js +103 -0
- package/dist/commands/packages/create-controllers.js.map +1 -0
- package/dist/commands/packages/create-lib-react.d.ts +17 -0
- package/dist/commands/packages/create-lib-react.d.ts.map +1 -0
- package/dist/commands/packages/create-lib-react.js +228 -0
- package/dist/commands/packages/create-lib-react.js.map +1 -0
- package/dist/commands/packages/create-react-elements.d.ts +16 -0
- package/dist/commands/packages/create-react-elements.d.ts.map +1 -0
- package/dist/commands/packages/create-react-elements.js +259 -0
- package/dist/commands/packages/create-react-elements.js.map +1 -0
- package/dist/commands/packages/enable-publishing.d.ts +11 -0
- package/dist/commands/packages/enable-publishing.d.ts.map +1 -0
- package/dist/commands/packages/enable-publishing.js +117 -0
- package/dist/commands/packages/enable-publishing.js.map +1 -0
- package/dist/commands/packages/init-synced-elements.d.ts +16 -0
- package/dist/commands/packages/init-synced-elements.d.ts.map +1 -0
- package/dist/commands/packages/init-synced-elements.js +204 -0
- package/dist/commands/packages/init-synced-elements.js.map +1 -0
- package/dist/commands/upstream/analyze-esm.d.ts +59 -0
- package/dist/commands/upstream/analyze-esm.d.ts.map +1 -0
- package/dist/commands/upstream/analyze-esm.js +1481 -0
- package/dist/commands/upstream/analyze-esm.js.map +1 -0
- package/dist/commands/upstream/check.d.ts +15 -0
- package/dist/commands/upstream/check.d.ts.map +1 -0
- package/dist/commands/upstream/check.js +165 -0
- package/dist/commands/upstream/check.js.map +1 -0
- package/dist/commands/upstream/deps.d.ts +15 -0
- package/dist/commands/upstream/deps.d.ts.map +1 -0
- package/dist/commands/upstream/deps.js +174 -0
- package/dist/commands/upstream/deps.js.map +1 -0
- package/dist/commands/upstream/sync-mathquill.d.ts +12 -0
- package/dist/commands/upstream/sync-mathquill.d.ts.map +1 -0
- package/dist/commands/upstream/sync-mathquill.js +97 -0
- package/dist/commands/upstream/sync-mathquill.js.map +1 -0
- package/dist/commands/upstream/sync.d.ts +25 -0
- package/dist/commands/upstream/sync.d.ts.map +1 -0
- package/dist/commands/upstream/sync.js +552 -0
- package/dist/commands/upstream/sync.js.map +1 -0
- package/dist/commands/upstream/track.d.ts +15 -0
- package/dist/commands/upstream/track.d.ts.map +1 -0
- package/dist/commands/upstream/track.js +115 -0
- package/dist/commands/upstream/track.js.map +1 -0
- package/dist/commands/upstream/update.d.ts +15 -0
- package/dist/commands/upstream/update.d.ts.map +1 -0
- package/dist/commands/upstream/update.js +98 -0
- package/dist/commands/upstream/update.js.map +1 -0
- package/dist/commands/verify/controllers.d.ts +12 -0
- package/dist/commands/verify/controllers.d.ts.map +1 -0
- package/dist/commands/verify/controllers.js +138 -0
- package/dist/commands/verify/controllers.js.map +1 -0
- package/dist/commands/verify/react-build.d.ts +13 -0
- package/dist/commands/verify/react-build.d.ts.map +1 -0
- package/dist/commands/verify/react-build.js +135 -0
- package/dist/commands/verify/react-build.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/mathquill/extension-generator.d.ts +9 -0
- package/dist/lib/mathquill/extension-generator.d.ts.map +1 -0
- package/dist/lib/mathquill/extension-generator.js +107 -0
- package/dist/lib/mathquill/extension-generator.js.map +1 -0
- package/dist/lib/mathquill/khan-extractor.d.ts +16 -0
- package/dist/lib/mathquill/khan-extractor.d.ts.map +1 -0
- package/dist/lib/mathquill/khan-extractor.js +148 -0
- package/dist/lib/mathquill/khan-extractor.js.map +1 -0
- package/dist/lib/mathquill/learnosity-extractor.d.ts +15 -0
- package/dist/lib/mathquill/learnosity-extractor.d.ts.map +1 -0
- package/dist/lib/mathquill/learnosity-extractor.js +170 -0
- package/dist/lib/mathquill/learnosity-extractor.js.map +1 -0
- package/dist/lib/mathquill/migration-orchestrator.d.ts +77 -0
- package/dist/lib/mathquill/migration-orchestrator.d.ts.map +1 -0
- package/dist/lib/mathquill/migration-orchestrator.js +391 -0
- package/dist/lib/mathquill/migration-orchestrator.js.map +1 -0
- package/dist/lib/mathquill/package-updater.d.ts +9 -0
- package/dist/lib/mathquill/package-updater.d.ts.map +1 -0
- package/dist/lib/mathquill/package-updater.js +103 -0
- package/dist/lib/mathquill/package-updater.js.map +1 -0
- package/dist/lib/mathquill/pie-extractor.d.ts +14 -0
- package/dist/lib/mathquill/pie-extractor.d.ts.map +1 -0
- package/dist/lib/mathquill/pie-extractor.js +167 -0
- package/dist/lib/mathquill/pie-extractor.js.map +1 -0
- package/dist/lib/upstream/repo-utils.d.ts +9 -0
- package/dist/lib/upstream/repo-utils.d.ts.map +1 -0
- package/dist/lib/upstream/repo-utils.js +10 -0
- package/dist/lib/upstream/repo-utils.js.map +1 -0
- package/dist/lib/upstream/sync-compatibility.d.ts +29 -0
- package/dist/lib/upstream/sync-compatibility.d.ts.map +1 -0
- package/dist/lib/upstream/sync-compatibility.js +70 -0
- package/dist/lib/upstream/sync-compatibility.js.map +1 -0
- package/dist/lib/upstream/sync-constants.d.ts +50 -0
- package/dist/lib/upstream/sync-constants.d.ts.map +1 -0
- package/dist/lib/upstream/sync-constants.js +66 -0
- package/dist/lib/upstream/sync-constants.js.map +1 -0
- package/dist/lib/upstream/sync-controllers-strategy.d.ts +18 -0
- package/dist/lib/upstream/sync-controllers-strategy.d.ts.map +1 -0
- package/dist/lib/upstream/sync-controllers-strategy.js +243 -0
- package/dist/lib/upstream/sync-controllers-strategy.js.map +1 -0
- package/dist/lib/upstream/sync-demo-metadata.d.ts +15 -0
- package/dist/lib/upstream/sync-demo-metadata.d.ts.map +1 -0
- package/dist/lib/upstream/sync-demo-metadata.js +165 -0
- package/dist/lib/upstream/sync-demo-metadata.js.map +1 -0
- package/dist/lib/upstream/sync-dev-exports.d.ts +13 -0
- package/dist/lib/upstream/sync-dev-exports.d.ts.map +1 -0
- package/dist/lib/upstream/sync-dev-exports.js +115 -0
- package/dist/lib/upstream/sync-dev-exports.js.map +1 -0
- package/dist/lib/upstream/sync-externals.d.ts +29 -0
- package/dist/lib/upstream/sync-externals.d.ts.map +1 -0
- package/dist/lib/upstream/sync-externals.js +164 -0
- package/dist/lib/upstream/sync-externals.js.map +1 -0
- package/dist/lib/upstream/sync-filesystem.d.ts +21 -0
- package/dist/lib/upstream/sync-filesystem.d.ts.map +1 -0
- package/dist/lib/upstream/sync-filesystem.js +64 -0
- package/dist/lib/upstream/sync-filesystem.js.map +1 -0
- package/dist/lib/upstream/sync-imports.d.ts +284 -0
- package/dist/lib/upstream/sync-imports.d.ts.map +1 -0
- package/dist/lib/upstream/sync-imports.js +816 -0
- package/dist/lib/upstream/sync-imports.js.map +1 -0
- package/dist/lib/upstream/sync-mathquill.d.ts +19 -0
- package/dist/lib/upstream/sync-mathquill.d.ts.map +1 -0
- package/dist/lib/upstream/sync-mathquill.js +160 -0
- package/dist/lib/upstream/sync-mathquill.js.map +1 -0
- package/dist/lib/upstream/sync-package-json.d.ts +28 -0
- package/dist/lib/upstream/sync-package-json.d.ts.map +1 -0
- package/dist/lib/upstream/sync-package-json.js +66 -0
- package/dist/lib/upstream/sync-package-json.js.map +1 -0
- package/dist/lib/upstream/sync-package-manager.d.ts +50 -0
- package/dist/lib/upstream/sync-package-manager.d.ts.map +1 -0
- package/dist/lib/upstream/sync-package-manager.js +570 -0
- package/dist/lib/upstream/sync-package-manager.js.map +1 -0
- package/dist/lib/upstream/sync-pielib-strategy.d.ts +26 -0
- package/dist/lib/upstream/sync-pielib-strategy.d.ts.map +1 -0
- package/dist/lib/upstream/sync-pielib-strategy.js +345 -0
- package/dist/lib/upstream/sync-pielib-strategy.js.map +1 -0
- package/dist/lib/upstream/sync-react-strategy.d.ts +54 -0
- package/dist/lib/upstream/sync-react-strategy.d.ts.map +1 -0
- package/dist/lib/upstream/sync-react-strategy.js +733 -0
- package/dist/lib/upstream/sync-react-strategy.js.map +1 -0
- package/dist/lib/upstream/sync-strategy.d.ts +49 -0
- package/dist/lib/upstream/sync-strategy.d.ts.map +1 -0
- package/dist/lib/upstream/sync-strategy.js +2 -0
- package/dist/lib/upstream/sync-strategy.js.map +1 -0
- package/dist/lib/upstream/sync-summary.d.ts +20 -0
- package/dist/lib/upstream/sync-summary.d.ts.map +1 -0
- package/dist/lib/upstream/sync-summary.js +157 -0
- package/dist/lib/upstream/sync-summary.js.map +1 -0
- package/dist/lib/upstream/sync-transforms.d.ts +60 -0
- package/dist/lib/upstream/sync-transforms.d.ts.map +1 -0
- package/dist/lib/upstream/sync-transforms.js +97 -0
- package/dist/lib/upstream/sync-transforms.js.map +1 -0
- package/dist/lib/upstream/sync-vite-config.d.ts +13 -0
- package/dist/lib/upstream/sync-vite-config.d.ts.map +1 -0
- package/dist/lib/upstream/sync-vite-config.js +231 -0
- package/dist/lib/upstream/sync-vite-config.js.map +1 -0
- package/dist/utils/compatibility.d.ts +129 -0
- package/dist/utils/compatibility.d.ts.map +1 -0
- package/dist/utils/compatibility.js +44 -0
- package/dist/utils/compatibility.js.map +1 -0
- package/dist/utils/conversion.d.ts +25 -0
- package/dist/utils/conversion.d.ts.map +1 -0
- package/dist/utils/conversion.js +123 -0
- package/dist/utils/conversion.js.map +1 -0
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +63 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +29 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +63 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/package-json.d.ts +12 -0
- package/dist/utils/package-json.d.ts.map +1 -0
- package/dist/utils/package-json.js +29 -0
- package/dist/utils/package-json.js.map +1 -0
- package/package.json +59 -0
- package/packages/cli/bin/dev.js +5 -0
- package/packages/cli/bin/run.js +5 -0
- package/src/commands/dev/demo/index.ts +313 -0
- package/src/commands/dev/test-bundler.ts +345 -0
- package/src/commands/mathquill/migrate.ts +269 -0
- package/src/commands/mathquill/status.ts +267 -0
- package/src/commands/packages/create-controllers.ts +127 -0
- package/src/commands/packages/create-lib-react.ts +279 -0
- package/src/commands/packages/create-react-elements.ts +306 -0
- package/src/commands/packages/enable-publishing.ts +161 -0
- package/src/commands/packages/init-synced-elements.ts +259 -0
- package/src/commands/upstream/README.md +444 -0
- package/src/commands/upstream/analyze-esm.ts +1973 -0
- package/src/commands/upstream/check.ts +228 -0
- package/src/commands/upstream/deps.ts +222 -0
- package/src/commands/upstream/sync-mathquill.ts +108 -0
- package/src/commands/upstream/sync.ts +687 -0
- package/src/commands/upstream/track.ts +145 -0
- package/src/commands/upstream/update.ts +110 -0
- package/src/commands/verify/controllers.ts +166 -0
- package/src/commands/verify/react-build.ts +171 -0
- package/src/index.ts +1 -0
- package/src/lib/mathquill/extension-generator.ts +111 -0
- package/src/lib/mathquill/khan-extractor.ts +175 -0
- package/src/lib/mathquill/learnosity-extractor.ts +198 -0
- package/src/lib/mathquill/migration-orchestrator.ts +513 -0
- package/src/lib/mathquill/package-updater.ts +120 -0
- package/src/lib/mathquill/pie-extractor.ts +194 -0
- package/src/lib/upstream/repo-utils.ts +18 -0
- package/src/lib/upstream/sync-compatibility.ts +89 -0
- package/src/lib/upstream/sync-constants.ts +77 -0
- package/src/lib/upstream/sync-controllers-strategy.ts +291 -0
- package/src/lib/upstream/sync-demo-metadata.ts +199 -0
- package/src/lib/upstream/sync-dev-exports.ts +151 -0
- package/src/lib/upstream/sync-externals.ts +159 -0
- package/src/lib/upstream/sync-filesystem.ts +87 -0
- package/src/lib/upstream/sync-imports.ts +992 -0
- package/src/lib/upstream/sync-mathquill.ts +194 -0
- package/src/lib/upstream/sync-package-json.ts +86 -0
- package/src/lib/upstream/sync-package-manager.ts +694 -0
- package/src/lib/upstream/sync-pielib-strategy.ts +441 -0
- package/src/lib/upstream/sync-react-strategy.ts +945 -0
- package/src/lib/upstream/sync-strategy.ts +61 -0
- package/src/lib/upstream/sync-summary.ts +194 -0
- package/src/lib/upstream/sync-transforms.ts +154 -0
- package/src/lib/upstream/sync-vite-config.ts +243 -0
- package/src/types/es-module-lexer.d.ts +4 -0
- package/src/utils/compatibility.ts +182 -0
- package/src/utils/conversion.ts +153 -0
- package/src/utils/git.ts +63 -0
- package/src/utils/logger.ts +69 -0
- package/src/utils/package-json.ts +43 -0
- package/tests/sync-package-manager.test.ts +112 -0
- package/tests/transform-assign-props.test.ts +101 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ESM package.json validation result
|
|
6
|
+
*/
|
|
7
|
+
export interface EsmPackageValidation {
|
|
8
|
+
hasTypeModule: boolean;
|
|
9
|
+
hasExportsField: boolean;
|
|
10
|
+
hasMainExport: boolean;
|
|
11
|
+
hasControllerExport: boolean;
|
|
12
|
+
sideEffectsFree: boolean;
|
|
13
|
+
mainExportIsEsm: boolean;
|
|
14
|
+
controllerExportIsEsm: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ESM player validation result for an element
|
|
19
|
+
*/
|
|
20
|
+
export interface EsmValidationResult {
|
|
21
|
+
compatible: boolean;
|
|
22
|
+
packageJson: EsmPackageValidation;
|
|
23
|
+
blockers: string[];
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Runtime-ish validation for ESM player readiness (CDN resolution / module parse).
|
|
29
|
+
* Note: this is NOT a full browser execution test, but it provides production-like signal
|
|
30
|
+
* by verifying CDN URLs resolve and the returned JS parses as an ES module.
|
|
31
|
+
*/
|
|
32
|
+
export interface EsmRuntimeValidationResult {
|
|
33
|
+
/**
|
|
34
|
+
* Whether this element passed the runtime-ish probe.
|
|
35
|
+
* When false, see `errors` for details.
|
|
36
|
+
*/
|
|
37
|
+
compatible: boolean;
|
|
38
|
+
/** The CDN base URL used for probing (e.g. https://esm.sh). */
|
|
39
|
+
cdnBaseUrl: string;
|
|
40
|
+
/** Version that was probed (usually the upstream package.json version). */
|
|
41
|
+
version: string;
|
|
42
|
+
/** Whether the package entrypoint URL resolved (HTTP 200). */
|
|
43
|
+
entryOk: boolean;
|
|
44
|
+
/** Whether the controller URL resolved (HTTP 200). */
|
|
45
|
+
controllerOk: boolean;
|
|
46
|
+
/** Whether the returned JS for the entry parsed as an ES module. */
|
|
47
|
+
entryParseOk: boolean;
|
|
48
|
+
/** Whether the returned JS for the controller parsed as an ES module. */
|
|
49
|
+
controllerParseOk: boolean;
|
|
50
|
+
/** Any errors/warnings collected during probe. */
|
|
51
|
+
errors: string[];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Probe details (for deep/transitive validation)
|
|
55
|
+
*/
|
|
56
|
+
probeMode?: 'shallow' | 'deep';
|
|
57
|
+
maxDepth?: number;
|
|
58
|
+
maxModules?: number;
|
|
59
|
+
fetchedModules?: number;
|
|
60
|
+
fetchedOk?: number;
|
|
61
|
+
firstFailingUrl?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compatibility report structure
|
|
66
|
+
*/
|
|
67
|
+
export interface CompatibilityReport {
|
|
68
|
+
elements: string[];
|
|
69
|
+
pieLibPackages: string[];
|
|
70
|
+
blockedElements: Record<string, string[]>;
|
|
71
|
+
elementDetails: Record<string, ElementDetail>;
|
|
72
|
+
pieLibDetails: Record<string, PieLibDetail>;
|
|
73
|
+
|
|
74
|
+
// ESM player validation
|
|
75
|
+
esmPlayerReady: string[];
|
|
76
|
+
esmValidation: Record<string, EsmValidationResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Whether ESM player validation (package.json structure / exports) was run as part of analysis.
|
|
79
|
+
* Optional for backwards compatibility with older reports.
|
|
80
|
+
*/
|
|
81
|
+
esmPlayerValidationEnabled?: boolean;
|
|
82
|
+
|
|
83
|
+
// Runtime-ish validation (CDN probes)
|
|
84
|
+
esmRuntimeValidationEnabled?: boolean;
|
|
85
|
+
esmRuntimeCdnBaseUrl?: string;
|
|
86
|
+
esmRuntimeValidation?: Record<string, EsmRuntimeValidationResult>;
|
|
87
|
+
|
|
88
|
+
// Student UI only (elements where student UI is ESM-ready but authoring isn't)
|
|
89
|
+
studentUIOnly?: string[];
|
|
90
|
+
|
|
91
|
+
lastAnalyzed: string;
|
|
92
|
+
summary: {
|
|
93
|
+
totalElements: number;
|
|
94
|
+
compatibleElements: number;
|
|
95
|
+
blockedElements: number;
|
|
96
|
+
studentUIOnlyElements?: number;
|
|
97
|
+
esmPlayerReady: number;
|
|
98
|
+
esmRuntimeReady?: number;
|
|
99
|
+
totalPieLibPackages: number;
|
|
100
|
+
compatiblePieLibPackages: number;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ElementDetail {
|
|
105
|
+
compatible: boolean;
|
|
106
|
+
directDeps: string[];
|
|
107
|
+
pieLibDeps: string[];
|
|
108
|
+
pieElementDeps: string[];
|
|
109
|
+
blockers: string[];
|
|
110
|
+
|
|
111
|
+
// Separate analysis for student UI vs authoring
|
|
112
|
+
studentUI?: {
|
|
113
|
+
compatible: boolean;
|
|
114
|
+
blockers: string[];
|
|
115
|
+
};
|
|
116
|
+
configure?: {
|
|
117
|
+
compatible: boolean;
|
|
118
|
+
blockers: string[];
|
|
119
|
+
};
|
|
120
|
+
controller?: {
|
|
121
|
+
compatible: boolean;
|
|
122
|
+
blockers: string[];
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface PieLibDetail {
|
|
127
|
+
compatible: boolean;
|
|
128
|
+
usedBy: string[];
|
|
129
|
+
blockers: string[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function loadCompatibilityReport(path: string): Promise<CompatibilityReport> {
|
|
133
|
+
if (!existsSync(path)) {
|
|
134
|
+
throw new Error(`Compatibility report not found at: ${path}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const content = await readFile(path, 'utf-8');
|
|
138
|
+
return JSON.parse(content) as CompatibilityReport;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isElementCompatible(element: string, report: CompatibilityReport): boolean {
|
|
142
|
+
return report.elements.includes(element);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function isPieLibPackageCompatible(pkg: string, report: CompatibilityReport): boolean {
|
|
146
|
+
return report.pieLibPackages.includes(pkg);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getElementBlockers(element: string, report: CompatibilityReport): string[] {
|
|
150
|
+
return report.blockedElements[element] || [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if an element is ESM player ready (no CommonJS deps + proper ESM package structure)
|
|
155
|
+
*/
|
|
156
|
+
export function isElementEsmPlayerReady(element: string, report: CompatibilityReport): boolean {
|
|
157
|
+
return report.esmPlayerReady.includes(element);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if an element has student UI ready (even if configure/controller aren't)
|
|
162
|
+
*/
|
|
163
|
+
export function isElementStudentUIReady(element: string, report: CompatibilityReport): boolean {
|
|
164
|
+
const detail = report.elementDetails[element];
|
|
165
|
+
if (!detail) return false;
|
|
166
|
+
|
|
167
|
+
// If fully compatible, student UI is ready
|
|
168
|
+
if (detail.compatible) return true;
|
|
169
|
+
|
|
170
|
+
// Check if student UI is explicitly marked as compatible
|
|
171
|
+
return detail.studentUI?.compatible === true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get ESM validation details for an element
|
|
176
|
+
*/
|
|
177
|
+
export function getEsmValidation(
|
|
178
|
+
element: string,
|
|
179
|
+
report: CompatibilityReport
|
|
180
|
+
): EsmValidationResult | null {
|
|
181
|
+
return report.esmValidation[element] || null;
|
|
182
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript to TypeScript conversion utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ConversionMetadata {
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
commit: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ConversionResult {
|
|
11
|
+
code: string;
|
|
12
|
+
hasDefaultObjectExport: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert JavaScript to TypeScript
|
|
17
|
+
*/
|
|
18
|
+
export function convertJsToTs(jsCode: string, metadata: ConversionMetadata): ConversionResult {
|
|
19
|
+
// We intentionally do NOT transform the JavaScript syntax.
|
|
20
|
+
// Goal: keep upstream source as-is, while letting the monorepo's TS/Vite pipeline build it.
|
|
21
|
+
// TypeScript diagnostics are suppressed for synced files.
|
|
22
|
+
let tsCode = jsCode;
|
|
23
|
+
const hasDefaultObjectExport = false;
|
|
24
|
+
|
|
25
|
+
// Convert CommonJS module.exports to ES modules
|
|
26
|
+
tsCode = convertModuleExportsToEsm(tsCode);
|
|
27
|
+
|
|
28
|
+
// Must be at the very top of the file to reliably suppress TypeScript diagnostics.
|
|
29
|
+
// We keep synced files buildable even when upstream JS has type issues.
|
|
30
|
+
const tsNoCheck = `// @ts-nocheck\n`;
|
|
31
|
+
|
|
32
|
+
// Add sync metadata header
|
|
33
|
+
const header = `/**
|
|
34
|
+
* @synced-from ${metadata.sourcePath}
|
|
35
|
+
* @auto-generated
|
|
36
|
+
*
|
|
37
|
+
* This file is automatically synced from pie-elements and converted to TypeScript.
|
|
38
|
+
* Manual edits will be overwritten on next sync.
|
|
39
|
+
* To make changes, edit the upstream JavaScript file and run sync again.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
`;
|
|
43
|
+
return { code: tsNoCheck + header + tsCode, hasDefaultObjectExport };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Convert CommonJS module.exports to ES module exports
|
|
48
|
+
*/
|
|
49
|
+
function convertModuleExportsToEsm(content: string): string {
|
|
50
|
+
// Match: module.exports = { key: value, key2: value2, ... };
|
|
51
|
+
const moduleExportsPattern = /module\.exports\s*=\s*\{([\s\S]*?)\};?/;
|
|
52
|
+
const match = content.match(moduleExportsPattern);
|
|
53
|
+
|
|
54
|
+
if (!match) {
|
|
55
|
+
return content;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [fullMatch, objectContent] = match;
|
|
59
|
+
|
|
60
|
+
// Parse the object content into key-value pairs
|
|
61
|
+
const exports: string[] = [];
|
|
62
|
+
const lines = objectContent.split('\n');
|
|
63
|
+
|
|
64
|
+
let currentKey: string | null = null;
|
|
65
|
+
let currentValue: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed || trimmed === '{' || trimmed === '}') continue;
|
|
70
|
+
|
|
71
|
+
// Check if this line starts a new key
|
|
72
|
+
const keyMatch = trimmed.match(/^(\w+):\s*(.*)/);
|
|
73
|
+
if (keyMatch) {
|
|
74
|
+
// Save previous export if any
|
|
75
|
+
if (currentKey) {
|
|
76
|
+
const value = currentValue.join('\n').replace(/,\s*$/, ''); // Remove trailing comma
|
|
77
|
+
exports.push(`export const ${currentKey} =\n${value};`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Start new export
|
|
81
|
+
currentKey = keyMatch[1];
|
|
82
|
+
currentValue = [keyMatch[2]];
|
|
83
|
+
} else {
|
|
84
|
+
// Continuation of current value
|
|
85
|
+
currentValue.push(line);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Save last export
|
|
90
|
+
if (currentKey) {
|
|
91
|
+
const value = currentValue.join('\n').replace(/,\s*$/, '');
|
|
92
|
+
exports.push(`export const ${currentKey} =\n${value};`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Replace the module.exports block with ES module exports
|
|
96
|
+
const esmExports = exports.join('\n\n');
|
|
97
|
+
return content.replace(fullMatch, esmExports);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convert JSX to TSX
|
|
102
|
+
*/
|
|
103
|
+
export function convertJsxToTsx(jsxCode: string, metadata: ConversionMetadata): ConversionResult {
|
|
104
|
+
// We intentionally do NOT transform the JavaScript/JSX syntax.
|
|
105
|
+
// Goal: keep upstream source as-is, while letting the monorepo's TS/Vite pipeline build it.
|
|
106
|
+
// TypeScript diagnostics are suppressed for synced files.
|
|
107
|
+
const tsxCode = jsxCode;
|
|
108
|
+
const hasDefaultObjectExport = false;
|
|
109
|
+
|
|
110
|
+
// Must be at the very top of the file to reliably suppress TypeScript diagnostics.
|
|
111
|
+
// We keep synced files buildable even when upstream JSX has type issues.
|
|
112
|
+
const tsNoCheck = `// @ts-nocheck\n`;
|
|
113
|
+
|
|
114
|
+
// Add sync metadata header
|
|
115
|
+
const header = `/**
|
|
116
|
+
* @synced-from ${metadata.sourcePath}
|
|
117
|
+
* @auto-generated
|
|
118
|
+
*
|
|
119
|
+
* This file is automatically synced from pie-elements and converted to TypeScript.
|
|
120
|
+
* Manual edits will be overwritten on next sync.
|
|
121
|
+
* To make changes, edit the upstream JavaScript file and run sync again.
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
`;
|
|
125
|
+
return { code: tsNoCheck + header + tsxCode, hasDefaultObjectExport };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fix imports from default export files
|
|
130
|
+
* Some files export default objects but are imported with named imports
|
|
131
|
+
*/
|
|
132
|
+
export function fixImports(content: string, hasDefaultObjectExport: boolean): string {
|
|
133
|
+
if (!hasDefaultObjectExport) {
|
|
134
|
+
return content;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Convert: import { something } from './file'
|
|
138
|
+
// To: import * as file from './file'
|
|
139
|
+
// This is needed when the imported file has export default {}
|
|
140
|
+
return content.replace(
|
|
141
|
+
/import\s+{\s*([^}]+)\s*}\s+from\s+(['"]\.\/[^'"]+['"])/g,
|
|
142
|
+
(_match, _imports, path) => {
|
|
143
|
+
// Extract filename from path
|
|
144
|
+
const filename =
|
|
145
|
+
path
|
|
146
|
+
.replace(/['"]/g, '')
|
|
147
|
+
.split('/')
|
|
148
|
+
.pop()
|
|
149
|
+
?.replace(/\.\w+$/, '') || 'module';
|
|
150
|
+
return `import * as ${filename} from ${path}`;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
package/src/utils/git.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function getCurrentCommit(cwd: string): string {
|
|
4
|
+
try {
|
|
5
|
+
return execSync('git rev-parse HEAD', { cwd, encoding: 'utf8' }).trim();
|
|
6
|
+
} catch (error) {
|
|
7
|
+
throw new Error(`Failed to get current commit: ${error}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCurrentCommitShort(cwd: string): string {
|
|
12
|
+
try {
|
|
13
|
+
return execSync('git rev-parse --short HEAD', { cwd, encoding: 'utf8' }).trim();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(`Failed to get short commit: ${error}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getCommitsSince(since: string, cwd: string): string[] {
|
|
20
|
+
try {
|
|
21
|
+
const output = execSync(`git log ${since}..HEAD --oneline`, {
|
|
22
|
+
cwd,
|
|
23
|
+
encoding: 'utf8',
|
|
24
|
+
}).trim();
|
|
25
|
+
|
|
26
|
+
if (!output) return [];
|
|
27
|
+
return output.split('\n');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Failed to get commits since ${since}: ${error}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getFileModifiedDate(filePath: string, cwd: string): Date {
|
|
34
|
+
try {
|
|
35
|
+
const timestamp = execSync(`git log -1 --format=%ct -- "${filePath}"`, {
|
|
36
|
+
cwd,
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
}).trim();
|
|
39
|
+
|
|
40
|
+
return new Date(Number.parseInt(timestamp, 10) * 1000);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`Failed to get file modification date: ${error}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getCommitDetails(commit: string, cwd: string): string {
|
|
47
|
+
try {
|
|
48
|
+
return execSync(`git show --stat ${commit}`, {
|
|
49
|
+
cwd,
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Failed to get commit details: ${error}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getAuthorName(cwd: string): string {
|
|
58
|
+
try {
|
|
59
|
+
return execSync('git config user.name', { cwd, encoding: 'utf8' }).trim();
|
|
60
|
+
} catch (_error) {
|
|
61
|
+
return 'Unknown';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export class Logger {
|
|
2
|
+
constructor(private verbose: boolean = false) {}
|
|
3
|
+
|
|
4
|
+
isVerbose(): boolean {
|
|
5
|
+
return this.verbose;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
info(message: string): void {
|
|
9
|
+
console.log(message);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
debug(message: string): void {
|
|
13
|
+
if (this.verbose) {
|
|
14
|
+
console.log(message);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
warn(message: string): void {
|
|
19
|
+
console.warn(`⚠️ ${message}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
error(message: string): void {
|
|
23
|
+
console.error(`❌ ${message}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
success(message: string): void {
|
|
27
|
+
console.log(`✅ ${message}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
section(title: string): void {
|
|
31
|
+
console.log(`\n${title}`);
|
|
32
|
+
console.log('='.repeat(title.length));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Show a progress message (only in verbose mode)
|
|
37
|
+
* In non-verbose mode, use progressStart/progressComplete instead
|
|
38
|
+
*/
|
|
39
|
+
progress(message: string): void {
|
|
40
|
+
if (this.verbose) {
|
|
41
|
+
console.log(` ${message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Show start of an operation (always shown)
|
|
47
|
+
*/
|
|
48
|
+
progressStart(message: string): void {
|
|
49
|
+
process.stdout.write(message);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Complete a progress operation with success
|
|
54
|
+
*/
|
|
55
|
+
progressComplete(details?: string): void {
|
|
56
|
+
if (details) {
|
|
57
|
+
console.log(` ✓ ${details}`);
|
|
58
|
+
} else {
|
|
59
|
+
console.log(' ✓');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Complete a progress operation with a count
|
|
65
|
+
*/
|
|
66
|
+
progressCompleteWithCount(count: number, label: string): void {
|
|
67
|
+
console.log(` ✓ (${count} ${label})`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
export interface PackageJson {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
private?: boolean;
|
|
7
|
+
dependencies?: Record<string, string>;
|
|
8
|
+
devDependencies?: Record<string, string>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function loadPackageJson(path: string): Promise<PackageJson> {
|
|
13
|
+
const content = await readFile(path, 'utf-8');
|
|
14
|
+
return JSON.parse(content) as PackageJson;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function writePackageJson(path: string, pkg: PackageJson): Promise<void> {
|
|
18
|
+
const content = `${JSON.stringify(pkg, null, 2)}\n`;
|
|
19
|
+
await writeFile(path, content, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function extractDependencies(content: string): string[] {
|
|
23
|
+
const deps = new Set<string>();
|
|
24
|
+
|
|
25
|
+
// Match import statements
|
|
26
|
+
const importRegex = /import\s+.+\s+from\s+['"]([^'"]+)['"]/g;
|
|
27
|
+
let match: RegExpExecArray | null = importRegex.exec(content);
|
|
28
|
+
|
|
29
|
+
while (match !== null) {
|
|
30
|
+
const dep = match[1];
|
|
31
|
+
// Skip relative imports
|
|
32
|
+
if (!dep.startsWith('.') && !dep.startsWith('/')) {
|
|
33
|
+
// Extract package name (handle scoped packages)
|
|
34
|
+
const pkgName = dep.startsWith('@')
|
|
35
|
+
? dep.split('/').slice(0, 2).join('/')
|
|
36
|
+
: dep.split('/')[0];
|
|
37
|
+
deps.add(pkgName);
|
|
38
|
+
}
|
|
39
|
+
match = importRegex.exec(content);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Array.from(deps).sort();
|
|
43
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { SCRIPTS } from '../src/lib/upstream/sync-constants.js';
|
|
6
|
+
import {
|
|
7
|
+
ensureElementPackageJson,
|
|
8
|
+
ensurePieLibPackageJson,
|
|
9
|
+
} from '../src/lib/upstream/sync-package-manager.js';
|
|
10
|
+
|
|
11
|
+
const createConfig = (rootDir: string) =>
|
|
12
|
+
({
|
|
13
|
+
pieElements: join(rootDir, 'upstream', 'pie-elements'),
|
|
14
|
+
pieLib: join(rootDir, 'upstream', 'pie-lib'),
|
|
15
|
+
pieElementsNg: rootDir,
|
|
16
|
+
syncControllers: true,
|
|
17
|
+
syncReactComponents: true,
|
|
18
|
+
syncPieLib: true,
|
|
19
|
+
skipDemos: true,
|
|
20
|
+
upstreamCommit: 'test',
|
|
21
|
+
}) as any;
|
|
22
|
+
|
|
23
|
+
const createElementBase = async (elementDir: string) => {
|
|
24
|
+
await mkdir(join(elementDir, 'src'), { recursive: true });
|
|
25
|
+
await writeFile(
|
|
26
|
+
join(elementDir, 'src', 'index.ts'),
|
|
27
|
+
'export default class TestElement {}\n',
|
|
28
|
+
'utf-8'
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const readBuildScript = async (elementDir: string): Promise<string> => {
|
|
33
|
+
const pkgJson = JSON.parse(await readFile(join(elementDir, 'package.json'), 'utf-8'));
|
|
34
|
+
return pkgJson.scripts.build;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe('ensureElementPackageJson iife build script generation', () => {
|
|
38
|
+
it('uses IIFE build script when vite.config.iife.ts exists', async () => {
|
|
39
|
+
const rootDir = await mkdtemp(join(tmpdir(), 'pie-cli-sync-test-'));
|
|
40
|
+
const elementDir = join(rootDir, 'packages', 'elements-react', 'test-element');
|
|
41
|
+
|
|
42
|
+
await createElementBase(elementDir);
|
|
43
|
+
await writeFile(join(elementDir, 'vite.config.iife.ts'), 'export default {};\n', 'utf-8');
|
|
44
|
+
|
|
45
|
+
const changed = await ensureElementPackageJson(
|
|
46
|
+
'test-element',
|
|
47
|
+
elementDir,
|
|
48
|
+
createConfig(rootDir)
|
|
49
|
+
);
|
|
50
|
+
expect(changed).toBe(true);
|
|
51
|
+
|
|
52
|
+
const buildScript = await readBuildScript(elementDir);
|
|
53
|
+
expect(buildScript).toBe(SCRIPTS.BUILD_WITH_IIFE);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('uses standard build script when no IIFE files exist', async () => {
|
|
57
|
+
const rootDir = await mkdtemp(join(tmpdir(), 'pie-cli-sync-test-'));
|
|
58
|
+
const elementDir = join(rootDir, 'packages', 'elements-react', 'test-element');
|
|
59
|
+
|
|
60
|
+
await createElementBase(elementDir);
|
|
61
|
+
|
|
62
|
+
const changed = await ensureElementPackageJson(
|
|
63
|
+
'test-element',
|
|
64
|
+
elementDir,
|
|
65
|
+
createConfig(rootDir)
|
|
66
|
+
);
|
|
67
|
+
expect(changed).toBe(true);
|
|
68
|
+
|
|
69
|
+
const buildScript = await readBuildScript(elementDir);
|
|
70
|
+
expect(buildScript).toBe(SCRIPTS.BUILD);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('ensurePieLibPackageJson', () => {
|
|
75
|
+
it('pins math-rendering to shared mathjax adapter workspace package', async () => {
|
|
76
|
+
const rootDir = await mkdtemp(join(tmpdir(), 'pie-cli-sync-test-'));
|
|
77
|
+
const libDir = join(rootDir, 'packages', 'lib-react', 'math-rendering');
|
|
78
|
+
|
|
79
|
+
await mkdir(join(libDir, 'src'), { recursive: true });
|
|
80
|
+
await writeFile(join(libDir, 'src', 'index.ts'), 'export {};\n', 'utf-8');
|
|
81
|
+
|
|
82
|
+
const changed = await ensurePieLibPackageJson('math-rendering', libDir, createConfig(rootDir));
|
|
83
|
+
expect(changed).toBe(true);
|
|
84
|
+
|
|
85
|
+
const pkgJson = JSON.parse(await readFile(join(libDir, 'package.json'), 'utf-8'));
|
|
86
|
+
expect(pkgJson.dependencies).toEqual({
|
|
87
|
+
'@pie-element/shared-math-rendering-mathjax': 'workspace:*',
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('adds workspace deps detected from pie-lib source imports', async () => {
|
|
92
|
+
const rootDir = await mkdtemp(join(tmpdir(), 'pie-cli-sync-test-'));
|
|
93
|
+
const libDir = join(rootDir, 'packages', 'lib-react', 'math-input');
|
|
94
|
+
const renderUiDir = join(rootDir, 'packages', 'lib-react', 'render-ui');
|
|
95
|
+
|
|
96
|
+
await mkdir(join(libDir, 'src'), { recursive: true });
|
|
97
|
+
await mkdir(renderUiDir, { recursive: true });
|
|
98
|
+
await writeFile(
|
|
99
|
+
join(libDir, 'src', 'index.ts'),
|
|
100
|
+
"import { color } from '@pie-lib/render-ui';\nexport { color };\n",
|
|
101
|
+
'utf-8'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const changed = await ensurePieLibPackageJson('math-input', libDir, createConfig(rootDir));
|
|
105
|
+
expect(changed).toBe(true);
|
|
106
|
+
|
|
107
|
+
const pkgJson = JSON.parse(await readFile(join(libDir, 'package.json'), 'utf-8'));
|
|
108
|
+
expect(pkgJson.dependencies).toMatchObject({
|
|
109
|
+
'@pie-lib/render-ui': 'workspace:*',
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|