jsrepo 1.0.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/LICENSE +21 -0
- package/README.md +5 -0
- package/bin.mjs +2 -0
- package/dist/index.js +2179 -0
- package/package.json +70 -0
- package/pnpm-lock.yaml +3185 -0
- package/schema.json +28 -0
- package/src/blocks/types/result.ts +736 -0
- package/src/blocks/utilities/array-sum.ts +31 -0
- package/src/blocks/utilities/lines.ts +73 -0
- package/src/blocks/utilities/map-to-array.ts +32 -0
- package/src/blocks/utilities/pad.ts +85 -0
- package/src/blocks/utilities/strip-ansi.ts +25 -0
- package/src/commands/add.ts +495 -0
- package/src/commands/build.ts +79 -0
- package/src/commands/diff.ts +218 -0
- package/src/commands/index.ts +7 -0
- package/src/commands/init.ts +117 -0
- package/src/commands/test.ts +369 -0
- package/src/config/index.ts +31 -0
- package/src/index.ts +40 -0
- package/src/utils/build.ts +189 -0
- package/src/utils/context.ts +19 -0
- package/src/utils/diff.ts +184 -0
- package/src/utils/get-installed-blocks.ts +38 -0
- package/src/utils/get-watermark.ts +7 -0
- package/src/utils/git-providers.ts +140 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/language-support.ts +198 -0
- package/src/utils/package.ts +16 -0
- package/src/utils/prompts.ts +72 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { builtinModules } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { walk } from 'estree-walker';
|
|
5
|
+
import * as sv from 'svelte/compiler';
|
|
6
|
+
import { Project } from 'ts-morph';
|
|
7
|
+
import validatePackageName from 'validate-npm-package-name';
|
|
8
|
+
import { Err, Ok, type Result } from '../blocks/types/result';
|
|
9
|
+
import { findNearestPackageJson } from './package';
|
|
10
|
+
|
|
11
|
+
export type ResolvedDependencies = {
|
|
12
|
+
local: string[];
|
|
13
|
+
devDependencies: string[];
|
|
14
|
+
dependencies: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type Lang = {
|
|
18
|
+
/** Matches the supported file types */
|
|
19
|
+
matches: (fileName: string) => boolean;
|
|
20
|
+
/** Reads the file and gets any dependencies from its imports */
|
|
21
|
+
resolveDependencies: (
|
|
22
|
+
filePath: string,
|
|
23
|
+
category: string,
|
|
24
|
+
isSubDir: boolean
|
|
25
|
+
) => Result<ResolvedDependencies, string>;
|
|
26
|
+
/** Returns a multiline comment containing the content */
|
|
27
|
+
comment: (content: string) => string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const typescript: Lang = {
|
|
31
|
+
matches: (fileName) =>
|
|
32
|
+
fileName.endsWith('.ts') ||
|
|
33
|
+
fileName.endsWith('.js') ||
|
|
34
|
+
fileName.endsWith('.tsx') ||
|
|
35
|
+
fileName.endsWith('.jsx'),
|
|
36
|
+
resolveDependencies: (filePath, category, isSubDir) => {
|
|
37
|
+
const project = new Project();
|
|
38
|
+
|
|
39
|
+
const blockFile = project.addSourceFileAtPath(filePath);
|
|
40
|
+
|
|
41
|
+
const imports = blockFile.getImportDeclarations();
|
|
42
|
+
|
|
43
|
+
const relativeImports = imports.filter((declaration) =>
|
|
44
|
+
declaration.getModuleSpecifierValue().startsWith('.')
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const localDeps = new Set<string>();
|
|
48
|
+
|
|
49
|
+
for (const relativeImport of relativeImports) {
|
|
50
|
+
const mod = relativeImport.getModuleSpecifierValue();
|
|
51
|
+
|
|
52
|
+
const localDep = resolveLocalImport(mod, category, isSubDir);
|
|
53
|
+
|
|
54
|
+
if (localDep) localDeps.add(localDep);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const deps = imports
|
|
58
|
+
.filter((declaration) => !declaration.getModuleSpecifierValue().startsWith('.'))
|
|
59
|
+
.map((declaration) => declaration.getModuleSpecifierValue());
|
|
60
|
+
|
|
61
|
+
const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
|
|
62
|
+
|
|
63
|
+
return Ok({
|
|
64
|
+
local: Array.from(localDeps),
|
|
65
|
+
dependencies,
|
|
66
|
+
devDependencies,
|
|
67
|
+
} satisfies ResolvedDependencies);
|
|
68
|
+
},
|
|
69
|
+
comment: (content) => `/*\n${content}\n*/`,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const svelte: Lang = {
|
|
73
|
+
matches: (fileName) => fileName.endsWith('.svelte'),
|
|
74
|
+
resolveDependencies: (filePath, category, isSubDir) => {
|
|
75
|
+
const sourceCode = fs.readFileSync(filePath).toString();
|
|
76
|
+
|
|
77
|
+
const root = sv.parse(sourceCode, { modern: true });
|
|
78
|
+
|
|
79
|
+
// if no script tag then no dependencies
|
|
80
|
+
if (!root.instance) return Ok({ dependencies: [], devDependencies: [], local: [] });
|
|
81
|
+
|
|
82
|
+
const localDeps = new Set<string>();
|
|
83
|
+
const deps = new Set<string>();
|
|
84
|
+
|
|
85
|
+
// biome-ignore lint/suspicious/noExplicitAny: The root instance is just missing the `id` prop
|
|
86
|
+
walk(root.instance as any, {
|
|
87
|
+
enter: (node) => {
|
|
88
|
+
if (node.type === 'ImportDeclaration') {
|
|
89
|
+
if (typeof node.source.value === 'string') {
|
|
90
|
+
if (node.source.value.startsWith('.')) {
|
|
91
|
+
const localDep = resolveLocalImport(
|
|
92
|
+
node.source.value,
|
|
93
|
+
category,
|
|
94
|
+
isSubDir
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (localDep) localDeps.add(localDep);
|
|
98
|
+
} else {
|
|
99
|
+
deps.add(node.source.value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
|
|
107
|
+
|
|
108
|
+
return Ok({
|
|
109
|
+
dependencies,
|
|
110
|
+
devDependencies,
|
|
111
|
+
local: Array.from(localDeps),
|
|
112
|
+
} satisfies ResolvedDependencies);
|
|
113
|
+
},
|
|
114
|
+
comment: (content) => `<!--\n${content}\n-->`,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const resolveLocalImport = (
|
|
118
|
+
mod: string,
|
|
119
|
+
category: string,
|
|
120
|
+
isSubDir: boolean
|
|
121
|
+
): string | undefined => {
|
|
122
|
+
// do not add local deps that are within the same folder
|
|
123
|
+
if (isSubDir && mod.startsWith('./')) return undefined;
|
|
124
|
+
|
|
125
|
+
if (mod.startsWith('./')) {
|
|
126
|
+
return `${category}/${path.parse(path.basename(mod)).name}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (isSubDir && mod.startsWith('../') && !mod.startsWith('../.')) {
|
|
130
|
+
return `${category}/${path.parse(path.basename(mod)).name}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const segments = mod.replaceAll('../', '').split('/');
|
|
134
|
+
|
|
135
|
+
// invalid path
|
|
136
|
+
if (segments.length !== 2) return undefined;
|
|
137
|
+
|
|
138
|
+
return `${segments[0]}/${segments[1]}`;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** Iterates over the dependency and resolves each one using the nearest package.json file.
|
|
142
|
+
* Strips node APIs and pins the version of each dependency based on what is in the package.json.
|
|
143
|
+
*
|
|
144
|
+
* @param deps
|
|
145
|
+
* @param filePath
|
|
146
|
+
* @returns
|
|
147
|
+
*/
|
|
148
|
+
const resolveRemoteDeps = (deps: string[], filePath: string) => {
|
|
149
|
+
const filteredDeps = deps.filter(
|
|
150
|
+
(dep) =>
|
|
151
|
+
!builtinModules.includes(dep) &&
|
|
152
|
+
!dep.startsWith('node:') &&
|
|
153
|
+
validatePackageName(dep).validForNewPackages
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const pkgPath = findNearestPackageJson(path.dirname(filePath), '');
|
|
157
|
+
|
|
158
|
+
const dependencies = new Set<string>();
|
|
159
|
+
const devDependencies = new Set<string>();
|
|
160
|
+
|
|
161
|
+
if (pkgPath) {
|
|
162
|
+
const { devDependencies: packageDevDependencies, dependencies: packageDependencies } =
|
|
163
|
+
JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
164
|
+
|
|
165
|
+
for (const dep of filteredDeps) {
|
|
166
|
+
let version: string | undefined = undefined;
|
|
167
|
+
if (packageDependencies !== undefined) {
|
|
168
|
+
version = packageDependencies[dep];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (version !== undefined) {
|
|
172
|
+
dependencies.add(`${dep}@${version}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (packageDevDependencies !== undefined) {
|
|
177
|
+
version = packageDevDependencies[dep];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (version !== undefined) {
|
|
181
|
+
devDependencies.add(`${dep}@${version}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// if no version found just add it without a version
|
|
186
|
+
dependencies.add(dep);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
dependencies: Array.from(dependencies),
|
|
192
|
+
devDependencies: Array.from(devDependencies),
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const languages: Lang[] = [typescript, svelte];
|
|
197
|
+
|
|
198
|
+
export { typescript, languages };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const findNearestPackageJson = (startDir: string, until: string): string | undefined => {
|
|
5
|
+
const packagePath = path.join(startDir, 'package.json');
|
|
6
|
+
|
|
7
|
+
if (fs.existsSync(packagePath)) return packagePath;
|
|
8
|
+
|
|
9
|
+
if (startDir === until) return undefined;
|
|
10
|
+
|
|
11
|
+
const segments = startDir.split(/[\/\\]/);
|
|
12
|
+
|
|
13
|
+
return findNearestPackageJson(segments.slice(0, segments.length - 1).join('/'), until);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { findNearestPackageJson };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { intro, spinner } from '@clack/prompts';
|
|
2
|
+
import color from 'chalk';
|
|
3
|
+
import { rightPad, rightPadMin } from '../blocks/utilities/pad';
|
|
4
|
+
import { stripAsni } from '../blocks/utilities/strip-ansi';
|
|
5
|
+
|
|
6
|
+
const VERTICAL_BORDER = color.gray('│');
|
|
7
|
+
const HORIZONTAL_BORDER = color.gray('─');
|
|
8
|
+
const TOP_RIGHT_CORNER = color.gray('┐');
|
|
9
|
+
const BOTTOM_RIGHT_CORNER = color.gray('┘');
|
|
10
|
+
const JUNCTION_RIGHT = color.gray('├');
|
|
11
|
+
// we may need these eventually
|
|
12
|
+
// const TOP_LEFT_CORNER = color.gray("┌");
|
|
13
|
+
// const BOTTOM_LEFT_CORNER = color.gray("└");
|
|
14
|
+
|
|
15
|
+
export type Task = {
|
|
16
|
+
loadingMessage: string;
|
|
17
|
+
completedMessage: string;
|
|
18
|
+
run: () => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const runTasks = async (tasks: Task[], { verbose = false }) => {
|
|
22
|
+
const loading = spinner();
|
|
23
|
+
|
|
24
|
+
for (const task of tasks) {
|
|
25
|
+
// we don't want this to clear logs when in verbose mode
|
|
26
|
+
if (!verbose) loading.start(task.loadingMessage);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await task.run();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(err);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!verbose) loading.stop(task.completedMessage);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const nextSteps = (steps: string[]): string => {
|
|
39
|
+
let max = 20;
|
|
40
|
+
steps.map((val) => {
|
|
41
|
+
const reset = rightPad(stripAsni(val), 4);
|
|
42
|
+
|
|
43
|
+
if (reset.length > max) max = reset.length;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const NEXT_STEPS = 'Next Steps';
|
|
47
|
+
|
|
48
|
+
let result = `${VERTICAL_BORDER}\n`;
|
|
49
|
+
|
|
50
|
+
// top
|
|
51
|
+
result += `${JUNCTION_RIGHT} ${NEXT_STEPS} ${HORIZONTAL_BORDER.repeat(
|
|
52
|
+
max - NEXT_STEPS.length - 1
|
|
53
|
+
)}${TOP_RIGHT_CORNER}\n`;
|
|
54
|
+
|
|
55
|
+
result += `${VERTICAL_BORDER} ${' '.repeat(max)} ${VERTICAL_BORDER}\n`;
|
|
56
|
+
|
|
57
|
+
steps.map((step) => {
|
|
58
|
+
result += `${VERTICAL_BORDER} ${rightPadMin(step, max - 1)} ${VERTICAL_BORDER}\n`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
result += `${VERTICAL_BORDER} ${' '.repeat(max)} ${VERTICAL_BORDER}\n`;
|
|
62
|
+
|
|
63
|
+
// bottom
|
|
64
|
+
result += `${JUNCTION_RIGHT}${HORIZONTAL_BORDER.repeat(max + 2)}${BOTTOM_RIGHT_CORNER}\n`;
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const _intro = (version: string) =>
|
|
70
|
+
intro(`${color.bgHex('#f7df1e').black(' jsrepo ')}${color.gray(` v${version} `)}`);
|
|
71
|
+
|
|
72
|
+
export { runTasks, nextSteps, _intro as intro };
|