@parlor/scaffold 0.0.0-stage → 0.2.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 +11 -2
- package/dist/add/addon.d.ts +103 -0
- package/dist/add/addon.d.ts.map +1 -0
- package/dist/add/addon.js +12 -0
- package/dist/add/addon.js.map +1 -0
- package/dist/add/plan.d.ts +72 -0
- package/dist/add/plan.d.ts.map +1 -0
- package/dist/add/plan.js +237 -0
- package/dist/add/plan.js.map +1 -0
- package/dist/add/run.d.ts +92 -0
- package/dist/add/run.d.ts.map +1 -0
- package/dist/add/run.js +167 -0
- package/dist/add/run.js.map +1 -0
- package/dist/add/transaction.d.ts +67 -0
- package/dist/add/transaction.d.ts.map +1 -0
- package/dist/add/transaction.js +181 -0
- package/dist/add/transaction.js.map +1 -0
- package/dist/addons/agent.d.ts +58 -0
- package/dist/addons/agent.d.ts.map +1 -0
- package/dist/addons/agent.js +50 -0
- package/dist/addons/agent.js.map +1 -0
- package/dist/addons/auth.d.ts +103 -0
- package/dist/addons/auth.d.ts.map +1 -0
- package/dist/addons/auth.js +93 -0
- package/dist/addons/auth.js.map +1 -0
- package/dist/addons/index.d.ts +159 -0
- package/dist/addons/index.d.ts.map +1 -0
- package/dist/addons/index.js +8 -0
- package/dist/addons/index.js.map +1 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +23 -0
- package/dist/catalog.js.map +1 -0
- package/dist/create.d.ts +45 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +162 -0
- package/dist/create.js.map +1 -0
- package/dist/docs.d.ts +12 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +70 -0
- package/dist/docs.js.map +1 -0
- package/dist/edit/inspect.d.ts +15 -0
- package/dist/edit/inspect.d.ts.map +1 -0
- package/dist/edit/inspect.js +73 -0
- package/dist/edit/inspect.js.map +1 -0
- package/dist/edit/ops.d.ts +92 -0
- package/dist/edit/ops.d.ts.map +1 -0
- package/dist/edit/ops.js +516 -0
- package/dist/edit/ops.js.map +1 -0
- package/dist/edit/source.d.ts +24 -0
- package/dist/edit/source.d.ts.map +1 -0
- package/dist/edit/source.js +36 -0
- package/dist/edit/source.js.map +1 -0
- package/dist/edit/syntax.d.ts +39 -0
- package/dist/edit/syntax.d.ts.map +1 -0
- package/dist/edit/syntax.js +129 -0
- package/dist/edit/syntax.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +15 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +65 -0
- package/dist/files.js.map +1 -0
- package/dist/fixtures.d.ts +28 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +66 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/git.d.ts +22 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +56 -0
- package/dist/git.js.map +1 -0
- package/dist/identity.d.ts +9 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +23 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/package-manager.d.ts +49 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +118 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/package.json +46 -3
- package/templates/auth/auth.ts +19 -0
- package/templates/auth/schema/auth.ts +1 -0
- package/templates/auth/src/auth/client.ts +3 -0
- package/templates/auth/src/auth/sign-in-gate.tsx +17 -0
- package/templates/auth/src/auth/sign-out-button.tsx +17 -0
- package/templates/auth/src/routes/link.tsx +92 -0
- package/templates/auth/src/routes/sign-in.tsx +117 -0
- package/templates/default/_gitignore +7 -0
- package/templates/default/actions/index.ts +58 -0
- package/templates/default/actions/todos.spec.ts +38 -0
- package/templates/default/index.html +13 -0
- package/templates/default/migrations/0000_init.sql +9 -0
- package/templates/default/migrations/meta/0000_snapshot.json +87 -0
- package/templates/default/migrations/meta/_journal.json +13 -0
- package/templates/default/package.json +44 -0
- package/templates/default/parlor.config.ts +12 -0
- package/templates/default/schema/index.ts +1 -0
- package/templates/default/schema/todos.ts +13 -0
- package/templates/default/server.ts +13 -0
- package/templates/default/src/agent/agent-button.tsx +120 -0
- package/templates/default/src/agent/agent-sidebar.tsx +828 -0
- package/templates/default/src/agent/agent.css +5 -0
- package/templates/default/src/agent/confirmation-card.tsx +42 -0
- package/templates/default/src/agent/sidebar-view.ts +36 -0
- package/templates/default/src/agent/views.ts +9 -0
- package/templates/default/src/app.css +1 -0
- package/templates/default/src/header-actions.tsx +9 -0
- package/templates/default/src/main.tsx +27 -0
- package/templates/default/src/parlor.d.ts +9 -0
- package/templates/default/src/routeTree.gen.ts +59 -0
- package/templates/default/src/routes/__root.tsx +29 -0
- package/templates/default/src/routes/index.tsx +141 -0
- package/templates/default/tsconfig.json +8 -0
- package/templates/default/vitest.config.ts +5 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
export class SourceSyntaxError extends Error {
|
|
3
|
+
path;
|
|
4
|
+
line;
|
|
5
|
+
name = "SourceSyntaxError";
|
|
6
|
+
constructor(path, line, message) {
|
|
7
|
+
super(`${path}:${line}: ${message}`);
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.line = line;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function parseSource(path, text) {
|
|
13
|
+
try {
|
|
14
|
+
const ast = parse(text, {
|
|
15
|
+
sourceType: "module",
|
|
16
|
+
plugins: path.endsWith(".tsx") ? ["typescript", "jsx"] : ["typescript"],
|
|
17
|
+
});
|
|
18
|
+
return { path, text, ast };
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const line = error.loc?.line ?? 0;
|
|
22
|
+
throw new SourceSyntaxError(path, line, error.message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Splices must not overlap; two inserts at one offset keep their order. */
|
|
26
|
+
export function applySplices(text, splices) {
|
|
27
|
+
const ordered = splices
|
|
28
|
+
.map((splice, index) => ({ splice, index }))
|
|
29
|
+
.sort((a, b) => b.splice.start - a.splice.start || b.index - a.index);
|
|
30
|
+
let result = text;
|
|
31
|
+
for (const { splice } of ordered) {
|
|
32
|
+
result = result.slice(0, splice.start) + splice.text + result.slice(splice.end);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/edit/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAWtC,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAG/B,IAAI;IACJ,IAAI;IAHG,IAAI,GAAG,mBAAmB,CAAC;IAC7C,YACW,IAAY,EACZ,IAAY,EACrB,OAAe;QAEf,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;oBAJ5B,IAAI;oBACJ,IAAI;IAIf,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE;YACtB,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;SACxE,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAI,KAAqC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,CAAC;QACnE,MAAM,IAAI,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AASD,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAA0B;IACnE,MAAM,OAAO,GAAG,OAAO;SACpB,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;SAC3C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACxE,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACjC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { parse } from \"@babel/parser\";\nimport type { File } from \"@babel/types\";\n\n/** A parsed TypeScript or TSX module. Edits never reprint it; they splice `text`. */\nexport interface SourceFile {\n /** Project-relative, with `/` separators. */\n readonly path: string;\n readonly text: string;\n readonly ast: File;\n}\n\nexport class SourceSyntaxError extends Error {\n override readonly name = \"SourceSyntaxError\";\n constructor(\n readonly path: string,\n readonly line: number,\n message: string,\n ) {\n super(`${path}:${line}: ${message}`);\n }\n}\n\nexport function parseSource(path: string, text: string): SourceFile {\n try {\n const ast = parse(text, {\n sourceType: \"module\",\n plugins: path.endsWith(\".tsx\") ? [\"typescript\", \"jsx\"] : [\"typescript\"],\n });\n return { path, text, ast };\n } catch (error) {\n const line = (error as { loc?: { line?: number } }).loc?.line ?? 0;\n throw new SourceSyntaxError(path, line, (error as Error).message);\n }\n}\n\n/** Offsets index the text before any splice of the same batch. */\nexport interface Splice {\n readonly start: number;\n readonly end: number;\n readonly text: string;\n}\n\n/** Splices must not overlap; two inserts at one offset keep their order. */\nexport function applySplices(text: string, splices: readonly Splice[]): string {\n const ordered = splices\n .map((splice, index) => ({ splice, index }))\n .sort((a, b) => b.splice.start - a.splice.start || b.index - a.index);\n let result = text;\n for (const { splice } of ordered) {\n result = result.slice(0, splice.start) + splice.text + result.slice(splice.end);\n }\n return result;\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CallExpression, Expression, Node, ObjectExpression, ObjectProperty } from "@babel/types";
|
|
2
|
+
import type { SourceFile } from "./source.js";
|
|
3
|
+
/** A call found by what it calls: `createServer` imported from `@parlor/server`, under any local name. */
|
|
4
|
+
export interface CallSite {
|
|
5
|
+
readonly callee: string;
|
|
6
|
+
readonly from: string;
|
|
7
|
+
/** Other names from `from` that stand in for `callee`, such as `createRootRouteWithContext`. */
|
|
8
|
+
readonly or?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export type Found<T> = {
|
|
11
|
+
readonly ok: true;
|
|
12
|
+
readonly value: T;
|
|
13
|
+
} | Unsafe;
|
|
14
|
+
export interface Unsafe {
|
|
15
|
+
readonly ok: false;
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const unsafe: (reason: string) => Unsafe;
|
|
19
|
+
export declare function textOf(file: SourceFile, node: Node): string;
|
|
20
|
+
export declare function forEachNode(node: Node, visit: (node: Node) => void): void;
|
|
21
|
+
/** The local name a value import of `name` from `from` binds, following `as` renames. */
|
|
22
|
+
export declare function importedName(file: SourceFile, name: string, from: string): string | undefined;
|
|
23
|
+
export declare function callsOf(file: SourceFile, local: string): CallExpression[];
|
|
24
|
+
/**
|
|
25
|
+
* Exactly one call of `site.callee`, or of a name in `site.or`, through its import binding;
|
|
26
|
+
* never a same-named local. A curried call, `f<T>()(options)`, is the call that takes options.
|
|
27
|
+
*/
|
|
28
|
+
export declare function findCall(file: SourceFile, site: CallSite): Found<CallExpression>;
|
|
29
|
+
export type ObjectArgument = {
|
|
30
|
+
readonly kind: "none";
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "object";
|
|
33
|
+
readonly node: ObjectExpression;
|
|
34
|
+
};
|
|
35
|
+
export declare function objectArgument(file: SourceFile, site: CallSite, call: CallExpression): Found<ObjectArgument>;
|
|
36
|
+
/** The property `key` of `object`, refusing shapes that could hide it: spreads and computed keys. */
|
|
37
|
+
export declare function propertyOf(file: SourceFile, object: ObjectExpression, key: string): Found<ObjectProperty | undefined>;
|
|
38
|
+
export declare function valueOf(property: ObjectProperty): Expression;
|
|
39
|
+
//# sourceMappingURL=syntax.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/edit/syntax.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,IAAI,EACJ,gBAAgB,EAChB,cAAc,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,0GAA0G;AAC1G,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,QAAQ,CAAC,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG,MAAM,CAAC;AACzE,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,MAAM,WAAY,MAAM,KAAG,MAAiC,CAAC;AAG1E,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAE3D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI,CAezE;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAkBpB;AAwBD,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE,CAYzE;AAYD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,CAgBhF;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEjE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,cAAc,GACnB,KAAK,CAAC,cAAc,CAAC,CAOvB;AASD,qGAAqG;AACrG,wBAAgB,UAAU,CACxB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,MAAM,GACV,KAAK,CAAC,cAAc,GAAG,SAAS,CAAC,CAgBnC;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,UAAU,CAE5D"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export const unsafe = (reason) => ({ ok: false, reason });
|
|
2
|
+
const found = (value) => ({ ok: true, value });
|
|
3
|
+
export function textOf(file, node) {
|
|
4
|
+
return file.text.slice(node.start ?? 0, node.end ?? 0);
|
|
5
|
+
}
|
|
6
|
+
export function forEachNode(node, visit) {
|
|
7
|
+
visit(node);
|
|
8
|
+
for (const [key, value] of Object.entries(node)) {
|
|
9
|
+
if (key === "loc" || key.endsWith("Comments"))
|
|
10
|
+
continue;
|
|
11
|
+
const children = Array.isArray(value) ? value : [value];
|
|
12
|
+
for (const child of children) {
|
|
13
|
+
if (child &&
|
|
14
|
+
typeof child === "object" &&
|
|
15
|
+
typeof child.type === "string") {
|
|
16
|
+
forEachNode(child, visit);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** The local name a value import of `name` from `from` binds, following `as` renames. */
|
|
22
|
+
export function importedName(file, name, from) {
|
|
23
|
+
for (const statement of file.ast.program.body) {
|
|
24
|
+
if (statement.type !== "ImportDeclaration" || statement.source.value !== from)
|
|
25
|
+
continue;
|
|
26
|
+
if (statement.importKind === "type")
|
|
27
|
+
continue;
|
|
28
|
+
for (const specifier of statement.specifiers) {
|
|
29
|
+
if (specifier.type === "ImportSpecifier" &&
|
|
30
|
+
specifier.importKind !== "type" &&
|
|
31
|
+
(specifier.imported.type === "Identifier"
|
|
32
|
+
? specifier.imported.name
|
|
33
|
+
: specifier.imported.value) === name) {
|
|
34
|
+
return specifier.local.name;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function declaresAtTopLevel(file, name) {
|
|
41
|
+
return file.ast.program.body.some((statement) => {
|
|
42
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
43
|
+
if (!declaration)
|
|
44
|
+
return false;
|
|
45
|
+
if ((declaration.type === "FunctionDeclaration" ||
|
|
46
|
+
declaration.type === "ClassDeclaration") &&
|
|
47
|
+
declaration.id?.name === name) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return (declaration.type === "VariableDeclaration" &&
|
|
51
|
+
declaration.declarations.some((declarator) => declarator.id.type === "Identifier" && declarator.id.name === name));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export function callsOf(file, local) {
|
|
55
|
+
const calls = [];
|
|
56
|
+
forEachNode(file.ast, (node) => {
|
|
57
|
+
if (node.type === "CallExpression" &&
|
|
58
|
+
node.callee.type === "Identifier" &&
|
|
59
|
+
node.callee.name === local) {
|
|
60
|
+
calls.push(node);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return calls;
|
|
64
|
+
}
|
|
65
|
+
/** The call a curried factory returns, `f<T>()(options)`, when `call` is `f<T>()`. */
|
|
66
|
+
function curriedCall(file, call) {
|
|
67
|
+
if (call.arguments.length > 0)
|
|
68
|
+
return call;
|
|
69
|
+
let outer;
|
|
70
|
+
forEachNode(file.ast, (node) => {
|
|
71
|
+
if (node.type === "CallExpression" && node.callee === call)
|
|
72
|
+
outer = node;
|
|
73
|
+
});
|
|
74
|
+
return outer ?? call;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Exactly one call of `site.callee`, or of a name in `site.or`, through its import binding;
|
|
78
|
+
* never a same-named local. A curried call, `f<T>()(options)`, is the call that takes options.
|
|
79
|
+
*/
|
|
80
|
+
export function findCall(file, site) {
|
|
81
|
+
const names = [site.callee, ...(site.or ?? [])];
|
|
82
|
+
const locals = names.flatMap((name) => importedName(file, name, site.from) ?? []);
|
|
83
|
+
if (locals.length === 0) {
|
|
84
|
+
return unsafe(declaresAtTopLevel(file, site.callee)
|
|
85
|
+
? `\`${site.callee}\` here is declared in ${file.path}, not imported from ${site.from}.`
|
|
86
|
+
: `${file.path} does not import ${names.join(" or ")} from ${site.from}.`);
|
|
87
|
+
}
|
|
88
|
+
const calls = locals.flatMap((local) => callsOf(file, local));
|
|
89
|
+
if (calls.length === 0)
|
|
90
|
+
return unsafe(`${file.path} never calls ${site.callee}().`);
|
|
91
|
+
if (calls.length > 1) {
|
|
92
|
+
return unsafe(`${site.callee}() is called in more than one place in ${file.path}.`);
|
|
93
|
+
}
|
|
94
|
+
return found(curriedCall(file, calls[0]));
|
|
95
|
+
}
|
|
96
|
+
export function objectArgument(file, site, call) {
|
|
97
|
+
const [first] = call.arguments;
|
|
98
|
+
if (!first)
|
|
99
|
+
return found({ kind: "none" });
|
|
100
|
+
if (first.type === "ObjectExpression")
|
|
101
|
+
return found({ kind: "object", node: first });
|
|
102
|
+
return unsafe(`${site.callee}() is called with \`${textOf(file, first)}\`, not an object literal.`);
|
|
103
|
+
}
|
|
104
|
+
function keyName(property) {
|
|
105
|
+
if (property.computed)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (property.key.type === "Identifier")
|
|
108
|
+
return property.key.name;
|
|
109
|
+
if (property.key.type === "StringLiteral")
|
|
110
|
+
return property.key.value;
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/** The property `key` of `object`, refusing shapes that could hide it: spreads and computed keys. */
|
|
114
|
+
export function propertyOf(file, object, key) {
|
|
115
|
+
for (const property of object.properties) {
|
|
116
|
+
if (property.type === "SpreadElement") {
|
|
117
|
+
return unsafe(`the object passes \`...${textOf(file, property.argument)}\`, which may already set \`${key}\`.`);
|
|
118
|
+
}
|
|
119
|
+
if (property.type === "ObjectProperty" && property.computed) {
|
|
120
|
+
return unsafe(`the object has a computed key, which may already set \`${key}\`.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const match = object.properties.find((property) => property.type === "ObjectProperty" && keyName(property) === key);
|
|
124
|
+
return found(match);
|
|
125
|
+
}
|
|
126
|
+
export function valueOf(property) {
|
|
127
|
+
return property.value;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=syntax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntax.js","sourceRoot":"","sources":["../../src/edit/syntax.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CallExpression,\n Expression,\n Node,\n ObjectExpression,\n ObjectProperty,\n} from \"@babel/types\";\n\nimport type { SourceFile } from \"./source.js\";\n\n/** A call found by what it calls: `createServer` imported from `@parlor/server`, under any local name. */\nexport interface CallSite {\n readonly callee: string;\n readonly from: string;\n /** Other names from `from` that stand in for `callee`, such as `createRootRouteWithContext`. */\n readonly or?: readonly string[];\n}\n\nexport type Found<T> = { readonly ok: true; readonly value: T } | Unsafe;\nexport interface Unsafe {\n readonly ok: false;\n readonly reason: string;\n}\n\nexport const unsafe = (reason: string): Unsafe => ({ ok: false, reason });\nconst found = <T>(value: T): Found<T> => ({ ok: true, value });\n\nexport function textOf(file: SourceFile, node: Node): string {\n return file.text.slice(node.start ?? 0, node.end ?? 0);\n}\n\nexport function forEachNode(node: Node, visit: (node: Node) => void): void {\n visit(node);\n for (const [key, value] of Object.entries(node)) {\n if (key === \"loc\" || key.endsWith(\"Comments\")) continue;\n const children: unknown[] = Array.isArray(value) ? value : [value];\n for (const child of children) {\n if (\n child &&\n typeof child === \"object\" &&\n typeof (child as Node).type === \"string\"\n ) {\n forEachNode(child as Node, visit);\n }\n }\n }\n}\n\n/** The local name a value import of `name` from `from` binds, following `as` renames. */\nexport function importedName(\n file: SourceFile,\n name: string,\n from: string,\n): string | undefined {\n for (const statement of file.ast.program.body) {\n if (statement.type !== \"ImportDeclaration\" || statement.source.value !== from)\n continue;\n if (statement.importKind === \"type\") continue;\n for (const specifier of statement.specifiers) {\n if (\n specifier.type === \"ImportSpecifier\" &&\n specifier.importKind !== \"type\" &&\n (specifier.imported.type === \"Identifier\"\n ? specifier.imported.name\n : specifier.imported.value) === name\n ) {\n return specifier.local.name;\n }\n }\n }\n return undefined;\n}\n\nfunction declaresAtTopLevel(file: SourceFile, name: string): boolean {\n return file.ast.program.body.some((statement) => {\n const declaration =\n statement.type === \"ExportNamedDeclaration\" ? statement.declaration : statement;\n if (!declaration) return false;\n if (\n (declaration.type === \"FunctionDeclaration\" ||\n declaration.type === \"ClassDeclaration\") &&\n declaration.id?.name === name\n ) {\n return true;\n }\n return (\n declaration.type === \"VariableDeclaration\" &&\n declaration.declarations.some(\n (declarator) =>\n declarator.id.type === \"Identifier\" && declarator.id.name === name,\n )\n );\n });\n}\n\nexport function callsOf(file: SourceFile, local: string): CallExpression[] {\n const calls: CallExpression[] = [];\n forEachNode(file.ast, (node) => {\n if (\n node.type === \"CallExpression\" &&\n node.callee.type === \"Identifier\" &&\n node.callee.name === local\n ) {\n calls.push(node);\n }\n });\n return calls;\n}\n\n/** The call a curried factory returns, `f<T>()(options)`, when `call` is `f<T>()`. */\nfunction curriedCall(file: SourceFile, call: CallExpression): CallExpression {\n if (call.arguments.length > 0) return call;\n let outer: CallExpression | undefined;\n forEachNode(file.ast, (node) => {\n if (node.type === \"CallExpression\" && node.callee === call) outer = node;\n });\n return outer ?? call;\n}\n\n/**\n * Exactly one call of `site.callee`, or of a name in `site.or`, through its import binding;\n * never a same-named local. A curried call, `f<T>()(options)`, is the call that takes options.\n */\nexport function findCall(file: SourceFile, site: CallSite): Found<CallExpression> {\n const names = [site.callee, ...(site.or ?? [])];\n const locals = names.flatMap((name) => importedName(file, name, site.from) ?? []);\n if (locals.length === 0) {\n return unsafe(\n declaresAtTopLevel(file, site.callee)\n ? `\\`${site.callee}\\` here is declared in ${file.path}, not imported from ${site.from}.`\n : `${file.path} does not import ${names.join(\" or \")} from ${site.from}.`,\n );\n }\n const calls = locals.flatMap((local) => callsOf(file, local));\n if (calls.length === 0) return unsafe(`${file.path} never calls ${site.callee}().`);\n if (calls.length > 1) {\n return unsafe(`${site.callee}() is called in more than one place in ${file.path}.`);\n }\n return found(curriedCall(file, calls[0]!));\n}\n\nexport type ObjectArgument =\n | { readonly kind: \"none\" }\n | { readonly kind: \"object\"; readonly node: ObjectExpression };\n\nexport function objectArgument(\n file: SourceFile,\n site: CallSite,\n call: CallExpression,\n): Found<ObjectArgument> {\n const [first] = call.arguments;\n if (!first) return found({ kind: \"none\" });\n if (first.type === \"ObjectExpression\") return found({ kind: \"object\", node: first });\n return unsafe(\n `${site.callee}() is called with \\`${textOf(file, first)}\\`, not an object literal.`,\n );\n}\n\nfunction keyName(property: ObjectProperty): string | undefined {\n if (property.computed) return undefined;\n if (property.key.type === \"Identifier\") return property.key.name;\n if (property.key.type === \"StringLiteral\") return property.key.value;\n return undefined;\n}\n\n/** The property `key` of `object`, refusing shapes that could hide it: spreads and computed keys. */\nexport function propertyOf(\n file: SourceFile,\n object: ObjectExpression,\n key: string,\n): Found<ObjectProperty | undefined> {\n for (const property of object.properties) {\n if (property.type === \"SpreadElement\") {\n return unsafe(\n `the object passes \\`...${textOf(file, property.argument)}\\`, which may already set \\`${key}\\`.`,\n );\n }\n if (property.type === \"ObjectProperty\" && property.computed) {\n return unsafe(`the object has a computed key, which may already set \\`${key}\\`.`);\n }\n }\n const match = object.properties.find(\n (property): property is ObjectProperty =>\n property.type === \"ObjectProperty\" && keyName(property) === key,\n );\n return found(match);\n}\n\nexport function valueOf(property: ObjectProperty): Expression {\n return property.value as Expression;\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAkB,IAAI,iBAAiB;CACxC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,OAAO,WAAY,SAAQ,KAAK;IAClB,IAAI,GAAG,aAAa,CAAC;CACxC","sourcesContent":["/** A refusal with a message for the terminal: the bin prints it and exits 1. */\nexport class CreateError extends Error {\n override readonly name = \"CreateError\";\n}\n"]}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PackageManifest, ProjectFiles } from "./add/plan.js";
|
|
2
|
+
/** npm never publishes a file named `.gitignore`, so templates store it as `_gitignore`. */
|
|
3
|
+
export declare const RENAMED_ON_COPY: Readonly<Record<string, string>>;
|
|
4
|
+
export declare const NEVER_COPIED: ReadonlySet<string>;
|
|
5
|
+
export interface TemplateFiles {
|
|
6
|
+
list(template: string, dir: string): Promise<readonly string[]>;
|
|
7
|
+
read(template: string, path: string): Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
/** `manifest` reads `<rootDir>/node_modules/<name>/package.json`. */
|
|
10
|
+
export declare function projectFiles(rootDir: string): ProjectFiles;
|
|
11
|
+
export declare function inMemoryFiles(files: ReadonlyMap<string, string>, manifests?: ReadonlyMap<string, PackageManifest>): ProjectFiles;
|
|
12
|
+
export declare function templateFiles(templatesDir?: string): TemplateFiles;
|
|
13
|
+
/** Temp-and-rename, so a crash leaves whole files. */
|
|
14
|
+
export declare function writeFiles(rootDir: string, files: ReadonlyMap<string, string>): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGnE,4FAA4F;AAC5F,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE5D,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAAwC,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACvD;AA0CD,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAM1D;AAID,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,SAAS,GAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAa,GAC1D,YAAY,CAOd;AAED,wBAAgB,aAAa,CAAC,YAAY,GAAE,MAAsB,GAAG,aAAa,CAOjF;AAED,sDAAsD;AACtD,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAOf"}
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { TEMPLATES_DIR } from "./version.js";
|
|
4
|
+
/** npm never publishes a file named `.gitignore`, so templates store it as `_gitignore`. */
|
|
5
|
+
export const RENAMED_ON_COPY = {
|
|
6
|
+
_gitignore: ".gitignore",
|
|
7
|
+
};
|
|
8
|
+
export const NEVER_COPIED = new Set(["node_modules", ".parlor"]);
|
|
9
|
+
function onCopy(path) {
|
|
10
|
+
const name = path.split("/").pop() ?? path;
|
|
11
|
+
const renamed = RENAMED_ON_COPY[name];
|
|
12
|
+
return renamed ? `${path.slice(0, -name.length)}${renamed}` : path;
|
|
13
|
+
}
|
|
14
|
+
function fromCopy(path) {
|
|
15
|
+
const name = path.split("/").pop() ?? path;
|
|
16
|
+
const original = Object.entries(RENAMED_ON_COPY).find(([, to]) => to === name)?.[0];
|
|
17
|
+
return original ? `${path.slice(0, -name.length)}${original}` : path;
|
|
18
|
+
}
|
|
19
|
+
async function walk(root, dir, skip) {
|
|
20
|
+
const entries = await readdir(join(root, dir), { withFileTypes: true }).catch((error) => {
|
|
21
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR")
|
|
22
|
+
return [];
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
const nested = await Promise.all(entries
|
|
26
|
+
.filter((entry) => !skip.has(entry.name))
|
|
27
|
+
.map((entry) => {
|
|
28
|
+
const path = dir ? `${dir}/${entry.name}` : entry.name;
|
|
29
|
+
return entry.isDirectory() ? walk(root, path, skip) : Promise.resolve([path]);
|
|
30
|
+
}));
|
|
31
|
+
return nested.flat();
|
|
32
|
+
}
|
|
33
|
+
const readJson = (path) => readFile(path, "utf8").then((text) => JSON.parse(text), () => undefined);
|
|
34
|
+
/** `manifest` reads `<rootDir>/node_modules/<name>/package.json`. */
|
|
35
|
+
export function projectFiles(rootDir) {
|
|
36
|
+
return {
|
|
37
|
+
read: (path) => readFile(join(rootDir, path), "utf8").catch(() => undefined),
|
|
38
|
+
list: async (dir) => (await walk(rootDir, dir, NEVER_LISTED)).sort(),
|
|
39
|
+
manifest: (name) => readJson(join(rootDir, "node_modules", name, "package.json")),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const NEVER_LISTED = new Set(["node_modules", ".git", ".parlor"]);
|
|
43
|
+
export function inMemoryFiles(files, manifests = new Map()) {
|
|
44
|
+
return {
|
|
45
|
+
read: async (path) => files.get(path),
|
|
46
|
+
list: async (dir) => [...files.keys()].filter((path) => dir === "" || path.startsWith(`${dir}/`)).sort(),
|
|
47
|
+
manifest: async (name) => manifests.get(name),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function templateFiles(templatesDir = TEMPLATES_DIR) {
|
|
51
|
+
return {
|
|
52
|
+
list: async (template, dir) => (await walk(join(templatesDir, template), dir, NEVER_COPIED)).map(onCopy).sort(),
|
|
53
|
+
read: (template, path) => readFile(join(templatesDir, template, fromCopy(path)), "utf8"),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Temp-and-rename, so a crash leaves whole files. */
|
|
57
|
+
export async function writeFiles(rootDir, files) {
|
|
58
|
+
for (const [path, content] of files) {
|
|
59
|
+
const target = join(rootDir, path);
|
|
60
|
+
await mkdir(dirname(target), { recursive: true });
|
|
61
|
+
await writeFile(`${target}.parlor-tmp`, content);
|
|
62
|
+
await rename(`${target}.parlor-tmp`, target);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport type { PackageManifest, ProjectFiles } from \"./add/plan.js\";\nimport { TEMPLATES_DIR } from \"./version.js\";\n\n/** npm never publishes a file named `.gitignore`, so templates store it as `_gitignore`. */\nexport const RENAMED_ON_COPY: Readonly<Record<string, string>> = {\n _gitignore: \".gitignore\",\n};\n\nexport const NEVER_COPIED: ReadonlySet<string> = new Set([\"node_modules\", \".parlor\"]);\n\nexport interface TemplateFiles {\n list(template: string, dir: string): Promise<readonly string[]>;\n read(template: string, path: string): Promise<string>;\n}\n\nfunction onCopy(path: string): string {\n const name = path.split(\"/\").pop() ?? path;\n const renamed = RENAMED_ON_COPY[name];\n return renamed ? `${path.slice(0, -name.length)}${renamed}` : path;\n}\n\nfunction fromCopy(path: string): string {\n const name = path.split(\"/\").pop() ?? path;\n const original = Object.entries(RENAMED_ON_COPY).find(([, to]) => to === name)?.[0];\n return original ? `${path.slice(0, -name.length)}${original}` : path;\n}\n\nasync function walk(\n root: string,\n dir: string,\n skip: ReadonlySet<string>,\n): Promise<string[]> {\n const entries = await readdir(join(root, dir), { withFileTypes: true }).catch(\n (error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\" || error.code === \"ENOTDIR\") return [];\n throw error;\n },\n );\n const nested = await Promise.all(\n entries\n .filter((entry) => !skip.has(entry.name))\n .map((entry) => {\n const path = dir ? `${dir}/${entry.name}` : entry.name;\n return entry.isDirectory() ? walk(root, path, skip) : Promise.resolve([path]);\n }),\n );\n return nested.flat();\n}\n\nconst readJson = (path: string) =>\n readFile(path, \"utf8\").then(\n (text) => JSON.parse(text) as PackageManifest,\n () => undefined,\n );\n\n/** `manifest` reads `<rootDir>/node_modules/<name>/package.json`. */\nexport function projectFiles(rootDir: string): ProjectFiles {\n return {\n read: (path) => readFile(join(rootDir, path), \"utf8\").catch(() => undefined),\n list: async (dir) => (await walk(rootDir, dir, NEVER_LISTED)).sort(),\n manifest: (name) => readJson(join(rootDir, \"node_modules\", name, \"package.json\")),\n };\n}\n\nconst NEVER_LISTED: ReadonlySet<string> = new Set([\"node_modules\", \".git\", \".parlor\"]);\n\nexport function inMemoryFiles(\n files: ReadonlyMap<string, string>,\n manifests: ReadonlyMap<string, PackageManifest> = new Map(),\n): ProjectFiles {\n return {\n read: async (path) => files.get(path),\n list: async (dir) =>\n [...files.keys()].filter((path) => dir === \"\" || path.startsWith(`${dir}/`)).sort(),\n manifest: async (name) => manifests.get(name),\n };\n}\n\nexport function templateFiles(templatesDir: string = TEMPLATES_DIR): TemplateFiles {\n return {\n list: async (template, dir) =>\n (await walk(join(templatesDir, template), dir, NEVER_COPIED)).map(onCopy).sort(),\n read: (template, path) =>\n readFile(join(templatesDir, template, fromCopy(path)), \"utf8\"),\n };\n}\n\n/** Temp-and-rename, so a crash leaves whole files. */\nexport async function writeFiles(\n rootDir: string,\n files: ReadonlyMap<string, string>,\n): Promise<void> {\n for (const [path, content] of files) {\n const target = join(rootDir, path);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(`${target}.parlor-tmp`, content);\n await rename(`${target}.parlor-tmp`, target);\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProjectLayout } from "./add/addon.js";
|
|
2
|
+
import { type PackageManifest } from "./add/plan.js";
|
|
3
|
+
import { type TemplateFiles } from "./files.js";
|
|
4
|
+
/**
|
|
5
|
+
* `fixtures/with-auth` is the default template after `parlor add auth`, checked in as a
|
|
6
|
+
* workspace package so CI typechecks and tests the composed project. `scripts/write-fixtures.ts`
|
|
7
|
+
* rewrites it from the engine; `fixtures.spec.ts` fails when the two differ, so it is never
|
|
8
|
+
* synced by hand.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FIXTURES_DIR: string;
|
|
11
|
+
export declare const WITH_AUTH: {
|
|
12
|
+
readonly dir: "with-auth";
|
|
13
|
+
readonly packageName: "parlor-fixture-with-auth";
|
|
14
|
+
readonly addons: readonly ["auth"];
|
|
15
|
+
};
|
|
16
|
+
/** The default layout, which both the template and the fixture use. */
|
|
17
|
+
export declare const DEFAULT_LAYOUT: Omit<ProjectLayout, "rootDir">;
|
|
18
|
+
/**
|
|
19
|
+
* The fixture's files before its generators run: the default template with `_gitignore` kept
|
|
20
|
+
* under its template name, the plan's writes laid over it, and `package.json` as the package
|
|
21
|
+
* manager would leave it, except that `"framework"` dependencies are `workspace:*` so the
|
|
22
|
+
* fixture installs inside the repo. `authManifest` stands in for the installed `@parlor/auth`,
|
|
23
|
+
* which is how `better-auth` gets its pin.
|
|
24
|
+
*/
|
|
25
|
+
export declare function composeWithAuth(templates: TemplateFiles, authManifest: PackageManifest): Promise<ReadonlyMap<string, string>>;
|
|
26
|
+
/** Paths the fixture holds that the composition does not: written by the generators. */
|
|
27
|
+
export declare function isGeneratedPath(path: string): boolean;
|
|
28
|
+
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,KAAK,eAAe,EAAc,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,MAE1B,CAAC;AAEF,eAAO,MAAM,SAAS;aACpB,GAAG,EAAE,WAAW;aAChB,WAAW,EAAE,0BAA0B;aACvC,MAAM,YAAG,MAAM;CACP,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,cAAc,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAKzD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,aAAa,EACxB,YAAY,EAAE,eAAe,GAC5B,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA8CtC;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKrD"}
|
package/dist/fixtures.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { planAddons } from "./add/plan.js";
|
|
3
|
+
import { ADDONS } from "./addons/index.js";
|
|
4
|
+
import { inMemoryFiles } from "./files.js";
|
|
5
|
+
/**
|
|
6
|
+
* `fixtures/with-auth` is the default template after `parlor add auth`, checked in as a
|
|
7
|
+
* workspace package so CI typechecks and tests the composed project. `scripts/write-fixtures.ts`
|
|
8
|
+
* rewrites it from the engine; `fixtures.spec.ts` fails when the two differ, so it is never
|
|
9
|
+
* synced by hand.
|
|
10
|
+
*/
|
|
11
|
+
export const FIXTURES_DIR = fileURLToPath(new URL("../fixtures/", import.meta.url));
|
|
12
|
+
export const WITH_AUTH = {
|
|
13
|
+
dir: "with-auth",
|
|
14
|
+
packageName: "parlor-fixture-with-auth",
|
|
15
|
+
addons: ["auth"],
|
|
16
|
+
};
|
|
17
|
+
/** The default layout, which both the template and the fixture use. */
|
|
18
|
+
export const DEFAULT_LAYOUT = {
|
|
19
|
+
serverEntry: "server.ts",
|
|
20
|
+
schemaDir: "schema",
|
|
21
|
+
migrationsDir: "migrations",
|
|
22
|
+
routeTree: "src/routeTree.gen.ts",
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The fixture's files before its generators run: the default template with `_gitignore` kept
|
|
26
|
+
* under its template name, the plan's writes laid over it, and `package.json` as the package
|
|
27
|
+
* manager would leave it, except that `"framework"` dependencies are `workspace:*` so the
|
|
28
|
+
* fixture installs inside the repo. `authManifest` stands in for the installed `@parlor/auth`,
|
|
29
|
+
* which is how `better-auth` gets its pin.
|
|
30
|
+
*/
|
|
31
|
+
export async function composeWithAuth(templates, authManifest) {
|
|
32
|
+
const files = new Map();
|
|
33
|
+
for (const path of await templates.list("default", "")) {
|
|
34
|
+
files.set(path, await templates.read("default", path));
|
|
35
|
+
}
|
|
36
|
+
const plan = await planAddons({ ...DEFAULT_LAYOUT, rootDir: "/" }, WITH_AUTH.addons, inMemoryFiles(files, new Map([["@parlor/auth", authManifest]])), templates);
|
|
37
|
+
if (plan.kind !== "ready") {
|
|
38
|
+
throw new Error(`The default template no longer takes ${WITH_AUTH.addons.join(", ")}: ${plan.kind}`);
|
|
39
|
+
}
|
|
40
|
+
for (const write of plan.writes)
|
|
41
|
+
files.set(write.path, write.content);
|
|
42
|
+
const manifest = JSON.parse(files.get("package.json") ?? "{}");
|
|
43
|
+
const declared = WITH_AUTH.addons.flatMap((name) => Object.entries(ADDONS[name].dependencies));
|
|
44
|
+
const dependencies = { ...manifest.dependencies };
|
|
45
|
+
for (const dependency of plan.dependencies) {
|
|
46
|
+
const version = declared.find(([name]) => name === dependency.name)?.[1];
|
|
47
|
+
dependencies[dependency.name] =
|
|
48
|
+
version === "framework" ? "workspace:*" : String(dependency.version);
|
|
49
|
+
}
|
|
50
|
+
files.set("package.json", `${JSON.stringify({
|
|
51
|
+
...manifest,
|
|
52
|
+
name: WITH_AUTH.packageName,
|
|
53
|
+
dependencies: Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b))),
|
|
54
|
+
}, null, 2)}\n`);
|
|
55
|
+
const ignore = files.get(".gitignore");
|
|
56
|
+
files.delete(".gitignore");
|
|
57
|
+
if (ignore !== undefined)
|
|
58
|
+
files.set("_gitignore", ignore);
|
|
59
|
+
return files;
|
|
60
|
+
}
|
|
61
|
+
/** Paths the fixture holds that the composition does not: written by the generators. */
|
|
62
|
+
export function isGeneratedPath(path) {
|
|
63
|
+
return (path === DEFAULT_LAYOUT.routeTree ||
|
|
64
|
+
path.startsWith(`${DEFAULT_LAYOUT.migrationsDir}/`));
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"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","sourcesContent":["import { fileURLToPath } from \"node:url\";\n\nimport type { ProjectLayout } from \"./add/addon.js\";\nimport { type PackageManifest, planAddons } from \"./add/plan.js\";\nimport { ADDONS } from \"./addons/index.js\";\nimport { inMemoryFiles, type TemplateFiles } from \"./files.js\";\n\n/**\n * `fixtures/with-auth` is the default template after `parlor add auth`, checked in as a\n * workspace package so CI typechecks and tests the composed project. `scripts/write-fixtures.ts`\n * rewrites it from the engine; `fixtures.spec.ts` fails when the two differ, so it is never\n * synced by hand.\n */\nexport const FIXTURES_DIR: string = fileURLToPath(\n new URL(\"../fixtures/\", import.meta.url),\n);\n\nexport const WITH_AUTH = {\n dir: \"with-auth\",\n packageName: \"parlor-fixture-with-auth\",\n addons: [\"auth\"],\n} as const;\n\n/** The default layout, which both the template and the fixture use. */\nexport const DEFAULT_LAYOUT: Omit<ProjectLayout, \"rootDir\"> = {\n serverEntry: \"server.ts\",\n schemaDir: \"schema\",\n migrationsDir: \"migrations\",\n routeTree: \"src/routeTree.gen.ts\",\n};\n\n/**\n * The fixture's files before its generators run: the default template with `_gitignore` kept\n * under its template name, the plan's writes laid over it, and `package.json` as the package\n * manager would leave it, except that `\"framework\"` dependencies are `workspace:*` so the\n * fixture installs inside the repo. `authManifest` stands in for the installed `@parlor/auth`,\n * which is how `better-auth` gets its pin.\n */\nexport async function composeWithAuth(\n templates: TemplateFiles,\n authManifest: PackageManifest,\n): Promise<ReadonlyMap<string, string>> {\n const files = new Map<string, string>();\n for (const path of await templates.list(\"default\", \"\")) {\n files.set(path, await templates.read(\"default\", path));\n }\n const plan = await planAddons(\n { ...DEFAULT_LAYOUT, rootDir: \"/\" },\n WITH_AUTH.addons,\n inMemoryFiles(files, new Map([[\"@parlor/auth\", authManifest]])),\n templates,\n );\n if (plan.kind !== \"ready\") {\n throw new Error(\n `The default template no longer takes ${WITH_AUTH.addons.join(\", \")}: ${plan.kind}`,\n );\n }\n for (const write of plan.writes) files.set(write.path, write.content);\n const manifest = JSON.parse(files.get(\"package.json\") ?? \"{}\") as PackageManifest &\n Record<string, unknown>;\n const declared = WITH_AUTH.addons.flatMap((name) =>\n Object.entries(ADDONS[name].dependencies),\n );\n const dependencies: Record<string, string> = { ...manifest.dependencies };\n for (const dependency of plan.dependencies) {\n const version = declared.find(([name]) => name === dependency.name)?.[1];\n dependencies[dependency.name] =\n version === \"framework\" ? \"workspace:*\" : String(dependency.version);\n }\n files.set(\n \"package.json\",\n `${JSON.stringify(\n {\n ...manifest,\n name: WITH_AUTH.packageName,\n dependencies: Object.fromEntries(\n Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)),\n ),\n },\n null,\n 2,\n )}\\n`,\n );\n const ignore = files.get(\".gitignore\");\n files.delete(\".gitignore\");\n if (ignore !== undefined) files.set(\"_gitignore\", ignore);\n return files;\n}\n\n/** Paths the fixture holds that the composition does not: written by the generators. */\nexport function isGeneratedPath(path: string): boolean {\n return (\n path === DEFAULT_LAYOUT.routeTree ||\n path.startsWith(`${DEFAULT_LAYOUT.migrationsDir}/`)\n );\n}\n"]}
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const INITIAL_COMMIT_MESSAGE = "Initial commit from create-parlor";
|
|
2
|
+
export type GitOutcome = {
|
|
3
|
+
readonly kind: "committed";
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: "skipped";
|
|
6
|
+
readonly reason: "inside-repository" | "git-missing";
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: "failed";
|
|
9
|
+
readonly message: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* `git init -b main`, `git add -A`, one commit. Never fails the create: a missing identity or
|
|
13
|
+
* hook failure becomes `failed` with git's message, and the files stay as they are.
|
|
14
|
+
*/
|
|
15
|
+
export declare function initialCommit(directory: string): Promise<GitOutcome>;
|
|
16
|
+
/**
|
|
17
|
+
* Paths with uncommitted changes, untracked included, so `parlor add` refuses to edit a tree
|
|
18
|
+
* whose diff the developer could not review afterwards. `undefined` outside a work tree or
|
|
19
|
+
* without git: nothing to check against.
|
|
20
|
+
*/
|
|
21
|
+
export declare function uncommittedChanges(directory: string): Promise<readonly string[] | undefined>;
|
|
22
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,sBAAsB,sCAAsC,CAAC;AAE1E,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAAA;CAAE,GAClF;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAO1D;;;GAGG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAoB1E;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAexC"}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const run = promisify(execFile);
|
|
4
|
+
export const INITIAL_COMMIT_MESSAGE = "Initial commit from create-parlor";
|
|
5
|
+
async function git(directory, args) {
|
|
6
|
+
const { stdout } = await run("git", [...args], { cwd: directory });
|
|
7
|
+
return stdout;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* `git init -b main`, `git add -A`, one commit. Never fails the create: a missing identity or
|
|
11
|
+
* hook failure becomes `failed` with git's message, and the files stay as they are.
|
|
12
|
+
*/
|
|
13
|
+
export async function initialCommit(directory) {
|
|
14
|
+
try {
|
|
15
|
+
await git(directory, ["--version"]);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { kind: "skipped", reason: "git-missing" };
|
|
19
|
+
}
|
|
20
|
+
const inside = await git(directory, ["rev-parse", "--is-inside-work-tree"]).then(() => true, () => false);
|
|
21
|
+
if (inside)
|
|
22
|
+
return { kind: "skipped", reason: "inside-repository" };
|
|
23
|
+
try {
|
|
24
|
+
await git(directory, ["init", "-b", "main"]);
|
|
25
|
+
await git(directory, ["add", "-A"]);
|
|
26
|
+
await git(directory, ["commit", "-m", INITIAL_COMMIT_MESSAGE]);
|
|
27
|
+
return { kind: "committed" };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const stderr = error.stderr?.trim();
|
|
31
|
+
return { kind: "failed", message: stderr || error.message };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Paths with uncommitted changes, untracked included, so `parlor add` refuses to edit a tree
|
|
36
|
+
* whose diff the developer could not review afterwards. `undefined` outside a work tree or
|
|
37
|
+
* without git: nothing to check against.
|
|
38
|
+
*/
|
|
39
|
+
export async function uncommittedChanges(directory) {
|
|
40
|
+
try {
|
|
41
|
+
const output = await git(directory, [
|
|
42
|
+
"status",
|
|
43
|
+
"--porcelain",
|
|
44
|
+
"--untracked-files=all",
|
|
45
|
+
".",
|
|
46
|
+
]);
|
|
47
|
+
return output
|
|
48
|
+
.split("\n")
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.map((line) => line.slice(3).replace(/^.* -> /, ""));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEhC,MAAM,CAAC,MAAM,sBAAsB,GAAG,mCAAmC,CAAC;AAO1E,KAAK,UAAU,GAAG,CAAC,SAAiB,EAAE,IAAuB;IAC3D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACpD,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAC9E,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAC;IACF,IAAI,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACpE,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7C,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QACpC,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC;QAC/D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAI,KAA6B,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAK,KAAe,CAAC,OAAO,EAAE,CAAC;IACzE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAiB;IAEjB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE;YAClC,QAAQ;YACR,aAAa;YACb,uBAAuB;YACvB,GAAG;SACJ,CAAC,CAAC;QACH,OAAO,MAAM;aACV,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC;aACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst run = promisify(execFile);\n\nexport const INITIAL_COMMIT_MESSAGE = \"Initial commit from create-parlor\";\n\nexport type GitOutcome =\n | { readonly kind: \"committed\" }\n | { readonly kind: \"skipped\"; readonly reason: \"inside-repository\" | \"git-missing\" }\n | { readonly kind: \"failed\"; readonly message: string };\n\nasync function git(directory: string, args: readonly string[]): Promise<string> {\n const { stdout } = await run(\"git\", [...args], { cwd: directory });\n return stdout;\n}\n\n/**\n * `git init -b main`, `git add -A`, one commit. Never fails the create: a missing identity or\n * hook failure becomes `failed` with git's message, and the files stay as they are.\n */\nexport async function initialCommit(directory: string): Promise<GitOutcome> {\n try {\n await git(directory, [\"--version\"]);\n } catch {\n return { kind: \"skipped\", reason: \"git-missing\" };\n }\n const inside = await git(directory, [\"rev-parse\", \"--is-inside-work-tree\"]).then(\n () => true,\n () => false,\n );\n if (inside) return { kind: \"skipped\", reason: \"inside-repository\" };\n try {\n await git(directory, [\"init\", \"-b\", \"main\"]);\n await git(directory, [\"add\", \"-A\"]);\n await git(directory, [\"commit\", \"-m\", INITIAL_COMMIT_MESSAGE]);\n return { kind: \"committed\" };\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n return { kind: \"failed\", message: stderr || (error as Error).message };\n }\n}\n\n/**\n * Paths with uncommitted changes, untracked included, so `parlor add` refuses to edit a tree\n * whose diff the developer could not review afterwards. `undefined` outside a work tree or\n * without git: nothing to check against.\n */\nexport async function uncommittedChanges(\n directory: string,\n): Promise<readonly string[] | undefined> {\n try {\n const output = await git(directory, [\n \"status\",\n \"--porcelain\",\n \"--untracked-files=all\",\n \".\",\n ]);\n return output\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => line.slice(3).replace(/^.* -> /, \"\"));\n } catch {\n return undefined;\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AppIdentity {
|
|
2
|
+
readonly directoryName: string;
|
|
3
|
+
readonly packageName: string;
|
|
4
|
+
readonly appName: string;
|
|
5
|
+
readonly appId: string;
|
|
6
|
+
}
|
|
7
|
+
/** `my-app` -> { packageName: "my-app", appName: "My App", appId: "com.example.myapp" }. */
|
|
8
|
+
export declare function identityFor(directoryName: string): AppIdentity;
|
|
9
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,CAqB9D"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CreateError } from "./errors.js";
|
|
2
|
+
/** `my-app` -> { packageName: "my-app", appName: "My App", appId: "com.example.myapp" }. */
|
|
3
|
+
export function identityFor(directoryName) {
|
|
4
|
+
const ascii = directoryName.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
|
|
5
|
+
const words = ascii.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
6
|
+
const packageName = ascii
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9._~-]+/g, "-")
|
|
10
|
+
.replace(/^[._-]+/, "")
|
|
11
|
+
.replace(/-+$/, "");
|
|
12
|
+
const segment = words.join("").toLowerCase();
|
|
13
|
+
if (words.length === 0 || packageName === "" || segment === "") {
|
|
14
|
+
throw new CreateError(`"${directoryName}" cannot name a project. Use letters and digits, for example my-app.`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
directoryName,
|
|
18
|
+
packageName,
|
|
19
|
+
appName: words.map((word) => word[0].toUpperCase() + word.slice(1)).join(" "),
|
|
20
|
+
appId: `com.example.${/^[0-9]/.test(segment) ? `app${segment}` : segment}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=identity.js.map
|