@motionscript/cli 0.0.0-stage → 0.1.0-alpha.0
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/CHANGELOG.md +5 -0
- package/LICENSE +201 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +78 -0
- package/dist/cli.js.map +1 -0
- package/dist/registry/add.d.ts +8 -0
- package/dist/registry/add.d.ts.map +1 -0
- package/dist/registry/add.js +93 -0
- package/dist/registry/add.js.map +1 -0
- package/dist/registry/config.d.ts +28 -0
- package/dist/registry/config.d.ts.map +1 -0
- package/dist/registry/config.js +55 -0
- package/dist/registry/config.js.map +1 -0
- package/dist/registry/diff.d.ts +16 -0
- package/dist/registry/diff.d.ts.map +1 -0
- package/dist/registry/diff.js +83 -0
- package/dist/registry/diff.js.map +1 -0
- package/dist/registry/fetch.d.ts +16 -0
- package/dist/registry/fetch.d.ts.map +1 -0
- package/dist/registry/fetch.js +59 -0
- package/dist/registry/fetch.js.map +1 -0
- package/dist/registry/init.d.ts +15 -0
- package/dist/registry/init.d.ts.map +1 -0
- package/dist/registry/init.js +69 -0
- package/dist/registry/init.js.map +1 -0
- package/dist/registry/install.d.ts +37 -0
- package/dist/registry/install.d.ts.map +1 -0
- package/dist/registry/install.js +105 -0
- package/dist/registry/install.js.map +1 -0
- package/dist/registry/list.d.ts +13 -0
- package/dist/registry/list.d.ts.map +1 -0
- package/dist/registry/list.js +61 -0
- package/dist/registry/list.js.map +1 -0
- package/dist/registry/lock.d.ts +73 -0
- package/dist/registry/lock.d.ts.map +1 -0
- package/dist/registry/lock.js +104 -0
- package/dist/registry/lock.js.map +1 -0
- package/dist/registry/package-manager.d.ts +28 -0
- package/dist/registry/package-manager.d.ts.map +1 -0
- package/dist/registry/package-manager.js +61 -0
- package/dist/registry/package-manager.js.map +1 -0
- package/dist/registry/protocol.d.ts +135 -0
- package/dist/registry/protocol.d.ts.map +1 -0
- package/dist/registry/protocol.js +88 -0
- package/dist/registry/protocol.js.map +1 -0
- package/package.json +64 -3
- package/README.md +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/registry/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport kleur from 'kleur';\nimport type minimist from 'minimist';\nimport { loadComponentsConfig } from './config.js';\nimport { fetchRegistryItem } from './fetch.js';\nimport { hashContent, loadLock } from './lock.js';\n\n/**\n * `ms diff [name]` — what upstream changed under your feet.\n *\n * The counterpart to `ms list`: that one answers \"did I change this\", this one\n * answers \"did they\". Both are only answerable because the lockfile recorded\n * the bytes that were written, which is what gives a three-way comparison a\n * middle term — yours, theirs, and the common ancestor they diverged from.\n *\n * Deliberately prints a summary rather than a unified diff. A component is\n * files on disk in a project that already has a diff tool and a version control\n * system with better ones; what the CLI uniquely knows is *which* files moved\n * and in which direction, and that is what it says.\n */\nexport async function runDiff(projectRoot: string, argv: minimist.ParsedArgs): Promise<void> {\n const lock = loadLock(projectRoot);\n const config = loadComponentsConfig(projectRoot);\n const defaultRegistryUrl = typeof argv.registry === 'string' ? argv.registry : undefined;\n\n const requested = argv._.slice(1).filter(Boolean);\n const names = requested.length > 0 ? requested : Object.keys(lock.components);\n\n if (names.length === 0) {\n console.log(kleur.dim('No components installed.'));\n return;\n }\n\n for (const name of names) {\n const entry = lock.components[name];\n if (!entry) {\n console.log(`${kleur.bold(name)} ${kleur.red('not installed')}`);\n continue;\n }\n\n let upstream;\n try {\n upstream = await fetchRegistryItem(name, config, defaultRegistryUrl);\n } catch (error) {\n console.log(\n `${kleur.bold(name)} ${kleur.red('unreachable')} ${kleur.dim(String(error))}`,\n );\n continue;\n }\n\n const header = [kleur.bold(name)];\n if (entry.version || upstream.version) {\n header.push(kleur.dim(`${entry.version ?? '?'} → ${upstream.version ?? '?'}`));\n }\n console.log(header.join(' '));\n\n const seen = new Set<string>();\n for (const file of upstream.files) {\n seen.add(file.path);\n const locked = entry.files[file.path];\n if (!locked) {\n console.log(` ${kleur.green('+')} ${file.path} ${kleur.dim('(new upstream)')}`);\n continue;\n }\n const onDisk = path.join(projectRoot, locked.target);\n const local = fs.existsSync(onDisk) ? fs.readFileSync(onDisk, 'utf8') : null;\n const upstreamChanged = hashContent(file.content) !== locked.installed;\n const localChanged = local !== null && hashContent(local) !== locked.installed;\n\n if (local === null) {\n console.log(` ${kleur.red('-')} ${file.path} ${kleur.dim('(missing locally)')}`);\n } else if (upstreamChanged && localChanged) {\n // The only state that needs a person: both moved, so\n // re-installing loses work and not re-installing keeps a bug.\n console.log(` ${kleur.yellow('!')} ${file.path} ${kleur.dim('(changed both sides)')}`);\n } else if (upstreamChanged) {\n console.log(` ${kleur.cyan('↑')} ${file.path} ${kleur.dim('(changed upstream)')}`);\n } else if (localChanged) {\n console.log(` ${kleur.yellow('~')} ${file.path} ${kleur.dim('(changed locally)')}`);\n }\n }\n\n for (const declared of Object.keys(entry.files)) {\n if (!seen.has(declared)) {\n console.log(` ${kleur.dim('×')} ${declared} ${kleur.dim('(dropped upstream)')}`);\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ComponentsConfig, type RegistryIndexEntry, type RegistryItem, type ResolvedRegistryTarget } from './protocol.js';
|
|
2
|
+
/** Resolve a (possibly namespaced) name to its item URL, headers and bare name. */
|
|
3
|
+
export declare function resolveItemUrl(name: string, config: ComponentsConfig | null, defaultRegistryUrl?: string): ResolvedRegistryTarget;
|
|
4
|
+
/**
|
|
5
|
+
* Read a registry URL's JSON content. Remote (`http(s)://`) URLs are
|
|
6
|
+
* fetched; anything else is treated as a local filesystem path (a bare path
|
|
7
|
+
* or a `file:` URL) and read directly. This isn't just a testing
|
|
8
|
+
* convenience — it's genuinely how a private/unpublished registry is
|
|
9
|
+
* served, and it's how this monorepo's own e2e package populates itself
|
|
10
|
+
* from a locally-built `packages/site/public/r` without needing a deployed
|
|
11
|
+
* site.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readRegistrySource(url: string, headers?: Record<string, string>): Promise<unknown>;
|
|
14
|
+
export declare function fetchRegistryIndex(defaultRegistryUrl?: string): Promise<RegistryIndexEntry[]>;
|
|
15
|
+
export declare function fetchRegistryItem(name: string, config: ComponentsConfig | null, defaultRegistryUrl?: string): Promise<RegistryItem>;
|
|
16
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/registry/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAIH,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC9B,MAAM,eAAe,CAAC;AAmBvB,mFAAmF;AACnF,wBAAgB,cAAc,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,kBAAkB,GAAE,MAA6B,GAClD,sBAAsB,CAExB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAaxG;AAED,wBAAsB,kBAAkB,CACpC,kBAAkB,GAAE,MAA6B,GAClD,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAM/B;AAED,wBAAsB,iBAAiB,CACnC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,kBAAkB,GAAE,MAA6B,GAClD,OAAO,CAAC,YAAY,CAAC,CAKvB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { assertRegistryItem, joinRegistryPath, resolveItemUrl as resolvePure, } from './protocol.js';
|
|
4
|
+
import { DEFAULT_REGISTRY_URL } from './config.js';
|
|
5
|
+
/**
|
|
6
|
+
* Reading a registry: the transport half.
|
|
7
|
+
*
|
|
8
|
+
* Everything about *which* URL a name resolves to — namespaces, the `{name}`
|
|
9
|
+
* template, `${VAR}` expansion in headers — lives in `./protocol.js`, kept
|
|
10
|
+
* pure and separate. That split is deliberate rather than tidy: an editor
|
|
11
|
+
* installing `@acme/gauge` and `ms add @acme/gauge` must reach the same URL,
|
|
12
|
+
* and two implementations of one rule is a bug with nowhere single to look.
|
|
13
|
+
* What is left here is `fs` and `fetch`, which is exactly the part a
|
|
14
|
+
* browser-based client cannot share.
|
|
15
|
+
*/
|
|
16
|
+
function isRemote(url) {
|
|
17
|
+
return /^https?:\/\//i.test(url);
|
|
18
|
+
}
|
|
19
|
+
/** Resolve a (possibly namespaced) name to its item URL, headers and bare name. */
|
|
20
|
+
export function resolveItemUrl(name, config, defaultRegistryUrl = DEFAULT_REGISTRY_URL) {
|
|
21
|
+
return resolvePure(name, config, defaultRegistryUrl, (key) => process.env[key]);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read a registry URL's JSON content. Remote (`http(s)://`) URLs are
|
|
25
|
+
* fetched; anything else is treated as a local filesystem path (a bare path
|
|
26
|
+
* or a `file:` URL) and read directly. This isn't just a testing
|
|
27
|
+
* convenience — it's genuinely how a private/unpublished registry is
|
|
28
|
+
* served, and it's how this monorepo's own e2e package populates itself
|
|
29
|
+
* from a locally-built `packages/site/public/r` without needing a deployed
|
|
30
|
+
* site.
|
|
31
|
+
*/
|
|
32
|
+
export async function readRegistrySource(url, headers) {
|
|
33
|
+
if (isRemote(url)) {
|
|
34
|
+
const response = await fetch(url, { headers });
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
return response.json();
|
|
39
|
+
}
|
|
40
|
+
const filePath = url.startsWith('file:') ? fileURLToPath(url) : url;
|
|
41
|
+
if (!fs.existsSync(filePath)) {
|
|
42
|
+
throw new Error(`Registry source not found: ${filePath}`);
|
|
43
|
+
}
|
|
44
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
export async function fetchRegistryIndex(defaultRegistryUrl = DEFAULT_REGISTRY_URL) {
|
|
47
|
+
const data = await readRegistrySource(joinRegistryPath(defaultRegistryUrl, 'registry.json'));
|
|
48
|
+
if (!Array.isArray(data)) {
|
|
49
|
+
throw new Error('Malformed registry index (expected an array).');
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
export async function fetchRegistryItem(name, config, defaultRegistryUrl = DEFAULT_REGISTRY_URL) {
|
|
54
|
+
const { url, headers, itemName } = resolveItemUrl(name, config, defaultRegistryUrl);
|
|
55
|
+
const data = await readRegistrySource(url, headers);
|
|
56
|
+
assertRegistryItem(data, itemName);
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/registry/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACH,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,IAAI,WAAW,GAKhC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;GAUG;AAEH,SAAS,QAAQ,CAAC,GAAW;IACzB,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAC1B,IAAY,EACZ,MAA+B,EAC/B,qBAA6B,oBAAoB;IAEjD,OAAO,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW,EAAE,OAAgC;IAClF,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACpE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,8BAA8B,QAAQ,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAY,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACpC,qBAA6B,oBAAoB;IAEjD,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,eAAe,CAAC,CAAC,CAAC;IAC7F,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,IAA4B,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,IAAY,EACZ,MAA+B,EAC/B,qBAA6B,oBAAoB;IAEjD,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACpD,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC;AAChB,CAAC","sourcesContent":["import fs from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport {\n assertRegistryItem,\n joinRegistryPath,\n resolveItemUrl as resolvePure,\n type ComponentsConfig,\n type RegistryIndexEntry,\n type RegistryItem,\n type ResolvedRegistryTarget,\n} from './protocol.js';\nimport { DEFAULT_REGISTRY_URL } from './config.js';\n\n/**\n * Reading a registry: the transport half.\n *\n * Everything about *which* URL a name resolves to — namespaces, the `{name}`\n * template, `${VAR}` expansion in headers — lives in `./protocol.js`, kept\n * pure and separate. That split is deliberate rather than tidy: an editor\n * installing `@acme/gauge` and `ms add @acme/gauge` must reach the same URL,\n * and two implementations of one rule is a bug with nowhere single to look.\n * What is left here is `fs` and `fetch`, which is exactly the part a\n * browser-based client cannot share.\n */\n\nfunction isRemote(url: string): boolean {\n return /^https?:\\/\\//i.test(url);\n}\n\n/** Resolve a (possibly namespaced) name to its item URL, headers and bare name. */\nexport function resolveItemUrl(\n name: string,\n config: ComponentsConfig | null,\n defaultRegistryUrl: string = DEFAULT_REGISTRY_URL,\n): ResolvedRegistryTarget {\n return resolvePure(name, config, defaultRegistryUrl, (key) => process.env[key]);\n}\n\n/**\n * Read a registry URL's JSON content. Remote (`http(s)://`) URLs are\n * fetched; anything else is treated as a local filesystem path (a bare path\n * or a `file:` URL) and read directly. This isn't just a testing\n * convenience — it's genuinely how a private/unpublished registry is\n * served, and it's how this monorepo's own e2e package populates itself\n * from a locally-built `packages/site/public/r` without needing a deployed\n * site.\n */\nexport async function readRegistrySource(url: string, headers?: Record<string, string>): Promise<unknown> {\n if (isRemote(url)) {\n const response = await fetch(url, { headers });\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n return response.json();\n }\n const filePath = url.startsWith('file:') ? fileURLToPath(url) : url;\n if (!fs.existsSync(filePath)) {\n throw new Error(`Registry source not found: ${filePath}`);\n }\n return JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown;\n}\n\nexport async function fetchRegistryIndex(\n defaultRegistryUrl: string = DEFAULT_REGISTRY_URL,\n): Promise<RegistryIndexEntry[]> {\n const data = await readRegistrySource(joinRegistryPath(defaultRegistryUrl, 'registry.json'));\n if (!Array.isArray(data)) {\n throw new Error('Malformed registry index (expected an array).');\n }\n return data as RegistryIndexEntry[];\n}\n\nexport async function fetchRegistryItem(\n name: string,\n config: ComponentsConfig | null,\n defaultRegistryUrl: string = DEFAULT_REGISTRY_URL,\n): Promise<RegistryItem> {\n const { url, headers, itemName } = resolveItemUrl(name, config, defaultRegistryUrl);\n const data = await readRegistrySource(url, headers);\n assertRegistryItem(data, itemName);\n return data;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type minimist from 'minimist';
|
|
2
|
+
import type { ComponentsConfig } from './protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* `ms init` — write `motionscript.json` and wire a "@/components/*" path alias
|
|
5
|
+
* into tsconfig.json so the editor/typechecker resolves it. Only TypeScript
|
|
6
|
+
* reads this edit; a bundler resolves the alias from its own config.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately diverges from shadcn's own `init` (which *requires* an
|
|
9
|
+
* existing tsconfig path alias and errors if one is missing): a fresh
|
|
10
|
+
* Motion Script project has none, so requiring it here would break its first
|
|
11
|
+
* `ms add`. Uses jsonc-parser so existing comments/formatting in a
|
|
12
|
+
* hand-edited tsconfig.json survive, unlike a JSON.parse/stringify round-trip.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runInit(projectRoot: string, argv: minimist.ParsedArgs): Promise<ComponentsConfig>;
|
|
15
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/registry/init.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AASrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwCvG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import kleur from 'kleur';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import { modify, applyEdits } from 'jsonc-parser';
|
|
6
|
+
import { componentsConfigExists, defaultComponentsConfig, writeComponentsConfig, DEFAULT_COMPONENTS_ALIAS, DEFAULT_COMPONENTS_PATH, COMPONENTS_CONFIG_FILENAME, } from './config.js';
|
|
7
|
+
/**
|
|
8
|
+
* `ms init` — write `motionscript.json` and wire a "@/components/*" path alias
|
|
9
|
+
* into tsconfig.json so the editor/typechecker resolves it. Only TypeScript
|
|
10
|
+
* reads this edit; a bundler resolves the alias from its own config.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately diverges from shadcn's own `init` (which *requires* an
|
|
13
|
+
* existing tsconfig path alias and errors if one is missing): a fresh
|
|
14
|
+
* Motion Script project has none, so requiring it here would break its first
|
|
15
|
+
* `ms add`. Uses jsonc-parser so existing comments/formatting in a
|
|
16
|
+
* hand-edited tsconfig.json survive, unlike a JSON.parse/stringify round-trip.
|
|
17
|
+
*/
|
|
18
|
+
export async function runInit(projectRoot, argv) {
|
|
19
|
+
if (componentsConfigExists(projectRoot) && !argv.force) {
|
|
20
|
+
console.log(kleur.yellow(`${COMPONENTS_CONFIG_FILENAME} already exists. Pass --force to overwrite.`));
|
|
21
|
+
return JSON.parse(fs.readFileSync(path.join(projectRoot, COMPONENTS_CONFIG_FILENAME), 'utf8'));
|
|
22
|
+
}
|
|
23
|
+
const nonInteractive = Boolean(argv.yes);
|
|
24
|
+
const answers = nonInteractive
|
|
25
|
+
? { componentsPath: DEFAULT_COMPONENTS_PATH, alias: DEFAULT_COMPONENTS_ALIAS }
|
|
26
|
+
: await prompts([
|
|
27
|
+
{
|
|
28
|
+
type: 'text',
|
|
29
|
+
name: 'componentsPath',
|
|
30
|
+
message: 'Where should added component source live?',
|
|
31
|
+
initial: DEFAULT_COMPONENTS_PATH,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'text',
|
|
35
|
+
name: 'alias',
|
|
36
|
+
message: 'Import alias for components',
|
|
37
|
+
initial: DEFAULT_COMPONENTS_ALIAS,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
if (!nonInteractive && answers.componentsPath === undefined) {
|
|
41
|
+
throw new Error('Aborted.');
|
|
42
|
+
}
|
|
43
|
+
const config = defaultComponentsConfig();
|
|
44
|
+
config.paths.components = answers.componentsPath;
|
|
45
|
+
config.aliases.components = answers.alias;
|
|
46
|
+
writeComponentsConfig(projectRoot, config);
|
|
47
|
+
console.log(kleur.green(`✓ Wrote ${COMPONENTS_CONFIG_FILENAME}`));
|
|
48
|
+
addTsconfigAlias(projectRoot, config.aliases.components, config.paths.components);
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
/** Add `"<alias>/*": ["./<dir>/*"]` under compilerOptions.paths, preserving comments/formatting. */
|
|
52
|
+
function addTsconfigAlias(projectRoot, alias, componentsDir) {
|
|
53
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
54
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
55
|
+
console.log(kleur.yellow('No tsconfig.json found — skipping path alias setup.'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const original = fs.readFileSync(tsconfigPath, 'utf8');
|
|
59
|
+
const aliasKey = `${alias}/*`;
|
|
60
|
+
const aliasValue = [`./${componentsDir.replace(/\\/g, '/')}/*`];
|
|
61
|
+
const edits = modify(original, ['compilerOptions', 'paths', aliasKey], aliasValue, {
|
|
62
|
+
formattingOptions: { insertSpaces: true, tabSize: 4 },
|
|
63
|
+
});
|
|
64
|
+
if (edits.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
fs.writeFileSync(tsconfigPath, applyEdits(original, edits));
|
|
67
|
+
console.log(kleur.green(`✓ Added "${aliasKey}" to tsconfig.json`));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/registry/init.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport kleur from 'kleur';\nimport prompts from 'prompts';\nimport { modify, applyEdits } from 'jsonc-parser';\nimport type minimist from 'minimist';\nimport {\n componentsConfigExists,\n defaultComponentsConfig,\n writeComponentsConfig,\n DEFAULT_COMPONENTS_ALIAS,\n DEFAULT_COMPONENTS_PATH,\n COMPONENTS_CONFIG_FILENAME,\n} from './config.js';\nimport type { ComponentsConfig } from './protocol.js';\n\n/**\n * `ms init` — write `motionscript.json` and wire a \"@/components/*\" path alias\n * into tsconfig.json so the editor/typechecker resolves it. Only TypeScript\n * reads this edit; a bundler resolves the alias from its own config.\n *\n * Deliberately diverges from shadcn's own `init` (which *requires* an\n * existing tsconfig path alias and errors if one is missing): a fresh\n * Motion Script project has none, so requiring it here would break its first\n * `ms add`. Uses jsonc-parser so existing comments/formatting in a\n * hand-edited tsconfig.json survive, unlike a JSON.parse/stringify round-trip.\n */\nexport async function runInit(projectRoot: string, argv: minimist.ParsedArgs): Promise<ComponentsConfig> {\n if (componentsConfigExists(projectRoot) && !argv.force) {\n console.log(kleur.yellow(`${COMPONENTS_CONFIG_FILENAME} already exists. Pass --force to overwrite.`));\n return JSON.parse(\n fs.readFileSync(path.join(projectRoot, COMPONENTS_CONFIG_FILENAME), 'utf8'),\n ) as ComponentsConfig;\n }\n\n const nonInteractive = Boolean(argv.yes);\n const answers = nonInteractive\n ? { componentsPath: DEFAULT_COMPONENTS_PATH, alias: DEFAULT_COMPONENTS_ALIAS }\n : await prompts([\n {\n type: 'text',\n name: 'componentsPath',\n message: 'Where should added component source live?',\n initial: DEFAULT_COMPONENTS_PATH,\n },\n {\n type: 'text',\n name: 'alias',\n message: 'Import alias for components',\n initial: DEFAULT_COMPONENTS_ALIAS,\n },\n ]);\n\n if (!nonInteractive && answers.componentsPath === undefined) {\n throw new Error('Aborted.');\n }\n\n const config = defaultComponentsConfig();\n config.paths.components = answers.componentsPath;\n config.aliases.components = answers.alias;\n\n writeComponentsConfig(projectRoot, config);\n console.log(kleur.green(`✓ Wrote ${COMPONENTS_CONFIG_FILENAME}`));\n\n addTsconfigAlias(projectRoot, config.aliases.components, config.paths.components);\n\n return config;\n}\n\n/** Add `\"<alias>/*\": [\"./<dir>/*\"]` under compilerOptions.paths, preserving comments/formatting. */\nfunction addTsconfigAlias(projectRoot: string, alias: string, componentsDir: string): void {\n const tsconfigPath = path.join(projectRoot, 'tsconfig.json');\n if (!fs.existsSync(tsconfigPath)) {\n console.log(kleur.yellow('No tsconfig.json found — skipping path alias setup.'));\n return;\n }\n\n const original = fs.readFileSync(tsconfigPath, 'utf8');\n const aliasKey = `${alias}/*`;\n const aliasValue = [`./${componentsDir.replace(/\\\\/g, '/')}/*`];\n\n const edits = modify(original, ['compilerOptions', 'paths', aliasKey], aliasValue, {\n formattingOptions: { insertSpaces: true, tabSize: 4 },\n });\n if (edits.length === 0) return;\n\n fs.writeFileSync(tsconfigPath, applyEdits(original, edits));\n console.log(kleur.green(`✓ Added \"${aliasKey}\" to tsconfig.json`));\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ComponentsConfig, RegistryItem } from './protocol.js';
|
|
2
|
+
import { type LockedComponent } from './lock.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a list of requested component names into an ordered, deduped
|
|
5
|
+
* install list, recursively pulling in each item's `registryDependencies`
|
|
6
|
+
* before the item itself (so a dependency's files land first).
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveComponents(names: string[], config: ComponentsConfig | null, defaultRegistryUrl: string | undefined): Promise<RegistryItem[]>;
|
|
9
|
+
export interface WriteResult {
|
|
10
|
+
written: string[];
|
|
11
|
+
skipped: string[];
|
|
12
|
+
/** What actually landed, by the path the item declared, for the lockfile. */
|
|
13
|
+
recorded: Record<string, {
|
|
14
|
+
target: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Write every file in `item` under `<paths.components>/`, at whatever path
|
|
20
|
+
* each file itself declares — prompting before overwriting an existing file
|
|
21
|
+
* unless `force` is set.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately does *not* nest every item under its own `<item.name>/`
|
|
24
|
+
* subfolder: a simple single-file component declares one file at
|
|
25
|
+
* `"code.ts"` and lands flat (`src/components/code.ts`), while a component
|
|
26
|
+
* that genuinely needs several files declares them nested
|
|
27
|
+
* (`"line-chart/chart.ts"`, `"line-chart/axis.ts"`) and lands in a real
|
|
28
|
+
* subfolder. The registry item's own file paths are the only thing that
|
|
29
|
+
* decides the shape — this function just joins them onto the components root.
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeComponentFiles(item: RegistryItem, config: ComponentsConfig, projectRoot: string, options?: {
|
|
32
|
+
force?: boolean;
|
|
33
|
+
previous?: LockedComponent;
|
|
34
|
+
}): Promise<WriteResult>;
|
|
35
|
+
/** Merge every installed item's npm `dependencies` ("pkg@range" strings) into one deduped map. */
|
|
36
|
+
export declare function collectDependencies(items: RegistryItem[]): Record<string, string>;
|
|
37
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/registry/install.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5D;;;;GAIG;AACH,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAAC,YAAY,EAAE,CAAC,CAkBzB;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACrC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAO,GAC9D,OAAO,CAAC,WAAW,CAAC,CAkDtB;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAajF"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { fetchRegistryItem } from './fetch.js';
|
|
5
|
+
import { fileState } from './lock.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a list of requested component names into an ordered, deduped
|
|
8
|
+
* install list, recursively pulling in each item's `registryDependencies`
|
|
9
|
+
* before the item itself (so a dependency's files land first).
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveComponents(names, config, defaultRegistryUrl) {
|
|
12
|
+
const resolved = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
async function visit(name) {
|
|
15
|
+
if (seen.has(name))
|
|
16
|
+
return;
|
|
17
|
+
seen.add(name);
|
|
18
|
+
const item = await fetchRegistryItem(name, config, defaultRegistryUrl);
|
|
19
|
+
for (const dep of item.registryDependencies ?? []) {
|
|
20
|
+
await visit(dep);
|
|
21
|
+
}
|
|
22
|
+
resolved.push(item);
|
|
23
|
+
}
|
|
24
|
+
for (const name of names) {
|
|
25
|
+
await visit(name);
|
|
26
|
+
}
|
|
27
|
+
return resolved;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Write every file in `item` under `<paths.components>/`, at whatever path
|
|
31
|
+
* each file itself declares — prompting before overwriting an existing file
|
|
32
|
+
* unless `force` is set.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately does *not* nest every item under its own `<item.name>/`
|
|
35
|
+
* subfolder: a simple single-file component declares one file at
|
|
36
|
+
* `"code.ts"` and lands flat (`src/components/code.ts`), while a component
|
|
37
|
+
* that genuinely needs several files declares them nested
|
|
38
|
+
* (`"line-chart/chart.ts"`, `"line-chart/axis.ts"`) and lands in a real
|
|
39
|
+
* subfolder. The registry item's own file paths are the only thing that
|
|
40
|
+
* decides the shape — this function just joins them onto the components root.
|
|
41
|
+
*/
|
|
42
|
+
export async function writeComponentFiles(item, config, projectRoot, options = {}) {
|
|
43
|
+
const targetDir = path.resolve(projectRoot, config.paths.components);
|
|
44
|
+
const written = [];
|
|
45
|
+
const skipped = [];
|
|
46
|
+
const recorded = {};
|
|
47
|
+
for (const file of item.files) {
|
|
48
|
+
const dest = path.join(targetDir, file.path);
|
|
49
|
+
if (fs.existsSync(dest) && !options.force) {
|
|
50
|
+
// The lockfile is what makes this prompt worth reading. "Already
|
|
51
|
+
// exists" is true of every file on a re-install and says nothing;
|
|
52
|
+
// "you changed this since 3.1.0" is the one fact that decides
|
|
53
|
+
// whether overwriting loses work.
|
|
54
|
+
const previous = options.previous?.files[file.path];
|
|
55
|
+
const state = previous ? fileState(projectRoot, previous) : undefined;
|
|
56
|
+
if (state === 'unchanged') {
|
|
57
|
+
// Untouched since it was installed, so there is nothing to lose.
|
|
58
|
+
fs.writeFileSync(dest, file.content);
|
|
59
|
+
written.push(dest);
|
|
60
|
+
recorded[file.path] = {
|
|
61
|
+
target: path.relative(projectRoot, dest),
|
|
62
|
+
content: file.content,
|
|
63
|
+
};
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const { overwrite } = await prompts({
|
|
67
|
+
type: 'confirm',
|
|
68
|
+
name: 'overwrite',
|
|
69
|
+
message: state === 'modified'
|
|
70
|
+
? `${path.relative(projectRoot, dest)} has local changes since ${options.previous?.version ?? 'it was installed'}. Overwrite?`
|
|
71
|
+
: `${path.relative(projectRoot, dest)} already exists. Overwrite?`,
|
|
72
|
+
initial: false,
|
|
73
|
+
});
|
|
74
|
+
if (!overwrite) {
|
|
75
|
+
skipped.push(dest);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
80
|
+
fs.writeFileSync(dest, file.content);
|
|
81
|
+
written.push(dest);
|
|
82
|
+
recorded[file.path] = {
|
|
83
|
+
target: path.relative(projectRoot, dest),
|
|
84
|
+
content: file.content,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { written, skipped, recorded };
|
|
88
|
+
}
|
|
89
|
+
/** Merge every installed item's npm `dependencies` ("pkg@range" strings) into one deduped map. */
|
|
90
|
+
export function collectDependencies(items) {
|
|
91
|
+
const deps = {};
|
|
92
|
+
for (const item of items) {
|
|
93
|
+
for (const spec of item.dependencies ?? []) {
|
|
94
|
+
const at = spec.lastIndexOf('@');
|
|
95
|
+
// A spec always has a version after the last "@" (a scoped package
|
|
96
|
+
// has an earlier "@" too, e.g. "@lezer/javascript@^1.5.4") — bail
|
|
97
|
+
// rather than silently mis-splitting an unexpected shape.
|
|
98
|
+
if (at <= 0)
|
|
99
|
+
throw new Error(`Invalid dependency spec (expected "pkg@range"): ${spec}`);
|
|
100
|
+
deps[spec.slice(0, at)] = spec.slice(at + 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return deps;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/registry/install.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport prompts from 'prompts';\nimport type { ComponentsConfig, RegistryItem } from './protocol.js';\nimport { fetchRegistryItem } from './fetch.js';\nimport { fileState, type LockedComponent } from './lock.js';\n\n/**\n * Resolve a list of requested component names into an ordered, deduped\n * install list, recursively pulling in each item's `registryDependencies`\n * before the item itself (so a dependency's files land first).\n */\nexport async function resolveComponents(\n names: string[],\n config: ComponentsConfig | null,\n defaultRegistryUrl: string | undefined,\n): Promise<RegistryItem[]> {\n const resolved: RegistryItem[] = [];\n const seen = new Set<string>();\n\n async function visit(name: string): Promise<void> {\n if (seen.has(name)) return;\n seen.add(name);\n const item = await fetchRegistryItem(name, config, defaultRegistryUrl);\n for (const dep of item.registryDependencies ?? []) {\n await visit(dep);\n }\n resolved.push(item);\n }\n\n for (const name of names) {\n await visit(name);\n }\n return resolved;\n}\n\nexport interface WriteResult {\n written: string[];\n skipped: string[];\n /** What actually landed, by the path the item declared, for the lockfile. */\n recorded: Record<string, { target: string; content: string }>;\n}\n\n/**\n * Write every file in `item` under `<paths.components>/`, at whatever path\n * each file itself declares — prompting before overwriting an existing file\n * unless `force` is set.\n *\n * Deliberately does *not* nest every item under its own `<item.name>/`\n * subfolder: a simple single-file component declares one file at\n * `\"code.ts\"` and lands flat (`src/components/code.ts`), while a component\n * that genuinely needs several files declares them nested\n * (`\"line-chart/chart.ts\"`, `\"line-chart/axis.ts\"`) and lands in a real\n * subfolder. The registry item's own file paths are the only thing that\n * decides the shape — this function just joins them onto the components root.\n */\nexport async function writeComponentFiles(\n item: RegistryItem,\n config: ComponentsConfig,\n projectRoot: string,\n options: { force?: boolean; previous?: LockedComponent } = {},\n): Promise<WriteResult> {\n const targetDir = path.resolve(projectRoot, config.paths.components);\n const written: string[] = [];\n const skipped: string[] = [];\n const recorded: Record<string, { target: string; content: string }> = {};\n\n for (const file of item.files) {\n const dest = path.join(targetDir, file.path);\n if (fs.existsSync(dest) && !options.force) {\n // The lockfile is what makes this prompt worth reading. \"Already\n // exists\" is true of every file on a re-install and says nothing;\n // \"you changed this since 3.1.0\" is the one fact that decides\n // whether overwriting loses work.\n const previous = options.previous?.files[file.path];\n const state = previous ? fileState(projectRoot, previous) : undefined;\n if (state === 'unchanged') {\n // Untouched since it was installed, so there is nothing to lose.\n fs.writeFileSync(dest, file.content);\n written.push(dest);\n recorded[file.path] = {\n target: path.relative(projectRoot, dest),\n content: file.content,\n };\n continue;\n }\n const { overwrite } = await prompts({\n type: 'confirm',\n name: 'overwrite',\n message:\n state === 'modified'\n ? `${path.relative(projectRoot, dest)} has local changes since ${\n options.previous?.version ?? 'it was installed'\n }. Overwrite?`\n : `${path.relative(projectRoot, dest)} already exists. Overwrite?`,\n initial: false,\n });\n if (!overwrite) {\n skipped.push(dest);\n continue;\n }\n }\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, file.content);\n written.push(dest);\n recorded[file.path] = {\n target: path.relative(projectRoot, dest),\n content: file.content,\n };\n }\n return { written, skipped, recorded };\n}\n\n/** Merge every installed item's npm `dependencies` (\"pkg@range\" strings) into one deduped map. */\nexport function collectDependencies(items: RegistryItem[]): Record<string, string> {\n const deps: Record<string, string> = {};\n for (const item of items) {\n for (const spec of item.dependencies ?? []) {\n const at = spec.lastIndexOf('@');\n // A spec always has a version after the last \"@\" (a scoped package\n // has an earlier \"@\" too, e.g. \"@lezer/javascript@^1.5.4\") — bail\n // rather than silently mis-splitting an unexpected shape.\n if (at <= 0) throw new Error(`Invalid dependency spec (expected \"pkg@range\"): ${spec}`);\n deps[spec.slice(0, at)] = spec.slice(at + 1);\n }\n }\n return deps;\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type minimist from 'minimist';
|
|
2
|
+
/**
|
|
3
|
+
* `ms list` — what this project is carrying, where each piece came from, and
|
|
4
|
+
* whether it still matches what was installed.
|
|
5
|
+
*
|
|
6
|
+
* The question a copied component makes hard to answer. A dependency's version
|
|
7
|
+
* is in `package.json`; copied source has nothing to look at, so a project six
|
|
8
|
+
* months in cannot tell which of its components are stock, which were edited,
|
|
9
|
+
* and which are three versions behind. That is the cost of copying, and the
|
|
10
|
+
* lockfile is what pays it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runList(projectRoot: string, argv: minimist.ParsedArgs): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/registry/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAKrC;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsD3F"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import { loadComponentsConfig } from './config.js';
|
|
3
|
+
import { fetchRegistryItem } from './fetch.js';
|
|
4
|
+
import { changedFiles, fileState, loadLock } from './lock.js';
|
|
5
|
+
/**
|
|
6
|
+
* `ms list` — what this project is carrying, where each piece came from, and
|
|
7
|
+
* whether it still matches what was installed.
|
|
8
|
+
*
|
|
9
|
+
* The question a copied component makes hard to answer. A dependency's version
|
|
10
|
+
* is in `package.json`; copied source has nothing to look at, so a project six
|
|
11
|
+
* months in cannot tell which of its components are stock, which were edited,
|
|
12
|
+
* and which are three versions behind. That is the cost of copying, and the
|
|
13
|
+
* lockfile is what pays it.
|
|
14
|
+
*/
|
|
15
|
+
export async function runList(projectRoot, argv) {
|
|
16
|
+
const lock = loadLock(projectRoot);
|
|
17
|
+
const entries = Object.entries(lock.components);
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
console.log(kleur.dim('No components installed. Add one with `ms add <name>`.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const config = loadComponentsConfig(projectRoot);
|
|
23
|
+
const defaultRegistryUrl = typeof argv.registry === 'string' ? argv.registry : undefined;
|
|
24
|
+
for (const [name, entry] of entries) {
|
|
25
|
+
const changed = changedFiles(projectRoot, entry);
|
|
26
|
+
const missing = Object.values(entry.files).filter((file) => fileState(projectRoot, file) === 'missing').length;
|
|
27
|
+
const parts = [kleur.bold(name)];
|
|
28
|
+
if (entry.version)
|
|
29
|
+
parts.push(kleur.dim(entry.version));
|
|
30
|
+
if (entry.registry !== 'default')
|
|
31
|
+
parts.push(kleur.dim(`from ${entry.registry}`));
|
|
32
|
+
if (missing > 0) {
|
|
33
|
+
parts.push(kleur.red(`${missing} file${missing === 1 ? '' : 's'} missing`));
|
|
34
|
+
}
|
|
35
|
+
else if (changed.length > 0) {
|
|
36
|
+
parts.push(kleur.yellow(`modified (${changed.length} of ${Object.keys(entry.files).length} files)`));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
parts.push(kleur.green('unchanged'));
|
|
40
|
+
}
|
|
41
|
+
// Only reached for; never acted on. Telling someone an update exists is
|
|
42
|
+
// useful, and taking it without being asked would overwrite the edits
|
|
43
|
+
// the line above just reported.
|
|
44
|
+
if (argv.check) {
|
|
45
|
+
try {
|
|
46
|
+
const upstream = await fetchRegistryItem(name, config, defaultRegistryUrl);
|
|
47
|
+
if (upstream.version && upstream.version !== entry.version) {
|
|
48
|
+
parts.push(kleur.cyan(`${entry.version ?? '?'} → ${upstream.version} available`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
parts.push(kleur.dim('(upstream unreachable)'));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
console.log(` ${parts.join(' ')}`);
|
|
56
|
+
}
|
|
57
|
+
if (!argv.check) {
|
|
58
|
+
console.log(kleur.dim('\nPass --check to ask each registry whether a newer version exists.'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/registry/list.ts"],"names":[],"mappings":"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","sourcesContent":["import kleur from 'kleur';\nimport type minimist from 'minimist';\nimport { loadComponentsConfig } from './config.js';\nimport { fetchRegistryItem } from './fetch.js';\nimport { changedFiles, fileState, loadLock } from './lock.js';\n\n/**\n * `ms list` — what this project is carrying, where each piece came from, and\n * whether it still matches what was installed.\n *\n * The question a copied component makes hard to answer. A dependency's version\n * is in `package.json`; copied source has nothing to look at, so a project six\n * months in cannot tell which of its components are stock, which were edited,\n * and which are three versions behind. That is the cost of copying, and the\n * lockfile is what pays it.\n */\nexport async function runList(projectRoot: string, argv: minimist.ParsedArgs): Promise<void> {\n const lock = loadLock(projectRoot);\n const entries = Object.entries(lock.components);\n\n if (entries.length === 0) {\n console.log(kleur.dim('No components installed. Add one with `ms add <name>`.'));\n return;\n }\n\n const config = loadComponentsConfig(projectRoot);\n const defaultRegistryUrl = typeof argv.registry === 'string' ? argv.registry : undefined;\n\n for (const [name, entry] of entries) {\n const changed = changedFiles(projectRoot, entry);\n const missing = Object.values(entry.files).filter(\n (file) => fileState(projectRoot, file) === 'missing',\n ).length;\n\n const parts = [kleur.bold(name)];\n if (entry.version) parts.push(kleur.dim(entry.version));\n if (entry.registry !== 'default') parts.push(kleur.dim(`from ${entry.registry}`));\n\n if (missing > 0) {\n parts.push(kleur.red(`${missing} file${missing === 1 ? '' : 's'} missing`));\n } else if (changed.length > 0) {\n parts.push(\n kleur.yellow(\n `modified (${changed.length} of ${Object.keys(entry.files).length} files)`,\n ),\n );\n } else {\n parts.push(kleur.green('unchanged'));\n }\n\n // Only reached for; never acted on. Telling someone an update exists is\n // useful, and taking it without being asked would overwrite the edits\n // the line above just reported.\n if (argv.check) {\n try {\n const upstream = await fetchRegistryItem(name, config, defaultRegistryUrl);\n if (upstream.version && upstream.version !== entry.version) {\n parts.push(kleur.cyan(`${entry.version ?? '?'} → ${upstream.version} available`));\n }\n } catch {\n parts.push(kleur.dim('(upstream unreachable)'));\n }\n }\n\n console.log(` ${parts.join(' ')}`);\n }\n\n if (!argv.check) {\n console.log(kleur.dim('\\nPass --check to ask each registry whether a newer version exists.'));\n }\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RegistryItem } from './protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* What was installed, from where, at what version — and whether it has been
|
|
4
|
+
* edited since.
|
|
5
|
+
*
|
|
6
|
+
* A copied component is source you are *meant* to change: that is the whole
|
|
7
|
+
* point of copying it rather than depending on it. But then "is this file
|
|
8
|
+
* yours or ours" stops having an obvious answer, and every later question —
|
|
9
|
+
* can `ms add` overwrite this, what did upstream change, which of these
|
|
10
|
+
* components am I even carrying — depends on it.
|
|
11
|
+
*
|
|
12
|
+
* So each install records the hash of the bytes it wrote. Re-hashing the file
|
|
13
|
+
* on disk answers "modified since install" exactly, with no heuristic.
|
|
14
|
+
*
|
|
15
|
+
* ## Why a separate file
|
|
16
|
+
*
|
|
17
|
+
* `motionscript.json` is hand-authored configuration: registries, paths, an
|
|
18
|
+
* alias. This is derived, rewritten on every install, and of no interest to a
|
|
19
|
+
* human until something is wrong. Mixing the two would mean `ms add` rewriting
|
|
20
|
+
* a file the user edits, and a merge conflict spanning both kinds of content.
|
|
21
|
+
* Every package manager makes the same split for the same reason.
|
|
22
|
+
*
|
|
23
|
+
* ## Why not a header in each file
|
|
24
|
+
*
|
|
25
|
+
* A "generated, do not edit" banner above a file whose purpose is to be edited
|
|
26
|
+
* is a lie, and the first `format-on-save` moves or mangles it. The record
|
|
27
|
+
* belongs beside the project, not inside the thing it describes.
|
|
28
|
+
*/
|
|
29
|
+
export declare const LOCK_FILENAME = "motionscript.lock.json";
|
|
30
|
+
/** What the lock file used to be called, oldest first. Both still read, never written. */
|
|
31
|
+
export declare const LEGACY_LOCK_FILENAMES: string[];
|
|
32
|
+
/** One file as it was written. */
|
|
33
|
+
export interface LockedFile {
|
|
34
|
+
/** Where it landed, relative to the project root. */
|
|
35
|
+
target: string;
|
|
36
|
+
/** sha256 of the bytes written, after any import rewriting. */
|
|
37
|
+
installed: string;
|
|
38
|
+
}
|
|
39
|
+
/** One installed component. */
|
|
40
|
+
export interface LockedComponent {
|
|
41
|
+
/** `"default"`, or the `@namespace` it came from. */
|
|
42
|
+
registry: string;
|
|
43
|
+
/** The exact URL it was read from. */
|
|
44
|
+
url: string;
|
|
45
|
+
/** The item name, with any namespace stripped. */
|
|
46
|
+
item: string;
|
|
47
|
+
/** The item's version at install time, when it declared one. */
|
|
48
|
+
version?: string;
|
|
49
|
+
/** The kit its copied source imports, and which stays a dependency. */
|
|
50
|
+
kit?: string;
|
|
51
|
+
installedAt: string;
|
|
52
|
+
files: Record<string, LockedFile>;
|
|
53
|
+
}
|
|
54
|
+
export interface ComponentsLock {
|
|
55
|
+
$schema?: string;
|
|
56
|
+
lockfileVersion: 1;
|
|
57
|
+
components: Record<string, LockedComponent>;
|
|
58
|
+
}
|
|
59
|
+
export declare function hashContent(content: string): string;
|
|
60
|
+
export declare function emptyLock(): ComponentsLock;
|
|
61
|
+
export declare function loadLock(projectRoot: string): ComponentsLock;
|
|
62
|
+
export declare function writeLock(projectRoot: string, lock: ComponentsLock): void;
|
|
63
|
+
/** Records one item's install, replacing any previous record of it. */
|
|
64
|
+
export declare function recordInstall(lock: ComponentsLock, requestedName: string, item: RegistryItem, url: string, written: Record<string, {
|
|
65
|
+
target: string;
|
|
66
|
+
content: string;
|
|
67
|
+
}>): void;
|
|
68
|
+
/** How one installed file stands against what was written. */
|
|
69
|
+
export type FileState = 'unchanged' | 'modified' | 'missing';
|
|
70
|
+
export declare function fileState(projectRoot: string, file: LockedFile): FileState;
|
|
71
|
+
/** Every file of a component that is no longer as it was installed. */
|
|
72
|
+
export declare function changedFiles(projectRoot: string, entry: LockedComponent): string[];
|
|
73
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/registry/lock.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,eAAO,MAAM,aAAa,2BAA2B,CAAC;AAEtD,0FAA0F;AAC1F,eAAO,MAAM,qBAAqB,UAAsD,CAAC;AAEzF,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACvB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,+BAA+B;AAC/B,MAAM,WAAW,eAAe;IAC5B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,CAAC,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC/C;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,SAAS,IAAI,cAAc,CAM1C;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAuB5D;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAUzE;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CACzB,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7D,IAAI,CAeN;AAED,8DAA8D;AAC9D,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7D,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,SAAS,CAI1E;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,EAAE,CAIlF"}
|