@moewolf/moe-icons-cli 0.0.1 → 0.0.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 +243 -9
- package/bin/check-node.cjs +36 -0
- package/bin/moeicons.js +12 -1
- package/dist/api/client.d.ts +13 -2
- package/dist/api/client.js +145 -21
- package/dist/auth/device-login.d.ts +10 -7
- package/dist/auth/device-login.js +60 -37
- package/dist/auth/login-schemas.d.ts +24 -0
- package/dist/auth/login-schemas.js +67 -0
- package/dist/catalog/catalog.d.ts +11 -0
- package/dist/catalog/catalog.js +43 -0
- package/dist/catalog/catalog.json +538 -3395
- package/dist/cli.d.ts +11 -2
- package/dist/cli.js +773 -147
- package/dist/commands/parser.d.ts +13 -2
- package/dist/commands/parser.js +138 -50
- package/dist/config-package/generated/SOURCE.json +25 -0
- package/dist/config-package/generated/moeicons-config.schema.json +191 -0
- package/dist/config-package/generated/moeicons.config.jsonc +42 -0
- package/dist/config-package/generated/render-config.cjs +145 -0
- package/dist/config-package/generated/validate-config.cjs +293 -0
- package/dist/config-package/generated-config.d.ts +32 -0
- package/dist/config-package/generated-config.js +19 -0
- package/dist/core/auth.d.ts +24 -0
- package/dist/core/auth.js +127 -8
- package/dist/core/bitmap-assets.d.ts +19 -5
- package/dist/core/bitmap-assets.js +40 -31
- package/dist/core/bitmap-shard-budget.d.ts +43 -0
- package/dist/core/bitmap-shard-budget.js +16 -0
- package/dist/core/bitmap-shard-cache.d.ts +68 -0
- package/dist/core/bitmap-shard-cache.js +115 -0
- package/dist/core/bitmap-shard-download.d.ts +130 -0
- package/dist/core/bitmap-shard-download.js +359 -0
- package/dist/core/bitmap-shard-resolver.d.ts +129 -0
- package/dist/core/bitmap-shard-resolver.js +262 -0
- package/dist/core/bitmap-shards.d.ts +70 -0
- package/dist/core/bitmap-shards.js +249 -0
- package/dist/core/bootstrap.d.ts +37 -0
- package/dist/core/bootstrap.js +54 -0
- package/dist/core/cache.d.ts +34 -0
- package/dist/core/cache.js +96 -0
- package/dist/core/context.d.ts +26 -0
- package/dist/core/doctor.d.ts +48 -0
- package/dist/core/doctor.js +190 -0
- package/dist/core/free-download.d.ts +64 -6
- package/dist/core/free-download.js +402 -30
- package/dist/core/generate.d.ts +29 -0
- package/dist/core/generate.js +341 -65
- package/dist/core/icon-selection.d.ts +8 -0
- package/dist/core/icon-selection.js +19 -0
- package/dist/core/install.d.ts +14 -0
- package/dist/core/install.js +132 -13
- package/dist/core/library-update.d.ts +5 -1
- package/dist/core/library-update.js +176 -17
- package/dist/core/local-test-env.d.ts +24 -0
- package/dist/core/local-test-env.js +43 -0
- package/dist/core/metadata-sync.d.ts +30 -0
- package/dist/core/metadata-sync.js +97 -0
- package/dist/core/pro-download.d.ts +63 -0
- package/dist/core/pro-download.js +247 -27
- package/dist/core/pro-install.d.ts +11 -3
- package/dist/core/pro-install.js +181 -19
- package/dist/core/pro-resources.d.ts +79 -0
- package/dist/core/pro-resources.js +204 -0
- package/dist/core/pro-selected-download.d.ts +18 -0
- package/dist/core/pro-selected-download.js +269 -0
- package/dist/core/release-descriptor.d.ts +42 -1
- package/dist/core/release-descriptor.js +117 -9
- package/dist/core/resource-variant.d.ts +27 -10
- package/dist/core/resource-variant.js +75 -34
- package/dist/core/selected-resources.d.ts +59 -0
- package/dist/core/selected-resources.js +350 -0
- package/dist/core/signed-artifact.d.ts +8 -1
- package/dist/core/signed-artifact.js +35 -11
- package/dist/core/target-subtree.d.ts +8 -0
- package/dist/core/target-subtree.js +54 -3
- package/dist/core/version-service.d.ts +11 -0
- package/dist/core/version-service.js +129 -24
- package/dist/core/wizard.d.ts +20 -17
- package/dist/core/wizard.js +66 -28
- package/dist/errors/errors.d.ts +2 -1
- package/dist/errors/errors.js +1 -0
- package/dist/generator/generate.d.ts +1 -1
- package/dist/generator/generate.js +420 -213
- package/dist/generator/shared/SOURCE.json +7 -0
- package/dist/generator/shared/svg-model.cjs +156 -0
- package/dist/generator/svg-model.d.ts +16 -0
- package/dist/generator/svg-model.js +4 -0
- package/dist/generator/theme-resolve.d.ts +2 -0
- package/dist/generator/theme-resolve.js +57 -26
- package/dist/mcp/server.d.ts +23 -36
- package/dist/mcp/server.js +74 -152
- package/dist/mcp/services.d.ts +4 -0
- package/dist/mcp/services.js +230 -0
- package/dist/metadata/archive.d.ts +38 -0
- package/dist/metadata/archive.js +94 -0
- package/dist/metadata/manifest.d.ts +33 -0
- package/dist/metadata/manifest.js +136 -0
- package/dist/metadata/version.d.ts +22 -0
- package/dist/metadata/version.js +53 -0
- package/dist/project/anchors/application.d.ts +13 -0
- package/dist/project/anchors/application.js +377 -0
- package/dist/project/anchors/apply.d.ts +28 -0
- package/dist/project/anchors/apply.js +65 -0
- package/dist/project/anchors/diagnose.d.ts +26 -0
- package/dist/project/anchors/diagnose.js +90 -0
- package/dist/project/anchors/helpers.d.ts +20 -0
- package/dist/project/anchors/helpers.js +55 -0
- package/dist/project/anchors/moeicons-config.d.ts +21 -0
- package/dist/project/anchors/moeicons-config.js +83 -0
- package/dist/project/anchors/parse.d.ts +14 -0
- package/dist/project/anchors/parse.js +36 -0
- package/dist/project/anchors/project-manifest.d.ts +19 -0
- package/dist/project/anchors/project-manifest.js +109 -0
- package/dist/project/anchors/style.d.ts +12 -0
- package/dist/project/anchors/style.js +87 -0
- package/dist/project/anchors/types.d.ts +32 -0
- package/dist/project/anchors/types.js +9 -0
- package/dist/project/config.d.ts +59 -5
- package/dist/project/config.js +234 -102
- package/dist/project/install-metadata.d.ts +23 -2
- package/dist/project/install-metadata.js +58 -7
- package/dist/project/install.d.ts +24 -4
- package/dist/project/install.js +402 -190
- package/dist/project/project-lock.d.ts +2 -2
- package/dist/project/project-lock.js +4 -4
- package/dist/project/tailwind.d.ts +2 -2
- package/dist/project/tailwind.js +3 -4
- package/dist/project/tar-gz.d.ts +18 -1
- package/dist/project/tar-gz.js +40 -3
- package/dist/tui/components.d.ts +62 -0
- package/dist/tui/components.js +100 -0
- package/dist/tui/primitives.d.ts +1 -0
- package/dist/tui/primitives.js +8 -1
- package/dist/ui/banner.d.ts +31 -3
- package/dist/ui/banner.js +120 -25
- package/dist/ui/branded-prompts.d.ts +41 -0
- package/dist/ui/branded-prompts.js +80 -0
- package/dist/ui/clack.d.ts +6 -3
- package/dist/ui/clack.js +56 -5
- package/dist/ui/create-ui.d.ts +1 -0
- package/dist/ui/create-ui.js +5 -1
- package/dist/ui/generated/wordmark-large.d.ts +17 -0
- package/dist/ui/generated/wordmark-large.js +28 -0
- package/dist/ui/generated/wordmark.d.ts +2 -0
- package/dist/ui/generated/wordmark.js +2 -0
- package/dist/ui/non-interactive.js +8 -0
- package/dist/ui/stream.js +30 -0
- package/dist/ui/theme.d.ts +42 -0
- package/dist/ui/theme.js +59 -0
- package/package.json +14 -6
package/dist/core/generate.js
CHANGED
|
@@ -1,25 +1,119 @@
|
|
|
1
1
|
import { join, relative, resolve } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
3
|
+
import { executeManagedReconcile, safeManagedPath, } from "../project/install.js";
|
|
4
4
|
import { detectProject } from "../project/detect.js";
|
|
5
|
-
import { readMoeiconsConfig } from "../project/config.js";
|
|
5
|
+
import { readMoeiconsConfig, loadConfigDocument, } from "../project/config.js";
|
|
6
|
+
import { catalog as defaultCatalog, parseCatalog } from "../catalog/catalog.js";
|
|
7
|
+
import { toProxyName } from "./icon-names.js";
|
|
6
8
|
import { planGeneratedFiles } from "../generator/generate.js";
|
|
7
9
|
import { ensureClassMergeDependencies, planTailwindIntegration } from "../project/tailwind.js";
|
|
8
|
-
import {
|
|
9
|
-
import { extractTarGz } from "../project/tar-gz.js";
|
|
10
|
+
import { isCliError } from "../errors/index.js";
|
|
11
|
+
import { extractTarGz, ICON_ARCHIVE_MAX_ENTRIES, ICON_ARCHIVE_MAX_EXPANDED_BYTES, } from "../project/tar-gz.js";
|
|
10
12
|
import { artifactCachePath } from "./free-download.js";
|
|
11
13
|
import { resolveThemes } from "../generator/theme-resolve.js";
|
|
14
|
+
import { loadPinnedBitmapShardAssets } from "./bitmap-shard-resolver.js";
|
|
12
15
|
import { parseInstallMetadata, serializeInstallMetadata, sha256Bytes, } from "../project/install-metadata.js";
|
|
13
16
|
import { withProjectLockSync } from "../project/project-lock.js";
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
import { allowLocalTestFromEnv } from "./local-test-env.js";
|
|
18
|
+
import { resolveIconTheme, themeHasIcon } from "./icon-selection.js";
|
|
19
|
+
function hasBitmapThemes(config, sourceCatalog) {
|
|
20
|
+
const resolved = resolveThemes(config, sourceCatalog);
|
|
16
21
|
return resolved.ok && resolved.themes.some((theme) => theme.kind === "bitmap");
|
|
17
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* B7A/B5/DEV-20-02: load the catalog written by install / pro-install / library
|
|
25
|
+
* update and verify it against the install metadata hash. A missing catalog
|
|
26
|
+
* falls back to the bundled contract; a corrupt or drifting one must surface a
|
|
27
|
+
* repair/reinstall error instead of silently switching entitlement semantics.
|
|
28
|
+
*/
|
|
29
|
+
export function loadInstalledCatalogState(projectRoot, fs_, opts = {}) {
|
|
30
|
+
const catalogPath = join(projectRoot, ".moeicons", "catalog.json");
|
|
31
|
+
const metadataPath = join(projectRoot, ".moeicons", "install-metadata.json");
|
|
32
|
+
const catalogExists = fs_.existsSync(catalogPath);
|
|
33
|
+
const metadataExists = fs_.existsSync(metadataPath);
|
|
34
|
+
// FIX-22-B: an installed project (metadata present) must have a parseable,
|
|
35
|
+
// hash-consistent catalog. Only a truly un-installed project falls back to
|
|
36
|
+
// the bundled contract.
|
|
37
|
+
if (!catalogExists) {
|
|
38
|
+
if (metadataExists) {
|
|
39
|
+
return {
|
|
40
|
+
status: "invalid",
|
|
41
|
+
message: "install metadata exists but the catalog is missing; run 'moeicons install' to repair or reinstall",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { status: "absent" };
|
|
45
|
+
}
|
|
46
|
+
let catalog;
|
|
47
|
+
let catalogBytes;
|
|
48
|
+
try {
|
|
49
|
+
catalogBytes = fs_.readFileSync(catalogPath, "utf8");
|
|
50
|
+
if (typeof catalogBytes !== "string")
|
|
51
|
+
return { status: "invalid", message: "installed catalog is not text" };
|
|
52
|
+
catalog = parseCatalog(JSON.parse(catalogBytes));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
status: "invalid",
|
|
57
|
+
message: `installed catalog cannot be parsed: ${error instanceof Error ? error.message : String(error)}; run 'moeicons install' to repair or reinstall`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!metadataExists) {
|
|
61
|
+
return {
|
|
62
|
+
status: "invalid",
|
|
63
|
+
message: "installed catalog is present without install metadata; run 'moeicons install' to repair or reinstall",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
let metadata;
|
|
67
|
+
try {
|
|
68
|
+
const rawMetadata = fs_.readFileSync(metadataPath, "utf8");
|
|
69
|
+
if (typeof rawMetadata !== "string")
|
|
70
|
+
throw new Error("metadata is not text");
|
|
71
|
+
metadata = parseInstallMetadata(rawMetadata, opts);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
status: "invalid",
|
|
76
|
+
message: `install metadata is invalid: ${error instanceof Error ? error.message : String(error)}; run 'moeicons install' to repair or reinstall`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!metadata) {
|
|
80
|
+
return {
|
|
81
|
+
status: "invalid",
|
|
82
|
+
message: "install metadata could not be parsed; run 'moeicons install' to repair or reinstall",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const actual = sha256Bytes(catalogBytes);
|
|
86
|
+
const managed = metadata.managedFiles?.[".moeicons/catalog.json"];
|
|
87
|
+
if (typeof metadata.catalogSha256 !== "string" || typeof managed !== "string") {
|
|
88
|
+
return {
|
|
89
|
+
status: "invalid",
|
|
90
|
+
message: "install metadata is missing the catalog digest; run 'moeicons install' to repair or reinstall",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (metadata.catalogSha256 !== managed) {
|
|
94
|
+
return {
|
|
95
|
+
status: "invalid",
|
|
96
|
+
message: "install metadata catalog digest is inconsistent; run 'moeicons install' to repair or reinstall",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (actual !== metadata.catalogSha256) {
|
|
100
|
+
return {
|
|
101
|
+
status: "invalid",
|
|
102
|
+
message: "installed catalog hash does not match install metadata; run 'moeicons install' to repair or reinstall",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { status: "ok", catalog };
|
|
106
|
+
}
|
|
107
|
+
/** Backwards-compatible accessor used by tests: returns the catalog or undefined. */
|
|
108
|
+
export function loadInstalledCatalog(projectRoot, fs_) {
|
|
109
|
+
const state = loadInstalledCatalogState(projectRoot, fs_);
|
|
110
|
+
return state.status === "ok" ? state.catalog : undefined;
|
|
111
|
+
}
|
|
18
112
|
/** Vanilla/Assets generate from raw SVG; bitmap themes need variant binaries. */
|
|
19
|
-
function needsArchiveFiles(config) {
|
|
113
|
+
function needsArchiveFiles(config, sourceCatalog) {
|
|
20
114
|
if (config.target === "vanilla" || config.target === "assets")
|
|
21
115
|
return true;
|
|
22
|
-
return hasBitmapThemes(config);
|
|
116
|
+
return hasBitmapThemes(config, sourceCatalog);
|
|
23
117
|
}
|
|
24
118
|
function archiveHasAssetsManifest(files) {
|
|
25
119
|
return Object.keys(files).some((path) => /(?:^|\/)assets\/manifest\.json$/.test(path));
|
|
@@ -57,6 +151,87 @@ function readBinaryFile(fs_, path) {
|
|
|
57
151
|
const data = fs_.readFileSync(path);
|
|
58
152
|
return typeof data === "string" ? Buffer.from(data, "utf8") : new Uint8Array(data);
|
|
59
153
|
}
|
|
154
|
+
/** Unique bitmap variants the config selects, in canonical order. */
|
|
155
|
+
function requiredBitmapVariants(config, sourceCatalog) {
|
|
156
|
+
const resolved = resolveThemes(config, sourceCatalog);
|
|
157
|
+
if (!resolved.ok)
|
|
158
|
+
return [];
|
|
159
|
+
const variants = resolved.themes
|
|
160
|
+
.filter((theme) => theme.kind === "bitmap" && theme.variant)
|
|
161
|
+
.flatMap((theme) => (theme.variant ? [theme.variant] : []));
|
|
162
|
+
return [...new Map(variants.map((variant) => [variant.resourceVariantId, variant])).values()];
|
|
163
|
+
}
|
|
164
|
+
function archiveHasVariantAssets(archiveFiles, variant, icons) {
|
|
165
|
+
return icons.every((iconId) => archiveFiles[`assets/${variant.resourceVariantId}/${iconId}.${variant.format}`] !== undefined);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* DEV-G07: a v4 code archive carries no bitmap payload, so bitmap variants are
|
|
169
|
+
* restored from the pinned shards. Only the config's selected tuples are read;
|
|
170
|
+
* each cached shard is re-verified against its pinned identity (archive SHA,
|
|
171
|
+
* manifest SHA, size, dimensions) before any bytes are handed to the generator.
|
|
172
|
+
*/
|
|
173
|
+
function mergePinnedBitmapShardAssets(archiveFiles, effectiveConfig, sourceCatalog, projectRoot, env, fs_) {
|
|
174
|
+
const variants = requiredBitmapVariants(effectiveConfig, sourceCatalog);
|
|
175
|
+
const missing = variants.filter((variant) => !archiveHasVariantAssets(archiveFiles, variant, effectiveConfig.icons.filter((iconId) => Object.entries(effectiveConfig.themes).some(([name, theme]) => theme.styleGroup === variant.styleGroupId &&
|
|
176
|
+
themeHasIcon(effectiveConfig, sourceCatalog ?? defaultCatalog, name, iconId) &&
|
|
177
|
+
(() => {
|
|
178
|
+
const resolved = resolveThemes(effectiveConfig, sourceCatalog);
|
|
179
|
+
return (resolved.ok &&
|
|
180
|
+
resolved.themes.some((item) => item.theme === name &&
|
|
181
|
+
item.variant?.resourceVariantId === variant.resourceVariantId));
|
|
182
|
+
})()))));
|
|
183
|
+
if (missing.length === 0)
|
|
184
|
+
return { ok: true };
|
|
185
|
+
const metadataPath = join(projectRoot, ".moeicons", "install-metadata.json");
|
|
186
|
+
if (!fs_.existsSync(metadataPath)) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
errors: ["bitmap shards require an installed pro artifact; run `moeicons install` first"],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const metadata = parseInstallMetadata(fs_.readFileSync(metadataPath, "utf8"), {
|
|
193
|
+
allowLocalTest: allowLocalTestFromEnv(env),
|
|
194
|
+
});
|
|
195
|
+
if (!metadata) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
errors: ["install metadata is invalid; run 'moeicons install' to repair or reinstall"],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (metadata.delivery?.mode === "icons") {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
errors: [
|
|
205
|
+
"selected bitmap resources are missing for this configuration; run `moeicons install` to download the configured icons, format and imageSize",
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (!metadata.bitmapShards || metadata.bitmapShards.length === 0) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
errors: [
|
|
213
|
+
"bitmap shards are not pinned in install metadata; run 'moeicons install' to repair or reinstall",
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const missingTuples = missing.map((variant) => ({
|
|
218
|
+
styleGroupId: variant.styleGroupId,
|
|
219
|
+
imageSize: { width: variant.imageSize, height: variant.imageSize },
|
|
220
|
+
format: variant.format,
|
|
221
|
+
}));
|
|
222
|
+
const cacheDir = env.MOEICONS_CACHE_DIR ?? join(homedir(), ".moeicons", "cache");
|
|
223
|
+
try {
|
|
224
|
+
const loaded = loadPinnedBitmapShardAssets(metadata.bitmapShards, missingTuples, cacheDir, fs_);
|
|
225
|
+
Object.assign(archiveFiles, loaded.files);
|
|
226
|
+
return { ok: true };
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
errors: [error instanceof Error ? error.message : "bitmap shard verification failed"],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
60
235
|
/**
|
|
61
236
|
* Load installed artifact bytes for generate. Prefers `.moeicons/artifact/<target>/`
|
|
62
237
|
* when that subtree already contains the assets the generator needs (assets target).
|
|
@@ -67,15 +242,45 @@ export function loadArchiveFiles(projectRoot, env, fs_, injected) {
|
|
|
67
242
|
if (injected)
|
|
68
243
|
return { ok: true, files: injected };
|
|
69
244
|
const fixtureTgz = env.MOEICONS_BITMAP_ARCHIVE;
|
|
245
|
+
// DEV-G08: the aggregate-archive fixture is a local-test-only seam. It must
|
|
246
|
+
// never bypass the shard contract in a production context, so a set variable
|
|
247
|
+
// outside a local environment fails closed instead of being silently used.
|
|
248
|
+
if (fixtureTgz && !allowLocalTestFromEnv(env)) {
|
|
249
|
+
return { ok: false, reason: "MOEICONS_BITMAP_ARCHIVE is only honored in a local-test context" };
|
|
250
|
+
}
|
|
70
251
|
if (fixtureTgz && fs_.existsSync(fixtureTgz)) {
|
|
71
252
|
const unpacked = extractTarGz(readBinaryFile(fs_, fixtureTgz), {
|
|
72
|
-
maxEntries:
|
|
73
|
-
maxExpandedBytes:
|
|
253
|
+
maxEntries: ICON_ARCHIVE_MAX_ENTRIES,
|
|
254
|
+
maxExpandedBytes: ICON_ARCHIVE_MAX_EXPANDED_BYTES,
|
|
74
255
|
});
|
|
75
256
|
if (unpacked.errors.length > 0)
|
|
76
257
|
return { ok: false, reason: unpacked.errors[0] ?? "invalid bitmap archive fixture" };
|
|
77
258
|
return { ok: true, files: unpacked.files };
|
|
78
259
|
}
|
|
260
|
+
const selectionMetadataPath = join(projectRoot, ".moeicons", "install-metadata.json");
|
|
261
|
+
if (fs_.existsSync(selectionMetadataPath)) {
|
|
262
|
+
const pinned = parseInstallMetadata(fs_.readFileSync(selectionMetadataPath, "utf8"), {
|
|
263
|
+
allowLocalTest: allowLocalTestFromEnv(env),
|
|
264
|
+
});
|
|
265
|
+
if (pinned?.delivery?.mode === "icons") {
|
|
266
|
+
const files = {};
|
|
267
|
+
for (const [name, hash] of Object.entries(pinned.managedFiles)) {
|
|
268
|
+
if (!name.startsWith(".moeicons/artifact/"))
|
|
269
|
+
continue;
|
|
270
|
+
const target = safeManagedPath(projectRoot, name).target;
|
|
271
|
+
if (!fs_.existsSync(target))
|
|
272
|
+
return { ok: false, reason: `selected resource missing: ${name}; run moeicons install` };
|
|
273
|
+
const bytes = readBinaryFile(fs_, target);
|
|
274
|
+
if (sha256Bytes(bytes) !== hash)
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
reason: `selected resource modified: ${name}; preserve your edit before reinstalling`,
|
|
278
|
+
};
|
|
279
|
+
files[name.slice(".moeicons/artifact/".length)] = bytes;
|
|
280
|
+
}
|
|
281
|
+
return { ok: true, files };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
79
284
|
const meta = readInstallMetadata(projectRoot, fs_.readFileSync, fs_.existsSync);
|
|
80
285
|
if (meta?.target) {
|
|
81
286
|
const subtreeRoot = join(projectRoot, ".moeicons", "artifact", meta.target);
|
|
@@ -105,7 +310,8 @@ export function loadArchiveFiles(projectRoot, env, fs_, injected) {
|
|
|
105
310
|
walk(subtreeRoot, "");
|
|
106
311
|
// assets install lands the raw tree; reuse it. Package subtrees (react/vue/
|
|
107
312
|
// vanilla) do not contain assets/manifest.json — fall through to the cache.
|
|
108
|
-
if (Object.keys(files).length > 0 &&
|
|
313
|
+
if (Object.keys(files).length > 0 &&
|
|
314
|
+
(meta.target === "assets" || archiveHasAssetsManifest(files))) {
|
|
109
315
|
return { ok: true, files };
|
|
110
316
|
}
|
|
111
317
|
}
|
|
@@ -115,8 +321,8 @@ export function loadArchiveFiles(projectRoot, env, fs_, injected) {
|
|
|
115
321
|
const cached = artifactCachePath(cacheDir, meta.artifactVersion, meta.artifactSha256);
|
|
116
322
|
if (fs_.existsSync(cached)) {
|
|
117
323
|
const unpacked = extractTarGz(readBinaryFile(fs_, cached), {
|
|
118
|
-
maxEntries:
|
|
119
|
-
maxExpandedBytes:
|
|
324
|
+
maxEntries: ICON_ARCHIVE_MAX_ENTRIES,
|
|
325
|
+
maxExpandedBytes: ICON_ARCHIVE_MAX_EXPANDED_BYTES,
|
|
120
326
|
});
|
|
121
327
|
if (unpacked.errors.length > 0)
|
|
122
328
|
return { ok: false, reason: unpacked.errors[0] ?? "invalid cached artifact" };
|
|
@@ -125,49 +331,46 @@ export function loadArchiveFiles(projectRoot, env, fs_, injected) {
|
|
|
125
331
|
}
|
|
126
332
|
return { ok: false, reason: "installed artifact not found" };
|
|
127
333
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
* migration (generated files are replaced and the install subtree changes).
|
|
131
|
-
* Interactive TTY prompts for explicit confirmation; non-interactive/JSON mode
|
|
132
|
-
* fails with VALIDATION_ERROR unless `--yes` was passed (the non-interactive
|
|
133
|
-
* UI resolves `--yes` confirmations to true). Fresh projects with no install
|
|
134
|
-
* metadata never prompt.
|
|
135
|
-
*/
|
|
136
|
-
async function confirmTargetSwitch(context, installedTarget, configuredTarget) {
|
|
137
|
-
const message = `Existing install targets "${installedTarget}" but the config now targets "${configuredTarget}". ` +
|
|
138
|
-
"Switching targets is a destructive migration that replaces generated files. Continue?";
|
|
139
|
-
try {
|
|
140
|
-
return (await context.ui.confirm(message, context.signal)) === true;
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
if (isCliError(error) && error.code === "NOT_TTY") {
|
|
144
|
-
throw new CliError("VALIDATION_ERROR", `target switch from "${installedTarget}" to "${configuredTarget}" is destructive and requires confirmation; pass --yes in non-interactive mode`);
|
|
145
|
-
}
|
|
146
|
-
throw error;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
334
|
+
// Keep the public asynchronous command contract while all filesystem work is synchronous.
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
149
336
|
export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
150
337
|
const project = detectProject(context.cwd);
|
|
151
338
|
if (!project)
|
|
152
339
|
return { ok: false, reason: "no-project" };
|
|
153
|
-
const
|
|
340
|
+
const allowLocalTest = allowLocalTestFromEnv(context.env);
|
|
341
|
+
const catalogState = loadInstalledCatalogState(project.root, fs_, { allowLocalTest });
|
|
342
|
+
if (catalogState.status === "invalid") {
|
|
343
|
+
return { ok: false, reason: "validation", errors: [catalogState.message] };
|
|
344
|
+
}
|
|
345
|
+
const sourceCatalog = catalogState.status === "ok" ? catalogState.catalog : undefined;
|
|
346
|
+
const plannedConfigDocument = JSON.stringify(loadConfigDocument(project.root));
|
|
347
|
+
const loaded = readMoeiconsConfig(project.root, sourceCatalog);
|
|
348
|
+
if (loaded.kind === "invalid")
|
|
349
|
+
return { ok: false, reason: "validation", errors: [loaded.message] };
|
|
154
350
|
if (loaded.kind !== "ok")
|
|
155
351
|
return { ok: false, reason: `config state: ${loaded.kind}` };
|
|
156
352
|
const effectiveConfig = options.target
|
|
157
353
|
? { ...loaded.config, target: options.target }
|
|
158
354
|
: loaded.config;
|
|
355
|
+
const plannedMetadataPath = join(project.root, ".moeicons", "install-metadata.json");
|
|
356
|
+
const plannedMetadataText = fs_.existsSync(plannedMetadataPath)
|
|
357
|
+
? fs_.readFileSync(plannedMetadataPath, "utf8")
|
|
358
|
+
: undefined;
|
|
159
359
|
const installedMeta = readInstallMetadata(project.root, fs_.readFileSync, fs_.existsSync);
|
|
360
|
+
if (!installedMeta) {
|
|
361
|
+
return {
|
|
362
|
+
ok: false,
|
|
363
|
+
reason: "generate requires an installed, version-pinned icon artifact; run 'moeicons install' first",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
160
366
|
if (installedMeta?.target && installedMeta.target !== effectiveConfig.target) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
reason: "cancelled",
|
|
166
|
-
errors: [`target switch from "${installedMeta.target}" to "${effectiveConfig.target}" cancelled`],
|
|
167
|
-
};
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
reason: `installed artifact targets "${installedMeta.target}" while config targets "${effectiveConfig.target}"; set config target to "${effectiveConfig.target}" and run 'moeicons install ${loaded.config.tier} --target ${effectiveConfig.target}' before generate`,
|
|
370
|
+
};
|
|
168
371
|
}
|
|
169
372
|
let archiveFiles = options.archiveFiles;
|
|
170
|
-
if (needsArchiveFiles(effectiveConfig) && archiveFiles === undefined) {
|
|
373
|
+
if (needsArchiveFiles(effectiveConfig, sourceCatalog) && archiveFiles === undefined) {
|
|
171
374
|
const loadedArchive = loadArchiveFiles(project.root, context.env, fs_, undefined);
|
|
172
375
|
if (!loadedArchive.ok) {
|
|
173
376
|
return {
|
|
@@ -178,11 +381,22 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
178
381
|
}
|
|
179
382
|
archiveFiles = loadedArchive.files;
|
|
180
383
|
}
|
|
181
|
-
|
|
384
|
+
if (archiveFiles !== undefined) {
|
|
385
|
+
const merged = { ...archiveFiles };
|
|
386
|
+
const shardMerge = mergePinnedBitmapShardAssets(merged, effectiveConfig, sourceCatalog, project.root, context.env, fs_);
|
|
387
|
+
if (!shardMerge.ok)
|
|
388
|
+
return { ok: false, reason: "validation", errors: [...shardMerge.errors] };
|
|
389
|
+
archiveFiles = merged;
|
|
390
|
+
}
|
|
391
|
+
const plan = planGeneratedFiles(effectiveConfig, effectiveConfig.outputDir, {
|
|
392
|
+
...(archiveFiles ? { archiveFiles } : {}),
|
|
393
|
+
...(sourceCatalog ? { catalog: sourceCatalog } : {}),
|
|
394
|
+
});
|
|
182
395
|
if (!plan.ok)
|
|
183
396
|
return { ok: false, reason: "validation", errors: plan.errors };
|
|
184
397
|
const notes = [];
|
|
185
398
|
const sideFiles = [];
|
|
399
|
+
const expectedSideFiles = {};
|
|
186
400
|
try {
|
|
187
401
|
const tw = planTailwindIntegration(project.root, loaded.config.outputDir, {
|
|
188
402
|
noTailwind: options.noTailwind === true,
|
|
@@ -190,6 +404,10 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
190
404
|
});
|
|
191
405
|
notes.push(...tw.notes);
|
|
192
406
|
sideFiles.push(...tw.files);
|
|
407
|
+
for (const file of tw.files)
|
|
408
|
+
expectedSideFiles[relative(project.root, file.path).replace(/\\/g, "/")] = fs_.existsSync(file.path)
|
|
409
|
+
? sha256Bytes(fs_.readFileSync(file.path))
|
|
410
|
+
: undefined;
|
|
193
411
|
}
|
|
194
412
|
catch (error) {
|
|
195
413
|
if (isCliError(error) && error.code === "TAILWIND_VERSION_UNSUPPORTED") {
|
|
@@ -200,6 +418,7 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
200
418
|
const pkgPath = join(project.root, "package.json");
|
|
201
419
|
if (fs_.existsSync(pkgPath)) {
|
|
202
420
|
const pkgSource = fs_.readFileSync(pkgPath, "utf8");
|
|
421
|
+
expectedSideFiles["package.json"] = sha256Bytes(pkgSource);
|
|
203
422
|
const target = effectiveConfig.target;
|
|
204
423
|
const deps = target === "react" || target === "vue"
|
|
205
424
|
? ensureClassMergeDependencies(pkgSource)
|
|
@@ -211,21 +430,35 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
211
430
|
notes.push(...deps.notes);
|
|
212
431
|
if (deps.changed) {
|
|
213
432
|
sideFiles.push({ path: pkgPath, content: deps.nextSource });
|
|
433
|
+
const installCommand = project.packageManager === "pnpm"
|
|
434
|
+
? "pnpm install"
|
|
435
|
+
: project.packageManager === "yarn"
|
|
436
|
+
? "yarn install"
|
|
437
|
+
: project.packageManager === "npm"
|
|
438
|
+
? "npm install"
|
|
439
|
+
: "your package manager's install command";
|
|
440
|
+
notes.push(`package.json changed; run ${installCommand} to update the lockfile and install dependencies`);
|
|
214
441
|
}
|
|
215
442
|
}
|
|
216
443
|
try {
|
|
217
|
-
|
|
444
|
+
return withProjectLockSync(project.root, "reload", () => {
|
|
445
|
+
if (JSON.stringify(loadConfigDocument(project.root)) !== plannedConfigDocument)
|
|
446
|
+
return { ok: false, reason: "config changed while preparing generate; retry" };
|
|
218
447
|
const metadataPath = join(project.root, ".moeicons", "install-metadata.json");
|
|
219
448
|
if (!fs_.existsSync(metadataPath))
|
|
220
449
|
return {
|
|
221
450
|
ok: false,
|
|
222
|
-
reason: "managed install metadata is missing; run repair or reinstall",
|
|
451
|
+
reason: "managed install metadata is missing; run 'moeicons install' to repair or reinstall",
|
|
223
452
|
};
|
|
224
|
-
const metadata = parseInstallMetadata(fs_.readFileSync(metadataPath, "utf8")
|
|
453
|
+
const metadata = parseInstallMetadata(fs_.readFileSync(metadataPath, "utf8"), {
|
|
454
|
+
allowLocalTest,
|
|
455
|
+
});
|
|
456
|
+
if (fs_.readFileSync(metadataPath, "utf8") !== plannedMetadataText)
|
|
457
|
+
return { ok: false, reason: "installation changed while preparing generate; retry" };
|
|
225
458
|
if (!metadata || metadata.tier !== loaded.config.tier)
|
|
226
459
|
return {
|
|
227
460
|
ok: false,
|
|
228
|
-
reason: "managed install metadata is invalid or does not match config tier; run repair or reinstall",
|
|
461
|
+
reason: "managed install metadata is invalid or does not match config tier; run 'moeicons install' to repair or reinstall",
|
|
229
462
|
};
|
|
230
463
|
for (const [managedPath, expected] of Object.entries(metadata.managedFiles)) {
|
|
231
464
|
const absolute = join(project.root, managedPath);
|
|
@@ -237,17 +470,62 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
237
470
|
if (metadata.managedFiles[".moeicons/catalog.json"] !== metadata.catalogSha256)
|
|
238
471
|
return {
|
|
239
472
|
ok: false,
|
|
240
|
-
reason: "managed catalog hash is inconsistent; run repair or reinstall",
|
|
473
|
+
reason: "managed catalog hash is inconsistent; run 'moeicons install' to repair or reinstall",
|
|
241
474
|
};
|
|
475
|
+
const hasInstalledComponents = Object.keys(metadata.managedFiles).some((path) => path.startsWith(`.moeicons/artifact/${effectiveConfig.target}/`) && path.endsWith(".js"));
|
|
476
|
+
if (hasInstalledComponents &&
|
|
477
|
+
(effectiveConfig.target === "react" || effectiveConfig.target === "vue")) {
|
|
478
|
+
const resolved = resolveThemes(effectiveConfig, sourceCatalog);
|
|
479
|
+
if (!resolved.ok)
|
|
480
|
+
return { ok: false, reason: resolved.errors.join("; ") };
|
|
481
|
+
for (const iconId of effectiveConfig.icons) {
|
|
482
|
+
for (const theme of resolved.themes) {
|
|
483
|
+
const selected = resolveIconTheme(effectiveConfig, sourceCatalog, theme.theme, iconId);
|
|
484
|
+
if (!selected)
|
|
485
|
+
return {
|
|
486
|
+
ok: false,
|
|
487
|
+
reason: `icon "${iconId}" has no selected variant for theme "${theme.theme}"`,
|
|
488
|
+
};
|
|
489
|
+
const group = effectiveConfig.themes[selected].styleGroup;
|
|
490
|
+
const kind = resolved.themes.find((candidate) => candidate.entry.styleGroup === group)?.kind;
|
|
491
|
+
if (kind === "bitmap")
|
|
492
|
+
continue;
|
|
493
|
+
const suffix = effectiveConfig.target === "vue" ? ".vue.js" : ".js";
|
|
494
|
+
const rel = `.moeicons/artifact/${effectiveConfig.target}/${group}/${toProxyName(iconId)}${suffix}`;
|
|
495
|
+
if (!fs_.existsSync(join(project.root, rel))) {
|
|
496
|
+
return {
|
|
497
|
+
ok: false,
|
|
498
|
+
reason: `icon "${iconId}" is configured but its ${group} component is not installed (${rel}); run 'moeicons install' before generate`,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
242
504
|
const outputPrefix = loaded.config.outputDir.replace(/\\/g, "/").replace(/\/$/, "") + "/";
|
|
505
|
+
const previousPrefix = (metadata.generatedOutputDir ?? loaded.config.outputDir)
|
|
506
|
+
.replace(/\\/g, "/")
|
|
507
|
+
.replace(/\/$/, "") + "/";
|
|
243
508
|
const generated = Object.fromEntries(plan.files.map((file) => [file.path.replace(/\\/g, "/"), file.content]));
|
|
509
|
+
for (const generatedPath of Object.keys(generated)) {
|
|
510
|
+
if (fs_.existsSync(join(project.root, generatedPath)) &&
|
|
511
|
+
!(generatedPath in metadata.managedFiles)) {
|
|
512
|
+
return {
|
|
513
|
+
ok: false,
|
|
514
|
+
reason: `generated path collides with an unowned user file: ${generatedPath}`,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
244
518
|
const nextManaged = {};
|
|
245
519
|
for (const [managedPath, hash] of Object.entries(metadata.managedFiles))
|
|
246
|
-
if (!managedPath.startsWith(outputPrefix))
|
|
520
|
+
if (!managedPath.startsWith(outputPrefix) && !managedPath.startsWith(previousPrefix))
|
|
247
521
|
nextManaged[managedPath] = hash;
|
|
248
522
|
for (const [path, content] of Object.entries(generated))
|
|
249
523
|
nextManaged[path] = sha256Bytes(content);
|
|
250
|
-
const nextMetadata = {
|
|
524
|
+
const nextMetadata = {
|
|
525
|
+
...metadata,
|
|
526
|
+
managedFiles: nextManaged,
|
|
527
|
+
generatedOutputDir: loaded.config.outputDir,
|
|
528
|
+
};
|
|
251
529
|
const writes = { ...generated };
|
|
252
530
|
for (const file of sideFiles) {
|
|
253
531
|
const absolute = resolve(file.path);
|
|
@@ -257,8 +535,17 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
257
535
|
writes[rel] = file.content;
|
|
258
536
|
}
|
|
259
537
|
writes[".moeicons/install-metadata.json"] = serializeInstallMetadata(nextMetadata);
|
|
260
|
-
const stale = Object.keys(metadata.managedFiles).filter((path) => path.startsWith(outputPrefix)
|
|
261
|
-
|
|
538
|
+
const stale = Object.keys(metadata.managedFiles).filter((path) => (path.startsWith(outputPrefix) || path.startsWith(previousPrefix)) &&
|
|
539
|
+
!(path in generated));
|
|
540
|
+
const expectedSha256 = {
|
|
541
|
+
...metadata.managedFiles,
|
|
542
|
+
...expectedSideFiles,
|
|
543
|
+
".moeicons/install-metadata.json": sha256Bytes(fs_.readFileSync(metadataPath)),
|
|
544
|
+
};
|
|
545
|
+
for (const path of Object.keys(generated))
|
|
546
|
+
if (!(path in expectedSha256))
|
|
547
|
+
expectedSha256[path] = undefined;
|
|
548
|
+
executeManagedReconcile(project.root, writes, stale, fs_, { expectedSha256 });
|
|
262
549
|
return {
|
|
263
550
|
ok: true,
|
|
264
551
|
files: plan.files.map((file) => file.path),
|
|
@@ -266,18 +553,7 @@ export async function runGenerateUseCase(context, fs_, options = {}) {
|
|
|
266
553
|
? { warnings: [...loaded.warnings, ...notes] }
|
|
267
554
|
: {}),
|
|
268
555
|
};
|
|
269
|
-
}
|
|
270
|
-
executeGeneratedFilesDir(plan.files, project.root, loaded.config.outputDir, fs_);
|
|
271
|
-
for (const file of sideFiles) {
|
|
272
|
-
fs_.writeFileSync(file.path, file.content);
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
ok: true,
|
|
276
|
-
files: plan.files.map((file) => file.path),
|
|
277
|
-
...(loaded.warnings.length > 0 || notes.length > 0
|
|
278
|
-
? { warnings: [...loaded.warnings, ...notes] }
|
|
279
|
-
: {}),
|
|
280
|
-
};
|
|
556
|
+
});
|
|
281
557
|
}
|
|
282
558
|
catch (error) {
|
|
283
559
|
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type IconCatalog } from "../catalog/catalog.js";
|
|
2
|
+
import type { MoeiconsConfigFile } from "../project/config.js";
|
|
3
|
+
/** A theme owns a variant only when it is selected and shipped by the catalog. */
|
|
4
|
+
export declare function themeHasIcon(config: MoeiconsConfigFile, catalog: IconCatalog, theme: string, iconId: string): boolean;
|
|
5
|
+
/** Stable fallback: requested theme, default theme, then theme keys in ASCII order.
|
|
6
|
+
* Unregistered icons and an empty configured variant set never resolve.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveIconTheme(config: MoeiconsConfigFile, catalog: IconCatalog, requested: string, iconId: string): string | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { findCatalogIcon } from "../catalog/catalog.js";
|
|
2
|
+
/** A theme owns a variant only when it is selected and shipped by the catalog. */
|
|
3
|
+
export function themeHasIcon(config, catalog, theme, iconId) {
|
|
4
|
+
const entry = config.themes[theme];
|
|
5
|
+
return Boolean(entry && config.icons.includes(iconId) &&
|
|
6
|
+
(entry.icons === undefined || entry.icons.includes(iconId)) &&
|
|
7
|
+
findCatalogIcon(iconId, catalog)?.availableIn.includes(entry.styleGroup));
|
|
8
|
+
}
|
|
9
|
+
/** Stable fallback: requested theme, default theme, then theme keys in ASCII order.
|
|
10
|
+
* Unregistered icons and an empty configured variant set never resolve.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveIconTheme(config, catalog, requested, iconId) {
|
|
13
|
+
if (themeHasIcon(config, catalog, requested, iconId))
|
|
14
|
+
return requested;
|
|
15
|
+
if (config.missingIconPolicy !== "fallback")
|
|
16
|
+
return undefined;
|
|
17
|
+
const candidates = [config.defaultTheme, ...Object.keys(config.themes).sort()];
|
|
18
|
+
return candidates.find((theme) => themeHasIcon(config, catalog, theme, iconId));
|
|
19
|
+
}
|
package/dist/core/install.d.ts
CHANGED
|
@@ -15,7 +15,12 @@ export type InstallResult = {
|
|
|
15
15
|
readonly artifactVersion: string;
|
|
16
16
|
readonly descriptorSha256: string;
|
|
17
17
|
readonly catalogSha256: string;
|
|
18
|
+
readonly metadataSha256: string;
|
|
18
19
|
readonly cacheHit: boolean;
|
|
20
|
+
readonly downloadMode?: "icons" | "full";
|
|
21
|
+
readonly downloadNotes?: readonly string[];
|
|
22
|
+
readonly networkBytes?: number;
|
|
23
|
+
readonly selectedFiles?: number;
|
|
19
24
|
} | {
|
|
20
25
|
readonly ok: false;
|
|
21
26
|
readonly reason: "no-project";
|
|
@@ -39,6 +44,10 @@ export type InstallResult = {
|
|
|
39
44
|
readonly ok: false;
|
|
40
45
|
readonly reason: "offline-no-cache";
|
|
41
46
|
readonly message: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly ok: false;
|
|
49
|
+
readonly reason: "disk-full";
|
|
50
|
+
readonly message: string;
|
|
42
51
|
} | {
|
|
43
52
|
readonly ok: false;
|
|
44
53
|
readonly reason: "validation";
|
|
@@ -52,6 +61,11 @@ export interface InstallUseCaseDeps {
|
|
|
52
61
|
readonly fs: TransactionalFs;
|
|
53
62
|
readonly download: Omit<FreeDownloadIo, "signal">;
|
|
54
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* P0-3: the project `types.ts` must re-export a type that actually exists in the
|
|
66
|
+
* installed `moe-icons` package. Assets have no component type, so it is empty.
|
|
67
|
+
*/
|
|
68
|
+
export declare function typesReexport(tier: "free" | "pro", target: Target, outputDir?: string): string;
|
|
55
69
|
/**
|
|
56
70
|
* Free install: resolve the catalog sourceVersion tag, download/verify the
|
|
57
71
|
* GitHub Release (or a local release fixture), verify the selected target's
|