@motionscript/cli 0.0.0-stage → 0.1.0-alpha.3
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 +15 -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 +97 -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 +45 -0
- package/dist/registry/install.d.ts.map +1 -0
- package/dist/registry/install.js +135 -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,83 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import kleur from 'kleur';
|
|
4
|
+
import { loadComponentsConfig } from './config.js';
|
|
5
|
+
import { fetchRegistryItem } from './fetch.js';
|
|
6
|
+
import { hashContent, loadLock } from './lock.js';
|
|
7
|
+
/**
|
|
8
|
+
* `ms diff [name]` — what upstream changed under your feet.
|
|
9
|
+
*
|
|
10
|
+
* The counterpart to `ms list`: that one answers "did I change this", this one
|
|
11
|
+
* answers "did they". Both are only answerable because the lockfile recorded
|
|
12
|
+
* the bytes that were written, which is what gives a three-way comparison a
|
|
13
|
+
* middle term — yours, theirs, and the common ancestor they diverged from.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately prints a summary rather than a unified diff. A component is
|
|
16
|
+
* files on disk in a project that already has a diff tool and a version control
|
|
17
|
+
* system with better ones; what the CLI uniquely knows is *which* files moved
|
|
18
|
+
* and in which direction, and that is what it says.
|
|
19
|
+
*/
|
|
20
|
+
export async function runDiff(projectRoot, argv) {
|
|
21
|
+
const lock = loadLock(projectRoot);
|
|
22
|
+
const config = loadComponentsConfig(projectRoot);
|
|
23
|
+
const defaultRegistryUrl = typeof argv.registry === 'string' ? argv.registry : undefined;
|
|
24
|
+
const requested = argv._.slice(1).filter(Boolean);
|
|
25
|
+
const names = requested.length > 0 ? requested : Object.keys(lock.components);
|
|
26
|
+
if (names.length === 0) {
|
|
27
|
+
console.log(kleur.dim('No components installed.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
const entry = lock.components[name];
|
|
32
|
+
if (!entry) {
|
|
33
|
+
console.log(`${kleur.bold(name)} ${kleur.red('not installed')}`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
let upstream;
|
|
37
|
+
try {
|
|
38
|
+
upstream = await fetchRegistryItem(name, config, defaultRegistryUrl);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.log(`${kleur.bold(name)} ${kleur.red('unreachable')} ${kleur.dim(String(error))}`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const header = [kleur.bold(name)];
|
|
45
|
+
if (entry.version || upstream.version) {
|
|
46
|
+
header.push(kleur.dim(`${entry.version ?? '?'} → ${upstream.version ?? '?'}`));
|
|
47
|
+
}
|
|
48
|
+
console.log(header.join(' '));
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
for (const file of upstream.files) {
|
|
51
|
+
seen.add(file.path);
|
|
52
|
+
const locked = entry.files[file.path];
|
|
53
|
+
if (!locked) {
|
|
54
|
+
console.log(` ${kleur.green('+')} ${file.path} ${kleur.dim('(new upstream)')}`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const onDisk = path.join(projectRoot, locked.target);
|
|
58
|
+
const local = fs.existsSync(onDisk) ? fs.readFileSync(onDisk, 'utf8') : null;
|
|
59
|
+
const upstreamChanged = hashContent(file.content) !== locked.installed;
|
|
60
|
+
const localChanged = local !== null && hashContent(local) !== locked.installed;
|
|
61
|
+
if (local === null) {
|
|
62
|
+
console.log(` ${kleur.red('-')} ${file.path} ${kleur.dim('(missing locally)')}`);
|
|
63
|
+
}
|
|
64
|
+
else if (upstreamChanged && localChanged) {
|
|
65
|
+
// The only state that needs a person: both moved, so
|
|
66
|
+
// re-installing loses work and not re-installing keeps a bug.
|
|
67
|
+
console.log(` ${kleur.yellow('!')} ${file.path} ${kleur.dim('(changed both sides)')}`);
|
|
68
|
+
}
|
|
69
|
+
else if (upstreamChanged) {
|
|
70
|
+
console.log(` ${kleur.cyan('↑')} ${file.path} ${kleur.dim('(changed upstream)')}`);
|
|
71
|
+
}
|
|
72
|
+
else if (localChanged) {
|
|
73
|
+
console.log(` ${kleur.yellow('~')} ${file.path} ${kleur.dim('(changed locally)')}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const declared of Object.keys(entry.files)) {
|
|
77
|
+
if (!seen.has(declared)) {
|
|
78
|
+
console.log(` ${kleur.dim('×')} ${declared} ${kleur.dim('(dropped upstream)')}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -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,45 @@
|
|
|
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, within the components root (see `componentDestinations`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeComponentFiles(item: RegistryItem, config: ComponentsConfig, projectRoot: string, options?: {
|
|
32
|
+
force?: boolean;
|
|
33
|
+
previous?: LockedComponent;
|
|
34
|
+
}): Promise<WriteResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Where each of `item`'s files lands, keyed by the path it declares.
|
|
37
|
+
*
|
|
38
|
+
* A registry is fetched, so its paths are input: one that is absolute, or that
|
|
39
|
+
* leaves the components root once resolved, throws before anything is written,
|
|
40
|
+
* as does a components root outside the project.
|
|
41
|
+
*/
|
|
42
|
+
export declare function componentDestinations(item: RegistryItem, config: ComponentsConfig, projectRoot: string): Map<string, string>;
|
|
43
|
+
/** Merge every installed item's npm `dependencies` ("pkg@range" strings) into one deduped map. */
|
|
44
|
+
export declare function collectDependencies(items: RegistryItem[]): Record<string, string>;
|
|
45
|
+
//# 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;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBrB;AAQD,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAajF"}
|
|
@@ -0,0 +1,135 @@
|
|
|
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, within the components root (see `componentDestinations`).
|
|
41
|
+
*/
|
|
42
|
+
export async function writeComponentFiles(item, config, projectRoot, options = {}) {
|
|
43
|
+
const destinations = componentDestinations(item, config, projectRoot);
|
|
44
|
+
const written = [];
|
|
45
|
+
const skipped = [];
|
|
46
|
+
const recorded = {};
|
|
47
|
+
for (const file of item.files) {
|
|
48
|
+
const dest = destinations.get(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
|
+
/**
|
|
90
|
+
* Where each of `item`'s files lands, keyed by the path it declares.
|
|
91
|
+
*
|
|
92
|
+
* A registry is fetched, so its paths are input: one that is absolute, or that
|
|
93
|
+
* leaves the components root once resolved, throws before anything is written,
|
|
94
|
+
* as does a components root outside the project.
|
|
95
|
+
*/
|
|
96
|
+
export function componentDestinations(item, config, projectRoot) {
|
|
97
|
+
const root = path.resolve(projectRoot);
|
|
98
|
+
const targetDir = path.resolve(root, config.paths.components);
|
|
99
|
+
if (!isWithin(root, targetDir)) {
|
|
100
|
+
throw new Error(`paths.components "${config.paths.components}" in motionscript.json is outside the project (${root}).`);
|
|
101
|
+
}
|
|
102
|
+
const destinations = new Map();
|
|
103
|
+
for (const file of item.files) {
|
|
104
|
+
const dest = path.resolve(targetDir, file.path);
|
|
105
|
+
const absolute = path.posix.isAbsolute(file.path) || path.win32.isAbsolute(file.path);
|
|
106
|
+
if (absolute || dest === targetDir || !isWithin(targetDir, dest)) {
|
|
107
|
+
throw new Error(`Registry item "${item.name}" declares a file at "${file.path}", outside ${config.paths.components}/. ` +
|
|
108
|
+
'A registry file path must be relative to the components folder and stay inside it.');
|
|
109
|
+
}
|
|
110
|
+
destinations.set(file.path, dest);
|
|
111
|
+
}
|
|
112
|
+
return destinations;
|
|
113
|
+
}
|
|
114
|
+
/** Whether `file` is `dir` or below it, by resolved path rather than by string prefix. */
|
|
115
|
+
function isWithin(dir, file) {
|
|
116
|
+
const rel = path.relative(dir, file);
|
|
117
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel));
|
|
118
|
+
}
|
|
119
|
+
/** Merge every installed item's npm `dependencies` ("pkg@range" strings) into one deduped map. */
|
|
120
|
+
export function collectDependencies(items) {
|
|
121
|
+
const deps = {};
|
|
122
|
+
for (const item of items) {
|
|
123
|
+
for (const spec of item.dependencies ?? []) {
|
|
124
|
+
const at = spec.lastIndexOf('@');
|
|
125
|
+
// A spec always has a version after the last "@" (a scoped package
|
|
126
|
+
// has an earlier "@" too, e.g. "@lezer/javascript@^1.5.4") — bail
|
|
127
|
+
// rather than silently mis-splitting an unexpected shape.
|
|
128
|
+
if (at <= 0)
|
|
129
|
+
throw new Error(`Invalid dependency spec (expected "pkg@range"): ${spec}`);
|
|
130
|
+
deps[spec.slice(0, at)] = spec.slice(at + 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return deps;
|
|
134
|
+
}
|
|
135
|
+
//# 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, within the components root (see `componentDestinations`).\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 destinations = componentDestinations(item, config, projectRoot);\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 = destinations.get(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/**\n * Where each of `item`'s files lands, keyed by the path it declares.\n *\n * A registry is fetched, so its paths are input: one that is absolute, or that\n * leaves the components root once resolved, throws before anything is written,\n * as does a components root outside the project.\n */\nexport function componentDestinations(\n item: RegistryItem,\n config: ComponentsConfig,\n projectRoot: string,\n): Map<string, string> {\n const root = path.resolve(projectRoot);\n const targetDir = path.resolve(root, config.paths.components);\n if (!isWithin(root, targetDir)) {\n throw new Error(\n `paths.components \"${config.paths.components}\" in motionscript.json is outside the project (${root}).`,\n );\n }\n const destinations = new Map<string, string>();\n for (const file of item.files) {\n const dest = path.resolve(targetDir, file.path);\n const absolute = path.posix.isAbsolute(file.path) || path.win32.isAbsolute(file.path);\n if (absolute || dest === targetDir || !isWithin(targetDir, dest)) {\n throw new Error(\n `Registry item \"${item.name}\" declares a file at \"${file.path}\", outside ${config.paths.components}/. ` +\n 'A registry file path must be relative to the components folder and stay inside it.',\n );\n }\n destinations.set(file.path, dest);\n }\n return destinations;\n}\n\n/** Whether `file` is `dir` or below it, by resolved path rather than by string prefix. */\nfunction isWithin(dir: string, file: string): boolean {\n const rel = path.relative(dir, file);\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel));\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"]}
|